从 C# 应用程序调用 Delphi 函数时的 PInvokeStackImbalance

PInvokeStackImbalance when calling Delphi function from C# application

我被迫使用非托管 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。任何帮助将不胜感激:)

编辑。 我已经实施了 David 的建议,现在我遇到了访问冲突异常:

“未处理的异常:System.AccessViolationException:试图读取或写入受保护的内存。这通常表明其他内存已损坏”

我的结构和方法声明只是从答案中复制粘贴而来,我的调用方式没有什么特别的:

        string str;
        var data = new SERVICE_DATA();
        data.DBALias = "test";
        data.LicKey = "test";
        data.Pass = "test";
        var result = CreateRole(ref data, out str);

翻译有几处错误:

  1. Delphi 代码接收到记录的指针。 C# 代码按值传递它。这就是堆栈不平衡警告的原因。
  2. 用户名参数可能在 Delphi 端处理不正确。它需要是指向动态分配内存的指针,通过调用 CoTaskMemAlloc 在 COM 堆上分配。我猜你没有这样做,所以当编组器试图通过调用 CoTaskMemFree.
  3. 来释放指针时,你会遇到问题

我可能会使用 COM 字符串类型作为字符串。我也会避免打包记录,因为这通常是不好的做法。

我会这样写:

Delphi

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

这是一个完整的测试项目,表明它按预期工作:

Delphi

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

输出

17
DBALiasLicKeyPass