从 C# 应用程序调用 Delphi 函数时的 PInvokeStackImbalance
本文关键字:PInvokeStackImbalance 函数 Delphi 应用程序 调用 | 更新日期: 2023-09-27 18:36:20
我被迫使用非托管的delphi dll。我无法访问源代码。只有模糊的文档:
type
TServiceData = packed record
DBAlias: PChar;
LicKey: PChar;
Pass: PChar;
end;
PServiceData = ^TServiceData;
function CreateRole(SrvData: PServiceData; var UserName: PChar): byte; stdcall;
UserName
应该是一个out
参数。
我的 C# 代码:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SERVICE_DATA
{
public string DBALias;
public string LicKey;
public string Pass;
}
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
public static extern byte CreateRole(SERVICE_DATA data, out string str);
我不知道什么会导致堆栈不平衡(除了调用似乎是正确的约定)。我不知道我的结构中的字符串是否正确编组,但根据其他线程,这不会导致 PStackImbalanceException。任何帮助将不胜感激:)
编辑。我已经实施了大卫的建议,现在我得到了访问冲突异常:
"未处理的异常:系统。访问违规异常:试图读取或写入受保护的内存。这通常表示其他内存已损坏"
我的结构和方法声明只是从答案中复制粘贴的,我称呼它的方式没有什么花哨的:
string str;
var data = new SERVICE_DATA();
data.DBALias = "test";
data.LicKey = "test";
data.Pass = "test";
var result = CreateRole(ref data, out str);
翻译有几个问题:
- 德尔菲代码接收指向记录的指针。C# 代码按值传递它。这就是堆栈不平衡警告的原因。
- 用户名参数可能在 Delphi 端处理不正确。它需要是指向动态分配内存的指针,通过调用 COM 堆
CoTaskMemAlloc
分配。我猜你没有这样做,所以当编组器试图通过调用CoTaskMemFree
来释放指针时,你会遇到问题。
我可能会对字符串使用 COM 字符串类型。我也会避免打包记录,因为一般来说,这是不好的做法。
我会这样写:
德 尔 福
type
TServiceData = record
DBAlias: WideString;
LicKey: WideString;
Pass: WideString;
end;
function CreateRole(const SrvData: TServiceData; out UserName: WideString): Byte;
stdcall;
C#
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_DATA
{
[MarshalAs(UnmanagedType.BStr)]
public string DBALias;
[MarshalAs(UnmanagedType.BStr)]
public string LicKey;
[MarshalAs(UnmanagedType.BStr)]
public string Pass;
}
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
public static extern byte CreateRole(
[In] ref SERVICE_DATA data,
[MarshalAs(UnmanagedType.BStr)] out string str
);
这是一个完整的测试项目,表明它按预期工作:
德 尔 福
library Project1;
type
TServiceData = record
DBAlias: WideString;
LicKey: WideString;
Pass: WideString;
end;
function CreateRole(const SrvData: TServiceData; out UserName: WideString): Byte;
stdcall;
begin
UserName := SrvData.DBAlias + SrvData.LicKey + SrvData.Pass;
Result := Length(UserName);
end;
exports
CreateRole;
begin
end.
C#
using System;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
const string dllname = @"...";
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_DATA
{
[MarshalAs(UnmanagedType.BStr)]
public string DBALias;
[MarshalAs(UnmanagedType.BStr)]
public string LicKey;
[MarshalAs(UnmanagedType.BStr)]
public string Pass;
}
[DllImport(dllname, CallingConvention = CallingConvention.StdCall)]
public static extern byte CreateRole(
[In] ref SERVICE_DATA data,
[MarshalAs(UnmanagedType.BStr)] out string str
);
static void Main(string[] args)
{
SERVICE_DATA data;
data.DBALias = "DBALias";
data.LicKey = "LicKey";
data.Pass = "Pass";
string str;
var result = CreateRole(ref data, out str);
Console.WriteLine(result);
Console.WriteLine(str);
}
}
}
输出
17DBALiasLicKeyPass