Delphi:Dll -> DataSet.First 导致访问冲突

sta*_*rhu 0 delphi dll access-violation

我有一个用 Delphi 编写的简单 Dll:

library usr_d;

uses
  System.SysUtils, System.Classes, DB,Vcl.Dialogs
  ;

{$R *.res}

Procedure SetMyData(DataSet: TDataSet);export;
begin
  if Assigned(DataSet) then
  begin
    ShowMessage(DataSet.FieldByName('MyFieldName1').AsString);
    try
      DataSet.First;
    except on E: Exception do
      ShowMessage('Error accessing field: ' + E.Message);
    end;
  end
  else
    ShowMessage('DataSet parameter is not assigned!');
end;

exports
  SetMyData;

begin
end.    
Run Code Online (Sandbox Code Playgroud)

我这样称呼它:

Procedure TMainForm.CallMyDll;

type
  TSetMyData = procedure(DataSet: TDataSet); stdcall;

Var
  MyHandle               : HMODULE;
  SetMyData              : TSetMyData;  

Begin
  MyHandle := LoadLibrary('c:\MyFolder\usr_d.dll');
  if MyHandle <> 0 then
   begin
     @SetMyData := GetProcAddress(MyHandle, 'SetMyData');
     if @SetMyData <> nil then
      begin
        SetMyData(MyQuery as TDataSet);
      end;
     FreeLibrary(MyHandle);
   end;
End;
Run Code Online (Sandbox Code Playgroud)

当我运行程序并调用Dll的过程时,“ShowMessage(DataSet.FieldByName('MyFieldName1').AsString);” 工作正常,它显示字段值。我用几个不同的数据集尝试过。

但是,如果我发出:“DataSet.First;” 或“DataSet.Next;”,然后我收到访问冲突,访问字段时出错。

我应该怎么办?谢谢你!

Rem*_*eau 12

当制作普通DLL 时,您只能跨 DLL 边界交换普通的 C 兼容类型(即整数、字符等)。您根本无法在普通DLL中跨 DLL 边界使用重要数据,例如 Delphi RTL/VCL 对象。

您需要创建一个包 (BPL),它是一种特殊类型的 DLL,具有对 RTL/VCL 框架的内置支持。

并且,为了在包中通过 DLL 边界共享 Delphi 对象,您还需要让 BPL 项目和调用项目(无论是 EXE 还是另一个 BPL)启用运行时包,以便它们可以共享单个RTL/VCL 框架实现的实例、内存管理器、RTTI 等。


如果无法将 DLL 重新设计成包,那么您将不得不改变整个方法。例如,您可以使用回调函数,以便与对象相关的所有内容都保留在 DLL 边界的一侧,例如:

MyCommon.pas

type
  TMyCallbackFuncs = record
    GetFieldStr: function(FieldName, Buffer: PChar; BufLen: Integer; UserData: Pointer): Integer; stdcall;
    SetDataToFirst: procedure(UserData: Pointer); stdcall;
  end;
Run Code Online (Sandbox Code Playgroud)

usr_d.pas

library usr_d;

uses
  System.SysUtils, System.Classes, Vcl.Dialogs, MyCommon;

{$R *.res}

procedure SetMyData(var Callbacks: TMyCallbackFuncs; UserData: Pointer); stdcall; export;
var
  Value: array[0..255] of Char;
begin
  try
    Callbacks.GetFieldStr('MyFieldName1', Value, Length(Value), UserData);
    ShowMessage(Value);
    Callbacks.SetDataToFirst(UserData);
  except
    ShowMessage('Error accessing field');
  end;
end;

exports
  SetMyData;

begin
end.    
Run Code Online (Sandbox Code Playgroud)

CallMyDll.pas

uses
  ..., MyCommon;

function GetFieldStr(FieldName, Buffer: PChar; BufLen: Integer; UserData: Pointer): Integer; stdcall;
begin
  StrPLCopy(Buffer, TDataSet(UserData).FieldByName(FieldName).AsString, BufLen-1);
  Result := StrLen(Buffer);
end;

procedure SetDataToFirst(UserData: Pointer); stdcall;
begin
  TDataSet(UserData).First;
end;

procedure TMainForm.CallMyDll;
type
  TSetMyData = procedure(var Callbacks: TCallbackFuncs; UserData: Pointer); stdcall;
var
  MyHandle  : HMODULE;
  SetMyData : TSetMyData;  
  Callbacks : TMyCallbackFuncs;
begin
  MyHandle := LoadLibrary('c:\MyFolder\usr_d.dll');
  if MyHandle <> 0 then
  begin
    @SetMyData := GetProcAddress(MyHandle, 'SetMyData');
    if @SetMyData <> nil then
    begin
      Callbacks.GetFieldStr := @GetFieldStr;
      Callbacks.SetDataToFirst := @SetDataToFirst;
      SetMyData(Callbacks, MyQuery as TDataSet);
    end;
    FreeLibrary(MyHandle);
  end;
end;
Run Code Online (Sandbox Code Playgroud)