You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi DLL调用中DataSet.First触发Access Violation问题求助

问题描述

编写了一个简单的Delphi DLL,调用时访问DataSet字段值正常,但执行DataSet.First或DataSet.Next时触发Access Violation错误。相关代码如下:

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.    

主程序调用代码

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;
问题原因

核心问题是DLL和主程序的RTL/VCL不是同一个实例。FieldByName仅读取当前记录的字段值,暂时未触发跨模块RTL冲突;但First/Next这类方法需要操作数据集内部迭代器、调用RTL内存管理或虚拟方法表,跨模块传递VCL对象时,不同实例的RTL无法正确处理这些操作,最终导致Access Violation。

解决方案

方案1:使用运行时包共享RTL/VCL

  • 同时在DLL和主程序的项目设置中,勾选**"Use runtime packages"**,确保两者使用相同的核心包(至少包含rtl、vcl、db)。
  • 这样DLL和主程序会共享同一个RTL/VCL实例,跨模块传递的TDataSet对象能正确调用所有方法。
  • 注意:发布程序时需要附带对应的BPL包文件(版本根据Delphi版本而定,比如rtl110.bpl、vcl110.bpl)。

方案2:传递数据而非直接传递VCL对象

避免跨模块传递TDataSet,改为从主程序提取数据后传递给DLL处理:

修改后的DLL代码

library usr_d;

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

{$R *.res}

Procedure ProcessMyData(const CurrentFieldVal: string; const AllRecords: TStringList); stdcall;
begin
  ShowMessage('当前字段值:' + CurrentFieldVal);
  if Assigned(AllRecords) then
  begin
    ShowMessage('总记录数:' + IntToStr(AllRecords.Count));
    // 按需处理所有记录
  end;
end;

exports
  ProcessMyData;

begin
end.

修改后的主程序调用代码

Procedure TMainForm.CallMyDll;
type
  TProcessMyData = procedure(const CurrentFieldVal: string; const AllRecords: TStringList); stdcall;
Var
  MyHandle       : HMODULE;
  ProcessMyData  : TProcessMyData;
  RecordList     : TStringList;
Begin
  RecordList := TStringList.Create;
  try
    // 从DataSet提取所有记录数据
    if MyQuery.Active then
    begin
      MyQuery.First;
      while not MyQuery.Eof do
      begin
        RecordList.Add(MyQuery.FieldByName('MyFieldName1').AsString);
        MyQuery.Next;
      end;
      MyQuery.First; // 回到初始位置
    end;

    MyHandle := LoadLibrary('c:\MyFolder\usr_d.dll');
    if MyHandle <> 0 then
    begin
      @ProcessMyData := GetProcAddress(MyHandle, 'ProcessMyData');
      if @ProcessMyData <> nil then
      begin
        if MyQuery.Active then
          ProcessMyData(MyQuery.FieldByName('MyFieldName1').AsString, RecordList)
        else
          ProcessMyData('', nil);
      end;
      FreeLibrary(MyHandle);
    end;
  finally
    RecordList.Free;
  end;
End;

方案3:使用接口封装数据集操作

定义共享接口,主程序实现接口后传递给DLL,接口的跨模块调用基于COM标准,能避免RTL实例冲突:

1. 定义共享接口单元(主程序和DLL都引用)

unit DataIntf;

interface

uses
  DB;

type
  IDataSetProcessor = interface(IInterface)
    ['{F5A4D2B1-7E0A-4F8D-8C1E-123456789ABC}'] // 替换为自己生成的GUID
    function GetFieldValue(const FieldName: string): string;
    procedure First;
    procedure Next;
    function IsEof: Boolean;
  end;

implementation

end.

2. 主程序实现接口

type
  TDataSetProcessor = class(TInterfacedObject, IDataSetProcessor)
  private
    FDataSet: TDataSet;
  public
    constructor Create(ADataSet: TDataSet);
    function GetFieldValue(const FieldName: string): string;
    procedure First;
    procedure Next;
    function IsEof: Boolean;
  end;

constructor TDataSetProcessor.Create(ADataSet: TDataSet);
begin
  inherited Create;
  FDataSet := ADataSet;
end;

function TDataSetProcessor.GetFieldValue(const FieldName: string): string;
begin
  if Assigned(FDataSet) then
    Result := FDataSet.FieldByName(FieldName).AsString
  else
    Result := '';
end;

procedure TDataSetProcessor.First;
begin
  if Assigned(FDataSet) then
    FDataSet.First;
end;

procedure TDataSetProcessor.Next;
begin
  if Assigned(FDataSet) then
    FDataSet.Next;
end;

function TDataSetProcessor.IsEof: Boolean;
begin
  Result := (FDataSet = nil) or FDataSet.Eof;
end;

3. 修改DLL代码接收接口

library usr_d;

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

{$R *.res}

Procedure ProcessDataSet(Processor: IDataSetProcessor); stdcall;
begin
  if Assigned(Processor) then
  begin
    ShowMessage(Processor.GetFieldValue('MyFieldName1'));
    try
      Processor.First;
      while not Processor.IsEof do
      begin
        // 处理当前记录
        Processor.Next;
      end;
    except on E: Exception do
      ShowMessage('错误信息:' + E.Message);
    end;
  end
  else
    ShowMessage('Processor未赋值!');
end;

exports
  ProcessDataSet;

begin
end.

4. 修改主程序调用代码

Procedure TMainForm.CallMyDll;
type
  TProcessDataSet = procedure(Processor: IDataSetProcessor); stdcall;
Var
  MyHandle           : HMODULE;
  ProcessDataSet     : TProcessDataSet;
  DataProcessor      : IDataSetProcessor;
Begin
  DataProcessor := TDataSetProcessor.Create(MyQuery as TDataSet);
  MyHandle := LoadLibrary('c:\MyFolder\usr_d.dll');
  if MyHandle <> 0 then
  begin
    @ProcessDataSet := GetProcAddress(MyHandle, 'ProcessDataSet');
    if @ProcessDataSet <> nil then
    begin
      ProcessDataSet(DataProcessor);
    end;
    FreeLibrary(MyHandle);
  end;
End;

内容的提问来源于stack exchange,提问作者starhu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:35:04