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

Delphi调用实现接口的C++ DLL触发Access Violation问题排查

Delphi调用C++ DLL时MonitorProc触发Access Violation的问题分析与解决

我之前也踩过跨语言调用虚函数的坑,结合你的代码来看,问题核心在于Delphi和C++的虚函数表(VMT)结构不兼容,再加上你调用了一个根本没有实现的纯虚函数,直接触发了内存访问错误。下面一步步拆解问题和解决方法:

先回顾你的场景代码

原C++头文件

class MyINTERFACE { 
public: 
    virtual void MonitorProc(int value1, int value2) = 0; 
}; 
extern MY_API MyHANDLE* Init(); 
extern MY_API UINT InitMonitorProc(MyHANDLE * handle, MyINTERFACE * pMyProc);

你的Delphi转换单元

unit myCdllUnit; 
interface 
const 
    TMyHANDLE = DWORD; 
MyINTERFACE = class 
public 
    procedure MonitorProc(value1: longint; value2: longint); virtual; cdecl; abstract; 
end; 
function Init(): TMyHANDLE; cdecl; external 'myCdll.dll'; 
function InitMonitorProc(handle: TMyHANDLE; pMyProc: MyINTERFACE):DWORD; cdecl; external 'myCdll.dll'; 
implementation 
end.

主窗体中的重写代码

TMyMonitor = class(MyINTERFACE) 
public 
    procedure MonitorProc(value1: longint; value2: longint); override; 
end; 

procedure TMyMonitor.MonitorProc(value1: longint; value2: longint); 
begin 
    inherited; // <--------------------------- 这里就是触发AV的直接原因 
end;

初始化调用

MyMonitor := TMyMonitor.Create; 
InitMonitorProc(Handle, MyMonitor);

问题到底出在哪?

  1. 调用纯虚函数的致命错误:C++里的virtual void MonitorProc(...) = 0是纯虚函数,完全没有实现代码。你在Delphi里调用inherited;,本质就是去执行这个不存在的函数,直接导致内存访问违规。
  2. VMT结构不兼容:就算你去掉inherited,后续DLL调用MonitorProc时还是会出问题。因为Delphi的类虚函数表(VMT)和C的完全不一样——Delphi的VMT开头有类类型指针、实例大小等额外字段,而C的VMT直接是虚函数指针列表。当C++ DLL按照自己的规则去寻址虚函数时,会拿到错误的内存地址,最终还是触发AV。

正确的解决方法:用接口+手工模拟VMT适配

不能直接用Delphi的类去继承C的抽象类,必须做一层适配,让C能识别虚函数表结构。这里推荐用Delphi的接口来实现业务逻辑,再用一个包装类模拟C++的VMT结构:

1. 改写Delphi单元

unit myCdllUnit; 
interface 
type
    TMyHANDLE = DWORD;
    // 定义Delphi接口,对应C++的抽象类方法
    IMyINTERFACE = interface
        ['{5B2F87A1-7E7D-4F0C-8F3A-1D7E4F9B2C3D}'] // 按Ctrl+Shift+G生成唯一GUID
        procedure MonitorProc(value1: Integer; value2: Integer); cdecl;
    end;

    // 包装类:模拟C++的类结构和虚函数表
    TMyInterfaceWrapper = class
    public
        VMT: Pointer; // 对应C++类的第一个成员:虚函数表指针
        FIntf: IMyINTERFACE; // 持有Delphi接口实例
        constructor Create(const AIntf: IMyINTERFACE);
        procedure MonitorProc(value1: Integer; value2: Integer); cdecl;
    end;

function Init(): TMyHANDLE; cdecl; external 'myCdll.dll'; 
// 注意这里参数改成Pointer,因为我们传的是模拟的C++类实例
function InitMonitorProc(handle: TMyHANDLE; pMyProc: Pointer): DWORD; cdecl; external 'myCdll.dll'; 

implementation

{ TMyInterfaceWrapper }

constructor TMyInterfaceWrapper.Create(const AIntf: IMyINTERFACE);
type
    // 模拟C++的虚函数表:按声明顺序排列函数指针
    TVMT = array[0..0] of Pointer;
var
    LocalVMT: TVMT;
begin
    inherited Create;
    FIntf := AIntf;
    // 把MonitorProc的地址放到VMT的第一个位置(对应C++的第一个虚函数)
    LocalVMT[0] := @TMyInterfaceWrapper.MonitorProc;
    // 把VMT的地址赋值给类的VMT字段
    Self.VMT := @LocalVMT;
end;

procedure TMyInterfaceWrapper.MonitorProc(value1, value2: Integer);
begin
    // 转发到Delphi接口的实现
    FIntf.MonitorProc(value1, value2);
end;

end.

2. 修改主窗体的实现

TMyMonitor = class(TInterfacedObject, IMyINTERFACE) 
public 
    procedure MonitorProc(value1: Integer; value2: Integer); cdecl; 
end; 

procedure TMyMonitor.MonitorProc(value1: Integer; value2: Integer); 
begin 
    // 这里写你的业务逻辑,不需要inherited
    ShowMessageFmt('收到监控值:Value1=%d, Value2=%d', [value1, value2]);
end;

3. 调整FormCreate的调用

var
    MyMonitor: IMyINTERFACE;
    Wrapper: TMyInterfaceWrapper;
begin
    MyMonitor := TMyMonitor.Create;
    Wrapper := TMyInterfaceWrapper.Create(MyMonitor);
    InitMonitorProc(Handle, Wrapper);
    // 注意:如果DLL会长期持有Wrapper,要把它保存到窗体的字段中,避免被释放
end;

额外注意事项

  • 虚函数的调用约定必须严格一致:C++里如果是默认的__cdecl,Delphi里也要用cdecl,不能错用stdcall或者其他约定。
  • 模拟VMT时,函数指针的顺序必须和C类里的虚函数声明顺序完全一致,C的VMT是按声明顺序排列的。
  • 不要让Wrapper被提前释放:如果DLL在后台会持续调用MonitorProc,必须确保Wrapper实例一直存在,比如把它赋值给窗体的全局字段。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:40:29