问题
I want to fill a TStringList
inside a DLL. My approach seems to be wrong regarding memory management documentaion, but it works and doesn't cause an error or AV.
Can someone tell me, if that code is OK? Not sure how I can fill a class in general in a 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 Code:
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;
I don't use BORLNDMM.DLL or any other ShareMem unit.
Edit:
I expanded the aList.Add()
call to aList.AddObject()
. It also doesn't crash, altough the TObject is created in DLL and freed in EXE.
Answer:
Regarding the comments in the accepted answer below, that code is correct, since exe and dll are compiled with the same delphi version and only virtual methodes are invoked.
Conclusion:
As long as virtual methods or interfaces are used, there is no problem with memory management. That means, it doesn't matter where the object is created or freed.
回答1:
If you want to pass classes across module boundaries then you need to link to the RTL/VCL with runtime packages. That's the only way to make sure that the TStringList
class in your DLL is the exact same one as in your EXE. That is the fundamental problem with your current approach. On the other hand, if you are already linking to the RTL with runtime packages, then you are fine.
If you don't want to use runtime packages then you need to redesign your interface completely. You would need to stop passing classes across the module boundary. You can use interfaces, but not classes. And you would need to take control of the memory allocation to ensure that memory is always deallocated in the module that allocated it. Or start using ShareMem
.
回答2:
If you seriously oppose to BPLs then you'd better stick with COM conventions of DLLs and Interfaces.
In particular there are TStream-like interface in COM. And VCL has TStreamAdapter class to convert between COM IStream and VCL TStream.
That way your DLL should make a data stream, wrap it into COM IStream and pass to exe. EXE would convert back and fill stringlist from TStream.
More fast and lo-tech approach would be to feel memory buffers, like Windows API functions do. They either do feel it, or return program error asking for buffer of larger size. Well, then you would call function twice - to get buffer size and to do actual work. And if you mix the pointer types like PChar may be PAnsiChar or PWideChar, or pass wrong buffer size - you have no safety net from compiler, you just corrupted memory. But that would be faster than COM IStream.
Maybe you'd make COM-enabled Buffer object, which has special kind of destructor not freeing memory, but passing reference to DLL background idle memory recollection thread. So when you no more need it in main EXe it would sooner or later be freed in DLL itself. It is still not so comfort to use as TStream, but at least would hopefully not blow Heap manager.
回答3:
For this type of functionality, and to remain share-mem-free, package-free, I would use a callback with an enumerator method in the dll. This is how you retrieve the fonts from windows for example. Here is a mock up of what I'm referring to:
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;
This is just to give you a starting point.... on the DLL side you use call back into your program using the function pointer and pass in the strings 1 at a time.
回答4:
The easiest way to get a string list from a dll is that way: you have to create tStringList then fill it insside the Dll, then pass the text as return.
From Dll library:
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;
From the 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;
Usage:
procedure TfMain.Button1Click(Sender: TObject);
var
S:tStringList;
begin
S := tStringList.create;
GetDLLExternalDocuments('123456','AAAAA',S);
Showmessage(S.Text);
s.Free;
end;
来源:https://stackoverflow.com/questions/11777592/fill-a-tstringlist-in-a-dll