【问题标题】:Fill a TStringList in a DLL在 DLL 中填充 TStringList
【发布时间】:2012-08-02 12:37:25
【问题描述】:

我想在 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 中释放的。

答案:
关于下面接受的答案中的 cmets,该代码是正确的,因为 exe 和 dll 是使用相同的 delphi 版本编译的,并且只调用了虚拟方法。

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

【问题讨论】:

    标签: delphi dll


    【解决方案1】:

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

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

    【讨论】:

    • 这是否意味着我必须在 exe 程序的项目设置中设置标志“使用运行时包”?还是把dll转换成一个包就够了?
    • 在上面的代码中,您只需要链接到 RTL/VCL 运行时包。如果你想在两个模块之间传递你实现的类,那么你也需要将你的 DLL 转换成一个包。
    • 我对包不熟悉。然后我可以像加载 dll 一样加载包吗?比如“function MyClass_Create: IMyClass; stdcall; external 'myPackage.bpl'”
    • 不,一旦您设置了引用,您只需在 exe 中源文件的 uses 子句中列出包中所需的单元。因此,您无需再次声明该函数。这比从 DLL 导入要简单得多。
    • 非常感谢您的帮助!我对 BPL 不太满意,因为当使用 3rd 方组件时,它会产生很多依赖。
    【解决方案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。

    【讨论】:

    • 你最好传递 PChar 而不是越界字符串。
    【解决方案3】:

    如果你真的反对 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 使用它仍然不是那么舒服,但至少希望不会破坏堆管理器。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-28
        • 1970-01-01
        • 2015-04-25
        • 1970-01-01
        • 2015-08-17
        相关资源
        最近更新 更多