Заполнить 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;
Другие вопросы по тегам