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

时间:2015-01-05 14:42:08

标签: c# delphi pinvoke

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

EDIT。 我已经实施了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 个答案:

答案 0 :(得分:2)

翻译有一些问题:

  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