procedure AddToList(Item: PChar); stdcall; external 'SomeDll.dll'; ... procedure TForm1.Button1Click(Sender: TObject); var X: Integer; begin for X := 0 to ListBox1.Items.Count - 1 do AddToList(PChar(ListBox1.Items[X])); end;Объяснить поведение кода и как его нужно исправить.
Ответ будет опубликован через две недели.
Ответ.
а) а оно скомпилируется? функция без указания результирующего типа..
ОтветитьУдалитьб) а версия компилятора юникодная? или это в данном случае не имеет значения?..
а, точно, скомпилируется.. такое допускается
ОтветитьУдалитьне, всё-таки надо либо отдельно написать прототип вызова (с параметрами и результатом) и далее связь с библиотекой без указания параметров в функции, либо должен быть указан результирующий тип. либо идти спать...
ОтветитьУдалить(сорри :з)
Прошу прощения, это ошибка, конечно же.
ОтветитьУдалитьpchar это поинтер в длл передаваемый адрес будет смотреть не тудой
ОтветитьУдалить"pchar это поинтер в длл передаваемый адрес будет смотреть не тудой"
ОтветитьУдалитьЭто вряд ли... "тудой"... Мы такое используем...
удивляюсь дотошности и интеллекту Александра...
ОтветитьУдалитьведь как всегда окажется - либо фреймы не такие.. либо стек...
откуда вы это берёте?
Если уж мы передаём указатель в dll, то лучше писать явно: PAnsiChar или PWideChar а то может быть очень весело, если в dll мы ожидаем анси, а прилетит юникод.
ОтветитьУдалитьВсе что касается передачи строковых параметров во внешние функции в DLL всегда вызывало у меня дикий бургурт. Предположу, что для того, чтобы нормально все передалось, лучше завести отдельный массив из Char, складывать строку из ListBox1 сначала туда, а потом уже передавать указатель на этот массив внутрь DLL. В таком варианте все явно, нигде ничего не течёт, указатель в конце прибиваем.
ОтветитьУдалитьЧувствую, что подвох с хранением строкового значения Result для неявно вызываемой функции Strings.GetItem(X).
И я, конечно же, искренне надеюсь на то, что сферическая функция AddToList складывает в куда-то _не_ указатели на строки.
Указать явно PAnsiChar вместо PChar
ОтветитьУдалитьСоглашусь со Степаном. GetItem создает локальную строку в Result. Далее, поскольку она ничему не присваивается, то к моменту вызова счетчик ссылок уже будет обнулен и строка освобождена.
ОтветитьУдалитьМаксим
Надо явно выделять память под передаваемый PChar.
ОтветитьУдалитьЕсли бы было что-то типа
procedure JustShow(Item: PChar); stdcall; external 'SomeDll.dll';
с выводом, например, MessageBox с текстом, то приведённый код рабочий.
Согласен с двумя ораторами, отписавшимися выше, именно так.
ОтветитьУдалить>>GetItem создает локальную строку в Result. Далее, поскольку она ничему не присваивается, то к моменту вызова счетчик ссылок уже будет обнулен и строка освобождена.
ОтветитьУдалитьИ тем не менее даже в самом LIstBox такой подход используется.
procedure TCustomListBox.CopySelection(Destination: TCustomListControl);
...
Destination.AddItem(PChar(Items[ItemIndex]), Items.Objects[ItemIndex]);
..
на момент вызова AddToList - указатель будет гарантированно валидный. а вот если этот указатель где то запоминается, тогда да. Будет бадабум
Название процедуры в dll - напрягает. Есть вероятность, что кто-то захочет удалить элемент из листа. А память чистит вызываемая подпрограмма.
ОтветитьУдалитьЕсли листбокс пустой, процедура попробует обратиться к несуществующему ийтему.
ОтветитьУдалитьДолжно быть так:
begin
if ListBox1.Items.Count >0 then
for X := 0 to ListBox1.Items.Count - 1 do
AddToList(PChar(ListBox1.Items[X]));
end;
>Если листбокс пустой, процедура попробует обратиться к несуществующему ийтему.
ОтветитьУдалитьБред. Идите читайте работу с циклами.
помоему все дело в pchar и в чьей области памяти будет выделятся область под нее
ОтветитьУдалитьСудя по названию импортированной функции, она добавляет строку типа Pchar в какой-то список. Отсюда можно предположить, что данный список будет потом как-то используется. Проблема состоит в том, что типы string и pchar не совместимы, а это значит, что в момент преобразования будет создана локальная переменная типа PChar, которая и будет передана AddToList. Далее, поскольку переменная локальная, то она будет уничтожена после вызова. Следовательно, в итоге наш список строк PChar будет пуст. А если точнее, то будет содержать неверные ссылки.
ОтветитьУдалить>Судя по названию импортированной функции, она добавляет строку типа Pchar в какой-то список
ОтветитьУдалитьА с чего вы взяли, что она (функция) добавляет именно указатель? Это вообще тогда будет детская ошибка. Но наверняка предполагается что в dll обработка сделана как положено, без ошибок (через копирование строки), а проблемный код - перед нами.
Повторюсь - реально что здесь возможно, так это путаница с Ansi/Wide Char, если используются разные версии компиляторов.
>А с чего вы взяли, что она (функция) добавляет именно указатель?
ОтветитьУдалитьА что такое по Вашему тип PChar? Это и есть указатель.
>А что такое по Вашему тип PChar? Это и есть указатель.
ОтветитьУдалитьНу так это всего лишь параметр функции. И в Delphi есть такая замечательная функция - StrPas (http://docwiki.embarcadero.com/Libraries/XE5/en/System.SysUtils.StrPas) которая из этого указателя сделает полноценную строку, которую уже можно хранить сколь угодно долго.
>И в Delphi есть такая замечательная функция - StrPas
ОтветитьУдалитьТ.е., Вы предполагаете, что библиотека, из которой вызывается функция AddToList, тоже написана на Delphi и собирается хранить строки в TStrings? А вот мне кажется, что библиотека может быть написана на С++, к примеру, и работает она именно с указателями, поскольку строки могут быть очень длинными, и копирование строк может приводить к неоправданному расходу вычислительных ресурсов.
Думаю, что с кодом всё в порядке. Можно что-то испортить в AddToList, но при грамотной реализации данной функции ошибок не будет.
ОтветитьУдалитьАлексей Гаибов
ОтветитьУдалитьВ условиях задачи написано: "Объяснить поведение кода и как его нужно исправить". Кода функции в dll мы не видим, а значит и исправлять там ничего не надо. И не важно, на каком языке он написан. Функцию StrPas я привёл как пример копирования строки. Аналогичные функции будут и в других языках программирования.
Если вы убеждены, что библиотека оперирует указателем и никак не копирует строку во внутреннее представление, то как по вашему нужно исправить представленный код на Delphi, чтобы всё работало и небыло бы утечек памяти? Думаю понятно, что изменять API интерфейс dll и добавлять функцию выделения памяти нельзя? Хотя, я думаю, что даже добавив такую функцию, но оставив тип PChar и не беря в расчёт его изменчивый размер в Delphi, у вас всё равно останется код с ошибкой.
Оформлю полный ответ:
ОтветитьУдалить1. Нужно выяснить какой тип строк ожидает dll (ansi или wide) и исправить соответствующим образом прототип функции.
2. В соответствии с новым прототипом функции из dll, привести нашу строку к ожидаемому типу (если у нас юникодные строки, а dll ожидает ansi, то мы столкнёмся с потенциальной потерей информации, т.к. однозначно конвертировать wide в ansi не всегда возможно).
3. Передать в dll указатель на подготовленную строку правильного типа.
Если вы убеждены, что библиотека оперирует указателем и никак не копирует строку во внутреннее представление, то как по вашему нужно исправить представленный код на Delphi, чтобы всё работало и небыло бы утечек памяти? Думаю понятно, что изменять API интерфейс dll и добавлять функцию выделения памяти нельзя?
ОтветитьУдалитьА я ничего в DLL исправлять не предлагаю. Достаточно явным образом выделять память для строки, которую передаем в импортированную функцию:
StrNew(PChar(ListBox1.Items[X]))
В этом случае строка будет размещена в куче, и останется там после вызова импортированной функции.
Конечно, потом нужно будет освободить память, которую использует строка, но это забота уже другой части программы. Ведь, как я уже говорил: судя по названию функции список где-то используется.
Ну сами-то подумайте - стоило ли приводить такой код только для того, чтобы проиллюстрировать возможные проблемы с PANSIChar/PWideChar? (хотя они, конечно, тоже могут быть)
>Конечно, потом нужно будет освободить память
ОтветитьУдалитьА вы уверены, что dll написана таким образом, что у неё есть соответствующий метод, который вызывает нашу функцию освобождения памяти? Мне кажется, что это гораздо более смелое предположение по поводу поведения кода в dll, нежели предположение, что там выполняется копирование строки. По-моему как раз наоборот, раз нам ничего не сказано, что такая функция есть, то мы должны считать, что её нет, и всё взаимодействие dll с нашим кодом - это эта одна-единственная функция.
>Ну сами-то подумайте - стоило ли приводить такой код только для того, чтобы проиллюстрировать возможные проблемы с PANSIChar/PWideChar? (хотя они, конечно, тоже могут быть)
Судя по ответам - таки стоило :)
А вы уверены, что dll написана таким образом, что у неё есть соответствующий метод, который вызывает нашу функцию освобождения памяти?
ОтветитьУдалитьНашу функцию? Зачем? Я предполагаю, и это вобщем-то достаточно стандартный подход, что Список, который создается при помощи Dll, уничтожает стоки при своем уничтожении (либо ему можно указать как поступать, как например в TObjectList). В любом случае здравый смысл подсказывает, что я в контексте данной программы буду иметь доступ к элементам списка, и следовательно, смогу корректно освободить память.
Осталось только узнать - что предполагал автор :)
>Нашу функцию? Зачем?
ОтветитьУдалитьКак зачем? Если мы выделили память при помощи StrNew, то dll самостоятельно эту память никаким способом освободить не сможет, поскольку строкой заведует наш менеджер памяти.
>В любом случае здравый смысл подсказывает, что я в контексте данной программы буду иметь доступ к элементам списка, и следовательно, смогу корректно освободить память.
ОтветитьУдалитьНе соглашусь. Во-первых это очень кривое API должно быть, а во-вторых, в этом случае в API должна быть оговорка большими красными буквами: мы не копируем строки и у нас нету спец. методов, которые будут дёргать ваш код, чтобы вы могли освободить память, поэтому пользуйтесь итератором по списку для освобождения памяти и не забудьте удалять элементы из списка, после освобождения памяти. А если там ещё и многопоточность будет завязана - вообще труба.
AddToList - предполагает что? ЧТЕНИЕ из переданного указателя? Или ЗАПИСЬ в него?
ОтветитьУдалитьAddToList предполагает передачу строки из приложения в dll.
ОтветитьУдалитьАлександр, а можно сначала?
ОтветитьУдалитьМне так кажется, что идеологически dll-ка в плагино-подобном стиле должна выполнять действия по обработки "глобального" хранилища данных, который управляется из ядра?
Или как, dll-ка для выполнения своей операции вынуждена "откачивать" себе данных немного?
Руки-то чешутся не входить в эту ситуацию, заполнить контейнер в ядре, а dll-ке дать на обработку (блин) указатель на (под)множество хранимых данных?
А если посмотреть дальше в сторону разбиения "ядро-dll-ки" на концепцию сервисов с "бесшовным" преодолением адресного пространства, то "транспортное" проталкивание элементов списка по указателю вообще не поедет.
Короче :) Можно прикладной контекст дать?
Если AddToList сразу обрабатывает строку, или делает из нее локальную копию и нет путаницы с ansi - widechar, то никаких проблем в коде нет. У меня прекрасно подобный код работает с библиотеками без всяких ошибок.
ОтветитьУдалитьУсловие задачи явно неполное, непонятно что происходит внутри dll.
> откуда вы это берёте?
ОтветитьУдалитьжизнь подкидывает примеры: http://www.sql.ru/forum/1049047/kak-v-funkci-peredat-massiv-pchar :о)
Так что не так с этим кодом-то, две недели вроде бы как бы прошло уже :)
ОтветитьУдалитьХм.. предположу следующее:
ОтветитьУдалитьPChar(ListBox1.Items[X]) по-сути создает локальную переменную внутри функции, куда закидывает сей pchar, при выходе из функции эта память станет "неиспользуемой", т.к. в отличии от string, pchar не содержит ссылок использования (или как они там называются))). Соответственно эта память может быть перезаписана чем угодно и при обращении внутри dll, там может оказаться любой мусор)).
Я достаточно давно программировал на delphi, но, насколько помню, нужно выделить память отдельно, скопировать туда строку, а потом уже передавать указатель... Ну, а очистку памяти оставим на усмотрение разработчика ;)
При переводе String в PChar может потеряться часть строки из-за #0
ОтветитьУдалитьСудя по всему, блог заброшен, печально ;(
ОтветитьУдалитьПоспешный вывод, с последней публикации всего месяц прошел.
ОтветитьУдалить>Что не так с этим кодом?
ОтветитьУдалитьВсё не так: написан на мертвом языке, вызывается перерисовка ListBox на каждом добавлении из-за отсутствия BeginUpdate/EndUpdate, противоречащие сути ООП антипаттерны.
>как его нужно исправить
Переписать на C#, избегая костыльных решений
Да все тут вроде нормально. Само собой разумеется, что DLL-ка имеет тот же тип кодировки строк и при вызове AddToList выделит у себя память и скопирует туда текст, а не будет складировать указатели 'на потом' )
ОтветитьУдалитьАнонимный комментирует...
ОтветитьУдалить>Что не так с этим кодом?
Всё не так: написан на мертвом языке, вызывается перерисовка ListBox на каждом добавлении из-за отсутствия BeginUpdate/EndUpdate, противоречащие сути ООП антипаттерны.
Вам и живой язык не поможет с такими понятиями интересными =)
Причем тут перерисовка ListBox и ООП ?
var i: word;
ОтветитьУдалитьbegin
for i:=0 to List1.Count-1 do
ShowMessage(List1.Items[i]);
...
Если в списке 0 элементов, появится исключение о недопустимом индексе
http://lurkmore.to/Быдлокод
Исправте свой косяк var i: word; на то, как показано в примере var i: integer; и уже в новом свете перечитайте лурк ;)
ОтветитьУдалитьВ тонкостях я не мастер, но мне кажется что с PChar всё нормально, подразумеваться же PAnsiChar по умолчанию. Есть и в Windows.pas строки вида:
ОтветитьУдалитьfunction MessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
Насчёт procedure вроде тоже сойдёт, тоже что и void функция. Остальное тоже вроде без помарок, только не понятно направление данных - не dll заполняет лист, а наоборот, странно.
Может с ним всё нормально? С:
Не знаю каков правильный ответ, но я создал из примера Чудищще... И оно живое... Память как ни странно не ест, но Винда уходит просто в Астрал...
ОтветитьУдалитьlibrary aSomeDll;
uses Windows;
var w: HWND;
procedure SetWND(h: HWND); stdcall;
begin
w:=h;
end;
procedure AddToList(Item: PChar); stdcall;
begin
SetWindowText(w,Item);
end;
exports SetWND, AddToList;
begin
end.
. . .
procedure AddToList(Item: PChar); stdcall; external 'aSomeDll.dll';
procedure SetWND(w: HWND); stdcall; external 'aSomeDll.dll';
procedure TForm1.FormCreate(Sender: TObject);
begin
SetWND(Handle);
ListBox1.Items.Add('0100');
ListBox1.Items.Add('1001');
ListBox1.Items.Add('011-');
end;
procedure TForm1.Button1Click(Sender: TObject);
var X: Integer;
begin
for X:=0 to ListBox1.Items.Count-1 do AddToList(PChar(ListBox1.Items[X]));
end;
procedure TForm1.Button2Click(Sender: TObject);
var X: Integer;
begin
for X:=0 to MaxInt do Button1.Click;
end;
Ни в коем случае не запускайте! >:3
var X: Integer; // по ГОСТу имена переменных с мелкой буквы нужно! :D
ОтветитьУдалитьОтвета уже не ждать?
ОтветитьУдалитьАлександр, не томи, por favore) Что там с памятью?
ОтветитьУдалитьВ чём подвох? Код-то работает...
ОтветитьУдалитьfor x:=0 to listbox1.Items.Count-1 do
MessageBox(0,pchar(listbox1.Items[x]),nil,0);
Кстати, кто может объяснить по какой причине listbox1.Items[x] на каждой итерации возвращает одну и ту же строку (указатель на один и тот же блок меняющихся данных)? И почему счётчик ссылок на этот блок равен единице и остаётся таким до самого выхода из процедуры, сколько бы ни было дальше кода?
PChar(s) возвращает указатель на (null-terminated) строку, но не изменят счетчик ссылок исходной строки. Исходная строка будет жить, пока на неё есть ссылки, т.е. как минимум до момента, пока из списка не будет удален соответствующий элемент. А потом - как повезет. Если на строку была только одна ссылка, то область памяти, где размещалась строка, может быть использована повторно. В этом случае при обращении к строки из DLL можно получить просто мусор, а можно и ошибку доступа к памяти.
ОтветитьУдалитьПодробнее здесь: http://www.transl-gunsmoker.ru/2009/09/pchars.html#mixing (Использования вместе String и PChar)
PChar(s) возвращает указатель на (null-terminated) строку, но не изменят счетчик ссылок исходной строки. Исходная строка будет жить, пока на неё есть ссылки, т.е. как минимум до момента, пока из списка не будет удален соответствующий элемент. А потом - как повезет. Если на строку была только одна ссылка, то область памяти, где размещалась строка, может быть использована повторно. В этом случае при обращении к строки из DLL можно получить просто мусор, а можно и ошибку доступа к памяти.
ОтветитьУдалитьПодробнее здесь: http://www.transl-gunsmoker.ru/2009/09/pchars.html#mixing (Использования вместе String и PChar)