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

C#调用Delphi DLL事件出现参数异常与崩溃,求原因分析

问题:如何在C#中调用Delphi DLL的事件?

问题背景

你提供的Delphi 7 DLL和C#调用代码在运行时出现了参数值错乱,随后抛出System.AccessViolationException异常,提示内存访问错误。我们来一步步分析问题原因并解决它。


原代码展示

Delphi7 test.dll 代码

unit DLLFunction;
interface
uses
  Sysutils, classes,  Dialogs;

type
  TEvent = procedure(index, status, percent: integer) of object; stdcall;
  IMyDll = interface
  ['{42C845F8-F45C-4BC7-8CB5-E76658262C4A}']
    procedure SetEvent(const value: TEvent); stdcall;
  end;

  TMyDllClass = class(TInterfacedObject, IMyDll)
  public
    procedure SetEvent(const value: TEvent); stdcall;
  end;

  procedure CreateDelphiClass(out intf: IMyDll); stdcall;

implementation
procedure TMyDllClass.SetEvent(const value: TEvent); stdcall;
begin
  if Assigned (value) then
  begin
    ShowMessage('Event call');
    value(1,2,3);
  end;
end;
exports
  createDelphiClass;
end.

C# 定义代码

// event
public delegate void TEvent(int index, int status, int percent);

[ComImport, Guid("42C845F8-F45C-4BC7-8CB5-E76658262C4A"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMyDll
{
    // event
    [MethodImplAttribute(MethodImplOptions.PreserveSig)]
    void SetEvent([MarshalAs(UnmanagedType.FunctionPtr)]TEvent eventCallback);

}

class TestInterface
{
    const string dllPath = "testDelphiDll.DLL";

    [DllImport(dllPath, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    public static extern void CreateDelphiClass(out IMyDll dll);

    public IMyDll getDelphiInterface()
    {
        IMyDll mydll = null;
        CreateDelphiClass(out mydll);

        return mydll;
    }
}

C# 调用示例

TestInterface tInterface = new TestInterface();
var delphi = tInterface.getDelphiInterface();

delphi.SetEvent(delegate(int index, int status, int percent)
{
    MessageBox.Show("index: " + index.ToString() + "\nstatus: " + status.ToString() + " \npercent: " + percent.ToString());
});

运行结果

index: -19238192731
status: 1
percent: 2

随后应用程序崩溃,抛出异常:
发生未处理的System.AccessViolationException异常,位于DLLCallTest.exe中。
提示:您尝试读取或写入受保护的内存。大多数情况下,这表示其他内存已损坏。


问题根源

问题出在Delphi定义的TEvent类型上:

TEvent = procedure(index, status, percent: integer) of object; stdcall;

这里的of object是Delphi特有的语法,它定义的是对象方法指针——这种指针实际上包含两个部分:一个指向对象实例的Self指针,一个指向方法的代码指针。而你在C#中定义的TEvent委托是普通的函数指针,只包含代码指针,没有Self部分。

当Delphi尝试调用value(1,2,3)时,它会把Self指针压入栈中作为第一个隐藏参数,再传入你的三个显式参数,但C#的委托并没有准备这个Self参数,导致栈结构完全混乱:原本的index参数被错误地读取成了栈里的垃圾值,后续参数偏移错误,最终触发内存访问异常。


解决方案

最简单的修复方式是修改Delphi的事件定义,去掉of object,把它改成普通的stdcall函数指针,让它和C#的委托类型匹配:

修改后的Delphi代码

unit DLLFunction;
interface
uses
  Sysutils, classes,  Dialogs;

type
  // 去掉 of object,改成普通函数指针
  TEvent = procedure(index, status, percent: integer); stdcall;
  IMyDll = interface
  ['{42C845F8-F45C-4BC7-8CB5-E76658262C4A}']
    procedure SetEvent(const value: TEvent); stdcall;
  end;

  TMyDllClass = class(TInterfacedObject, IMyDll)
  public
    procedure SetEvent(const value: TEvent); stdcall;
  end;

  procedure CreateDelphiClass(out intf: IMyDll); stdcall;

implementation
procedure TMyDllClass.SetEvent(const value: TEvent); stdcall;
begin
  if Assigned (value) then
  begin
    ShowMessage('Event call');
    value(1,2,3);
  end;
end;
exports
  createDelphiClass;
end.

C#代码无需修改(保持原定义即可)

调用示例也不变,此时运行就能得到正确的结果:

index: 1
status: 2
percent: 3
不会再抛出内存访问异常。


额外说明

如果你因为某些原因必须保留Delphi中的of object方法指针(比如需要在Delphi内部绑定对象方法),那需要在Delphi层做一个适配层:

  • 定义一个普通函数指针类型,用于接收C#的委托
  • 在Delphi类中保存这个普通函数指针,同时定义一个对象方法作为中间转发器
  • 当需要触发事件时,先调用中间转发器,再由转发器调用C#的委托

不过这种方式会增加复杂度,对于你的场景来说,直接去掉of object是最简洁可靠的解决方案。

内容的提问来源于stack exchange,提问作者InKwon Devik Kim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:17:22