C#调用含类指针参数的Delphi DLL函数时遇访问违规求助
C#调用含类指针参数的Delphi DLL出现访问违规问题
我在使用C#调用带有类指针参数的Delphi DLL函数时触发访问违规错误,请求协助排查。Delphi函数内部尝试访问传入的类成员时,发现成员为“不可访问值”,进而触发异常。
Delphi端代码定义
函数与类型声明
function CallQPLExt(ADeviceCode: PChar; AProva : string; var AData: TQPLData): boolean; stdcall;
相关类型定义:
TQPLData = Pointer; PProva = ^TProva; TProva = class; TProva = class private public Pippo : PChar; end;
出错的函数内部逻辑
函数执行以下代码时触发访问违规:
function CallQPLExt(ADeviceCode: PChar; AProva : string; var AData: TQPLData): boolean; var HostCode : String; begin HostCode:= PProva(AData)^.Pippo; // 访问Pippo成员时出错 end;
调试可见Pippo为“不可访问值”。
C#端调用代码
DLL导入与结构体定义
[DllImport(@"D:\AMS\3.03\EXE\LAB_QPLMGR.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] public static extern bool CallQPLExt(string deviceCode, string prova, ref Prova qplData); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct Prova { public string Pippo; }
调用方式
Prova resultat = new(); resultat.Pippo = "Pippo"; bool boolOk = CallQPLExt("ABI", "prova", ref resultat);
错误核心原因
- 内存模型不匹配:Delphi的
TProva是类,属于引用类型,变量本质是指向堆上实例的指针;而C#定义的Prova是值类型结构体,直接存储在栈上。用ref Prova传递时,传递的是结构体的地址,但Delphi端期望的是指向Delphi类实例的指针,两者内存结构完全不兼容。 - 字符串类型映射错误:Delphi的
PChar在Unicode环境下是PWideChar,但C#的string是托管字符串,直接映射会导致内存地址不匹配;同时Delphi类的内存布局包含虚方法表指针,进一步加剧了内存偏移错误。
修正方案
方案1:修改Delphi端为值类型记录(推荐)
如果可以修改DLL代码,将TProva从类改为值类型记录,消除引用类型的内存差异:
// Delphi端修改为记录类型 TProva = record Pippo : PWideChar; // 明确使用宽字符指针,匹配C# Unicode设置 end; PProva = ^TProva; TQPLData = PProva; // 直接定义为TProva的指针
C#端调整结构体定义,用IntPtr托管非托管字符串指针,避免内存泄漏:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct Prova { public IntPtr Pippo; } // 调用代码 Prova resultat = new(); // 将托管字符串转为非托管宽字符指针 IntPtr pippoPtr = Marshal.StringToHGlobalUni("Pippo"); resultat.Pippo = pippoPtr; bool boolOk = CallQPLExt("ABI", "prova", ref resultat); // 调用后释放非托管内存 Marshal.FreeHGlobal(pippoPtr);
方案2:保持Delphi类,C#端用IntPtr传递指针
如果无法修改DLL,需要DLL提供创建/销毁TProva实例的接口,C#通过IntPtr传递指针:
Delphi新增接口
function CreateProva: PProva; stdcall; begin Result := PProva(TProva.Create); // 创建Delphi类实例 end; procedure DestroyProva(AProva: PProva); stdcall; begin TProva(AProva^).Free; // 销毁实例 end;
C#端导入并调用
[DllImport(@"D:\AMS\3.03\EXE\LAB_QPLMGR.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] public static extern bool CallQPLExt(string deviceCode, string prova, ref IntPtr qplData); [DllImport(@"D:\AMS\3.03\EXE\LAB_QPLMGR.dll", CallingConvention = CallingConvention.StdCall)] public static extern IntPtr CreateProva(); [DllImport(@"D:\AMS\3.03\EXE\LAB_QPLMGR.dll", CallingConvention = CallingConvention.StdCall)] public static extern void DestroyProva(IntPtr prova); // 调用代码 IntPtr provaPtr = CreateProva(); // 写入Pippo值:Delphi类首4字节是虚表指针,Pippo偏移为4字节(32位环境) IntPtr pippoPtr = Marshal.StringToHGlobalUni("Pippo"); Marshal.WriteIntPtr(provaPtr, 4, pippoPtr); bool boolOk = CallQPLExt("ABI", "prova", ref provaPtr); // 清理资源 DestroyProva(provaPtr); Marshal.FreeHGlobal(pippoPtr);
注意:64位环境下虚表指针为8字节,需调整偏移量为8,这种方式依赖Delphi内存布局,风险较高。
内容的提问来源于stack exchange,提问作者Luca Colombano
相关产品推荐
相关产品推荐

