Fill a TStringList in a DLL

流过昼夜 提交于 2020-01-04 03:54:33

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!