当我尝试从动态加载的dll访问宿主程序中的TDictionary变量时,我遇到了一个非常严重的问题。这是完整的代码,任何人都可以提供一些帮助?谢谢!
===========主程序项目源代码===================
program main;
uses
ShareMem,
Forms,
uMain in 'uMain.pas' {Form1},
uCommon in 'uCommon.pas';
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
============== unit uMain ================
unit uMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, uCommon;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
Tfoo = function(ADic: TMyDic): string; stdcall;
procedure TForm1.Button1Click(Sender: TObject);
var
Dic: TMyDic;
HLib: THandle;
foo: Tfoo;
begin
Dic := TMyDic.Create;
try
Dic.Add(1, 'World!');
Dic.Add(2, 'Hello, ');
HLib := LoadLibrary('Mydll.dll');
try
@foo := GetProcAddress(HLib, 'foo');
ShowMessage(foo(Dic));
finally
FreeLibrary(HLib);
end;
finally
Dic.Free;
end;
end;
end.
================= dll项目源代码=====================
library MyDll;
uses
ShareMem,
SysUtils,
Classes,
uCommon in 'uCommon.pas';
function foo(ADic: TMyDic):string; stdcall;
var
I: Integer;
S: string;
begin
for I in ADic.Keys do
begin
S := S + ADic[I];
end;
Result := s;
end;
exports
foo;
end.
================ unit uCommon ==============
unit uCommon;
interface
uses
SysUtils, Generics.Collections;
type
TMyDic = TDictionary<Integer, string>;
implementation
end.
答案 0 :(得分:5)
你有例外吗?可能是访问冲突或无效的指针操作?
如果DLL有自己的内存管理器,则无法在Delphi和DLL之间共享字符串和对象。由于您使用的是Delphi 2010,因此默认情况下应安装FastMM。添加“SimpleShareMem”作为DLL和EXE的使用列表中的第一件事,看看是否无法解决问题?
编辑:回应海报的其他信息:
卸载DLL后,您正在调用dic.free。即使你共享内存管理器,也会给你一个访问冲突。这就是原因。
免费调用TObject.Destroy,这是一个虚方法。编译器生成代码以在对象的虚拟方法表中查找它。但是VMT存储在特定于模块的静态存储器中,而不是存储器管理器分配的共享存储器中。您卸载了DLL并从对象中的VMT指针下方拉出了地毯,因此当它尝试调用虚拟方法时会出现访问冲突。
您可以通过确保在卸载DLL之前调用Free 来解决此问题。或者您可以使用运行时包而不是DLL,它可以通过将对象的VMT放在外部包中来解决此问题,该包在完成之前不会被卸载。
答案 1 :(得分:5)
我强烈反对在可执行文件和常规DLL之间传递对象实例。主要是出于您遇到的确切原因。如果重建DLL并且您以某种不兼容的微妙方式更改了对象会发生什么?
正如梅森所指出的,软件包是将应用程序划分为模块的首选方式。
答案 2 :(得分:0)
我终于找到了真正的问题!看起来像这样:“For..in keys”循环将导致TDictionary为其数据字段FKeyCollection创建一个实例:
function TDictionary<TKey,TValue>.GetKeys: TKeyCollection;
begin
if FKeyCollection = nil then
FKeyCollection := TKeyCollection.Create(Self);
Result := FKeyCollection;
end;
因此,当卸载dll时,FKeyCollection指向的内存也被释放,因此留下了“悬空指针”。
destructor TDictionary<TKey,TValue>.Destroy;
begin
Clear;
FKeyCollection.Free; //here will throw an exception
FValueCollection.Free;
inherited;
end;