我想在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版本编译的,并且只调用虚拟方法。
结论:
只要使用虚拟方法或接口,内存管理就没有问题。这意味着,创建或释放对象的位置并不重要。
答案 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;