views:

161

answers:

4

Hi, I got a very serious problem when I'm trying to access TDictionary variable in host program from a dynamicly loaded dll. Here is the complete code, anyone can give some help? thanks!

===========main program project source code===================

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 project source code=====================

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.
+4  A: 

Are you getting exceptions? Maybe access violations or invalid pointer operations?

You can't share strings and objects between Delphi and a DLL if the DLL has its own memory manager. Since you're using Delphi 2010, you should have FastMM installed by default. Add "SimpleShareMem" as the first thing in the uses list for both the DLL and the EXE, and see if that doesn't fix the problem?

EDIT: In response to additional information from the poster:

You're calling dic.free after you unload the DLL. Even if you share memory managers, that's going to give you an access violation. Here's why.

Free calls TObject.Destroy, which is a virtual method. The compiler generates code to look it up in the object's Virtual Method Table. But the VMT is stored in static memory that's specific to the module, not in shared memory allocated by the memory manager. You unloaded the DLL and pulled the rug out from underneath the VMT pointer in the object, and so when it tries to call a virtual method you get an access violation.

You can fix this by making sure to call Free before unloading the DLL. Or you can use runtime packages instead of a DLL, which gets around this problem by putting the VMT for the object in an external package that won't be unloaded before you're done with it.

Mason Wheeler
Hi,Mason: Yes, this code will get an exception when 'Dic.Free' is execute. And, I have try your suggestion, add "ShareMem" or "SimpleShareMem" both in the uses list. But, the problem can't be resolved. thx!
@neo2010: OK, that helps. I'll edit my answer.
Mason Wheeler
Think you,Mason. I got it, your advise is very helpful to me!So, again, many, many thanks! :)
Hi, I finally found what the real problem is!For..in keys loop will cause TDictionary create an instance for its data field FKeyCollection:function TDictionary<TKey,TValue>.GetKeys: TKeyCollection;begin if FKeyCollection = nil then FKeyCollection := TKeyCollection.Create(Self); Result := FKeyCollection;end;When the dll is unloaded, the memory FKeyCollection pointed is also freed, thus left a "dangling pointer".destructor TDictionary<TKey,TValue>.Destroy;begin Clear; FKeyCollection.Free; FValueCollection.Free; inherited;end;
@neo2010 - please listen to Allen and NEVER share any TObject-derived class between a DLL and the main EXE, unless you are using Runtime Packages. Too many weird failures.
Warren P
+5  A: 

I would strongly discourage passing object instances between an executable and a regular DLL. Mainly for the exact reasons you are are encountering. What happens if the DLL is rebuilt and you've changed the object in some incompatible subtle way?

As Mason points out, packages are the preferred way to partition your application into modules.

Allen Bauer
And the object doesn't need to change itself. Correct me if I'm wrong, but compiler directives alone can alter the binary representation of the object. (If data alignment has no effect on objects, I'm pretty sure the "minimum enum size" switch will).
Ken Bourassa
Yes, that among other things. Adding or even rearranging the order of virtual methods in the declaration. Changing method parameters (types, number, etc) or calling convention.Then again, this still applies to packages, but at least the Windows loader should catch a lot of those things because the export signature of the methods will change. Also, the compiler will help to detect that the dependent modules will need to recompile.C++, OTOH, really leaves you twisting in the wind a lot more ;-)
Allen Bauer
Exactly. Use Packages or don't share objects.
Warren P
A: 

Hi, I finally found what the real problem is! It seems like this: "For..in keys" loop will cause TDictionary create an instance for its data field FKeyCollection:

function TDictionary<TKey,TValue>.GetKeys: TKeyCollection;
begin
  if FKeyCollection = nil then
    FKeyCollection := TKeyCollection.Create(Self);
  Result := FKeyCollection;
end;

So when the dll is unloaded, the memory that FKeyCollection pointed is also freed, thus left a "dangling pointer".

destructor TDictionary<TKey,TValue>.Destroy;
begin
  Clear;
  FKeyCollection.Free;  //here will throw an exception
  FValueCollection.Free;
  inherited;
end;
If you're still sharing a pointer to a TObject between the DLL and the main EXE, you're committing a Delphi "sin", and you will find more glitches in the future. Don't.
Warren P
A: 

Thinks to you guys all.

I have fix this problem by using COM.

I even bulit an out-process automation server.

It fits my program well.

Best regarts!