3

我想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

4 回答 4

4

如果要跨模块边界传递类,则需要使用运行时包链接到 RTL/VCL。这是确保TStringListDLL 中的类与 EXE 中的类完全相同的唯一方法。这是您当前方法的根本问题。另一方面,如果您已经使用运行时包链接到 RTL,那么您就可以了。

如果您不想使用运行时包,那么您需要完全重新设计您的界面。您需要停止跨模块边界传递类。您可以使用接口,但不能使用类。而且您需要控制内存分配,以确保始终在分配它的模块中释放内存。或开始使用ShareMem.

于 2012-08-02T13:38:30.353 回答
2

对于这种类型的功能,为了保持无共享内存、无包,我将在 dll 中使用带有枚举器方法的回调。例如,这就是您从 Windows 检索字体的方式。这是我所指的模型:

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。

于 2012-08-02T20:00:15.507 回答
1

如果你真的反对 BPL,那么你最好坚持 DLL 和接口的 COM 约定。

特别是在 COM 中有类似 TStream 的接口。VCL 有 TStreamAdapter 类在 COM IStream 和 VCL TStream 之间进行转换。

这样,您的 DLL 应该创建一个数据流,将其包装到 COM IStream 中并传递给 exe。EXE 将从 TStream 转换回来并填充字符串列表。

更快速和低技术的方法是感觉内存缓冲区,就像 Windows API 函数一样。他们要么感觉到它,要么返回程序错误,要求更大的缓冲区。好吧,那么您将调用函数两次 - 以获取缓冲区大小并进行实际工作。而且,如果您混合使用 PChar 之类的指针类型可能是 PAnsiChar 或 PWideChar,或者传递错误的缓冲区大小 - 您没有来自编译器的安全网,您只是损坏了内存。但这会比 COM IStream 快。

也许你会创建支持 COM 的 Buffer 对象,它有一种特殊的析构函数,它不会释放内存,而是将引用传递给 DLL 后台空闲内存回收线程。因此,当您在主 EXe 中不再需要它时,它迟早会在 DLL 本身中被释放。使用 TStream 仍然不是那么舒服,但至少希望不会打击堆管理器。

于 2012-08-02T15:31:14.167 回答
0

从 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;
于 2016-09-10T16:47:07.623 回答