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
相关产品推荐
相关产品推荐

