Delphi 在DLL中填充TStringList

Delphi 在DLL中填充TStringList,delphi,dll,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

我想在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版本编译,并且只调用虚拟方法

结论:

只要使用虚拟方法或接口,内存管理就没有问题。这意味着,对象在何处创建或释放并不重要。

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


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

如果您严重反对BPLs,那么您最好坚持DLL和接口的COM约定

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

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

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


也许您应该创建启用COM的缓冲区对象,它有一种特殊的析构函数,不释放内存,而是将引用传递给DLL后台空闲内存回忆线程。所以,当您不再需要在主EXe中使用它时,它迟早会在DLL中释放。作为TStream使用仍然不太舒服,但至少希望不会破坏堆管理器。

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

从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;

这是否意味着我必须在exe程序的项目设置中设置标志“使用运行时包”?或者仅仅转换一个包中的dll就足够了吗?在上面的代码中,您只需要链接到RTL/VCL运行时包。如果您想在两个模块之间传递实现的类,那么您也需要将DLL转换为包。我不熟悉包。然后我可以像加载dll一样加载包吗?像“function MyClass_Create:IMyClass;stdcall;external'myPackage.bpl'”不,一旦设置了引用,您只需在exe中源文件的uses子句中列出包中所需的单元。所以您不需要再次声明该函数。这比从DLL导入要简单得多。非常感谢您的帮助!我对BPLs不太满意,因为当使用第三方组件时,它会产生很多依赖关系。你最好传递PChar,而不是跨越边界。
procedure TfMain.Button1Click(Sender: TObject);
var
 S:tStringList;
begin
  S := tStringList.create;
  GetDLLExternalDocuments('123456','AAAAA',S);
  Showmessage(S.Text);
  s.Free; 
end;