Заполнить TStringList в DLL
Я хочу заполнить TStringList
внутри DLL. Мой подход кажется неправильным в отношении документации по управлению памятью, но он работает и не вызывает ошибку или AV.
Может кто-нибудь сказать мне, если этот код в порядке? Не уверен, как я могу заполнить класс в целом в DLL.
programm EXE
function MyClass_Create: IMyClass; stdcall; external ...
var
_myClass_DLL: IMyClass; //shared interface in exe and dll
procedure FillList;
var
list: TStringList;
begin
list := TStringList.Create(true); //memory allocated in EXE
try
_myClass_DLL.FillList(list); //memory allocated in DLL???
ShowMessage(list.Text);
finally
list.Free; //memory freed in EXE, frees also TObject created in DLL
end;
end;
Код DLL:
library DLL
TMyClass = class(TInterfacedObject, IMyClass)
public
procedure FillList(aList: TStringList);
end;
procedure TMyClass.FillList(aList: TStringList);
begin
aList.AddObject('Text1', TObject.Create); //memory allocation in DLL?
aList.AddObject('Text2', TObject.Create); //memory allocation in DLL?
end;
Я не использую BORLNDMM.DLL или какой-либо другой модуль ShareMem.
Редактировать:
Я расширил aList.Add()
позвонить aList.AddObject()
, Он также не падает, хотя объект TObject создается в DLL и освобождается в EXE.
Ответ:
Что касается комментариев в принятом ответе ниже, этот код является правильным, так как exe и dll скомпилированы с одной и той же версией delphi, и только виртуальные методы вызываются.
Заключение:
Пока используются виртуальные методы или интерфейсы, нет проблем с управлением памятью. Это означает, что не имеет значения, где объект создан или освобожден.
4 ответа
Если вы хотите передавать классы через границы модуля, вам нужно связать RTL/VCL с пакетами времени выполнения. Это единственный способ убедиться, что TStringList
класс в вашей DLL точно такой же, как в вашем EXE. Это фундаментальная проблема с вашим текущим подходом. С другой стороны, если вы уже подключаетесь к RTL с помощью пакетов времени выполнения, то все в порядке.
Если вы не хотите использовать пакеты времени выполнения, вам нужно полностью изменить интерфейс. Вам нужно будет остановить передачу классов через границу модуля. Вы можете использовать интерфейсы, но не классы. И вам нужно будет взять под контроль распределение памяти, чтобы гарантировать, что память всегда освобождается в модуле, который ее выделил. Или начать использовать ShareMem
,
Для этого типа функциональности и для того, чтобы остаться без общего ресурса, без пакета, я бы использовал обратный вызов с методом enumerator в dll. Вот как вы получаете шрифты из окон, например. Вот макет того, что я имею в виду:
type
TMyClass = class
private
FList: TStringList; // obv you need to construct this
public
function EnumListItem(s: string): integer;
end;
function TMyClass.EnumListItem(s: string): integer;
begin
FList.Add(s);
end;
procedure TMyClass.FillList;
begin
_myClass_DLL.FillList(@EnumListItem);
ShowMessage(FList.Text);
end;
Это просто для того, чтобы дать вам отправную точку.... на стороне DLL вы используете обратный вызов в вашу программу, используя указатель функции, и передаете строки по 1 за раз.
Если вы серьезно противостоите BPL, то вам лучше придерживаться COM-соглашений DLL и интерфейсов.
В частности есть TStream-подобный интерфейс в COM. И VCL имеет класс TStreamAdapter для преобразования между COM IStream и VCL TStream.
Таким образом, ваша DLL должна создать поток данных, обернуть его в COM IStream и передать в exe. EXE конвертирует обратно и заполняет список строк из TStream.
Более быстрый и технологичный подход - чувствовать буферы памяти, как это делают функции Windows API. Они либо чувствуют это, либо возвращают ошибку программы, запрашивая буфер большего размера. Что ж, тогда вы вызываете функцию дважды - чтобы получить размер буфера и выполнить реальную работу. И если вы смешиваете типы указателей, например PChar, это может быть PAnsiChar или PWideChar, или вы передаете неверный размер буфера - у вас нет защитной сетки от компилятора, вы просто повредили память. Но это будет быстрее, чем COM IStream.
Возможно, вы бы создали объект буфера с поддержкой COM, который имеет специальный вид деструктора, который не освобождает память, а передает ссылку на нить воспоминания о незанятой памяти в фоновом режиме DLL. Поэтому, когда он больше не нужен в основном EXe, он рано или поздно будет освобожден в самой DLL. Это все еще не так удобно для использования, как TStream, но, по крайней мере, мы надеемся, что не ударить менеджер кучи.
Самый простой способ получить список строк из dll - это способ: вам нужно создать tStringList, затем заполнить его внутри Dll, а затем передать текст в качестве возврата.
Из библиотеки Dll:
function getDocuments(customer,depot:Pchar):Pchar;export;
var
s:TstringList;
begin
S:=TStringList.Create;
S.Add('Row 1 '+customer);
S.Add('Row 2 '+depot);
S.Add('Row 3 ');
S.Add('Row 4 ');
S.Add('Row 5 ');
Result:=pchar(s.Text);
S.Free;
end;
Из EXE
function GetDLLExternalDocuments(customer,depot:pchar;out fList:TStringList):Word;
var GetDocumentsDLLExport:TGetDocumentsDLLExport;
var s:String;
var HandleDllExport :Thandle;
begin
HandleDllExport := LoadLibrary('my_dll_library.dll');
if HandleDllExport <> 0 then
begin
@GetDocumentsDLLExport := GetProcAddress(HandleDllExport, 'getDocuments');
if @GetDocumentsDLLExport <> nil then
begin
s:=GetDocumentsDLLExport(cliente,impianto);
fList.Text:=S;
result:=0;
end;
FreeLibrary(HandleDllExport);
HandleDllExport:=0;
end;
end;
Использование:
procedure TfMain.Button1Click(Sender: TObject);
var
S:tStringList;
begin
S := tStringList.create;
GetDLLExternalDocuments('123456','AAAAA',S);
Showmessage(S.Text);
s.Free;
end;