在DLL中填充TStringList

时间:2012-08-02 12:37:25

标签: delphi dll

我想在DLL中填充TStringList。关于内存管理文档,我的方法似乎是错误的,但它可以工作,不会导致错误或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 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:1)

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

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

这样你的DLL应该创建一个数据流,将其包装到COM IStream并传递给exe。 EXE将从TStream转换回并填充stringlist。

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

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

答案 2 :(得分:1)

对于这种类型的功能,并保持无共享,无包,我会在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。

答案 3 :(得分:0)

从dll获取字符串列表的最简单方法是这样的: 你必须创建tStringList然后在Dll中填充它,然后将文本作为return返回。

来自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;