2012-08-02 68 views
3

我想在一個DLL中填充一個TStringList。我的方法在內存管理文檔中似乎是錯誤的,但它的工作原理並不會導致錯誤或AV。在一個DLL中填充一個TStringList

有人可以告訴我,如果該代碼是好的?不知道我怎樣才能在一個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編譯的,只有虛擬methodes被調用。

結論:
只要使用虛擬方法或接口,存在與存儲器管理沒有問題。這意味着,創建或釋放對象的位置並不重要。

回答

4

如果你想跨模塊邊界傳遞類,那麼你需要鏈接到運行時包的RTL/VCL。這是確保DLL中的TStringList類與您的EXE中的類完全相同的唯一方法。這是你目前的方法的根本問題。另一方面,如果你已經通過運行時包連接到RTL,那麼你沒事。

如果你不想使用運行時軟件包,那麼你需要完全重新設計你的界面。您需要停止在模塊邊界上傳遞課程。您可以使用接口,但不能使用類。您需要控制內存分配,以確保內存總是在分配內存的模塊中釋放。或者開始使用ShareMem

+0

這是否意味着,我必須設置標誌 「使用運行時包」 中該exe程序的項目設置?或者它只是足以將一個包中的DLL轉換? – 2012-08-02 13:54:59

+0

在上面的代碼中,您只需鏈接到RTL/VCL運行時軟件包。如果您想傳遞您在兩個模塊之間實現的類,那麼您需要將DLL轉換爲一個包。 – 2012-08-02 13:56:31

+0

我對包裝不熟悉。然後,我可以以與dll相同的方式加載軟件包嗎?例如「函數MyClass_Create:IMyClass; stdcall; external'myPackage.bpl'」 – 2012-08-02 14:13:03

1

如果你真的反對BPL,那麼你最好堅持使用DLL和接口的COM約定。

特別是在COM中有類似於TStream的接口。而VCL有TStreamAdapter類來在COM IStream和VCL TStream之間進行轉換。

這樣,您的DLL應該製作一個數據流,將其包裝到COM IStream並傳遞給exe。 EXE會從TStream轉換回來並填充字符串列表。

更快,更低技術的方法是感受內存緩衝區,就像Windows API函數一樣。他們要麼感覺到它,要麼返回程序錯誤,要求更大的緩衝區。那麼,你會調用函數兩次 - 獲得緩衝區大小和做實際工作。如果你混合像PChar這樣的指針類型可能是PAnsiChar或PWideChar,或者傳遞錯誤的緩衝區大小 - 你沒有編譯器的安全網,你只是損壞了內存。但是這比COM IStream快。

也許你會製作COM啓用的緩衝區對象,它具有特殊類型的析構函數而不是釋放內存,但是會傳遞對DLL後臺空閒內存回憶線程的引用。因此,當你在主EXE中不再需要它時,遲早會在DLL本身中釋放它。使用TStream仍然不是那麼舒適,但至少希望不會打擊堆管理員。

1

對於這種類型的功能,並保持無共享內存,無包,我會使用DLL中的枚舉方法的回調。例如,您可以從窗口檢索字體。這是一個模擬了什麼我指的是:你用

這只是給你一個起點....在DLL側回調到您的程序中使用的函數指針,並通過在一次一個字符串1中。

+1

你會更好地通過PChar而不是跨越邊界。 – 2012-08-03 07:32:27

+0

@DavidHeffernan同意。 – GDF 2012-08-06 21:48:56

0

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