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

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);

错误核心原因

  1. 内存模型不匹配:Delphi的TProva是类,属于引用类型,变量本质是指向堆上实例的指针;而C#定义的Prova是值类型结构体,直接存储在栈上。用ref Prova传递时,传递的是结构体的地址,但Delphi端期望的是指向Delphi类实例的指针,两者内存结构完全不兼容。
  2. 字符串类型映射错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 13:25:22