Получил большую проблему: TDictionary и DLL в Delphi 2010! - PullRequest
1 голос
/ 17 июля 2010

У меня возникла очень серьезная проблема, когда я пытаюсь получить доступ к переменной TDictionary в хост-программе из динамически загруженной библиотеки DLL.Вот полный код, кто-нибудь может помочь?спасибо!

=========== Исходный код основного проекта программы ==================

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.

============== Единица 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.

================ единица uCommon ==============

unit uCommon;

interface
uses
  SysUtils, Generics.Collections;

type
  TMyDic = TDictionary<Integer, string>;



implementation

end.

Ответы [ 3 ]

5 голосов
/ 17 июля 2010

Я бы настоятельно не рекомендовал передавать экземпляры объектов между исполняемым файлом и обычной DLL.Главным образом по точным причинам, с которыми вы сталкиваетесь.Что произойдет, если DLL будет перестроена, и вы изменили объект каким-то несовместимым тонким способом?

Как указывает Мейсон, пакеты являются предпочтительным способом разделения вашего приложения на модули.

5 голосов
/ 17 июля 2010

Вы получаете исключения? Может быть, нарушения прав доступа или недействительные операции с указателями?

Вы не можете обмениваться строками и объектами между Delphi и DLL, если у DLL есть собственный менеджер памяти. Поскольку вы используете Delphi 2010, у вас должен быть установлен FastMM по умолчанию. Добавьте «SimpleShareMem» в качестве первого пункта в использует список для DLL и EXE, и посмотрите, не решит ли это проблему?

РЕДАКТИРОВАТЬ: В ответ на дополнительную информацию от автора:

Вы вызываете dic.free после выгрузки DLL. Даже если вы поделитесь диспетчерами памяти, это приведет к нарушению прав доступа. Вот почему.

Бесплатные звонки TObject.Destroy, который является виртуальным методом. Компилятор генерирует код для поиска в таблице виртуальных методов объекта. Но VMT хранится в статической памяти, специфичной для модуля, а не в разделяемой памяти, выделенной диспетчером памяти. Вы выгружали DLL и вытаскивали коврик из-под указателя VMT в объекте, поэтому при попытке вызова виртуального метода вы получаете нарушение прав доступа.

Это можно исправить, вызвав Free до выгрузки DLL. Или вы можете использовать пакеты времени выполнения вместо DLL, которая решает эту проблему, помещая VMT для объекта во внешний пакет, который не будет выгружен, пока вы не покончили с ним.

0 голосов
/ 18 июля 2010

Наконец-то я понял, в чем настоящая проблема!Это выглядит так: цикл «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;
...