4

我有一个具有以下签名的非托管 C++ 函数:

int function(char* param, int ret)

我试图从 C# 调用它:

unsafe delegate int MyFunc(char* param, int ret);

...

int Module = LoadLibrary("fullpathToUnamanagedDll");
IntPtr pProc = GetProcAddress(Module, "functionName");
MyFunc func = (MyFunc)System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer(pProc, typeof(MyFunc));

unsafe
{
    char* param = null;
    int ret = 0;
    int result = func(param, ret);
}

据我从旧的 C++ 项目规范中可以看出,param的 null 和ret的0都是函数的有效输入。当我尝试调用它时,它似乎可以工作,但是在退出时出现以下错误:

检测到 PInvokeStackImbalance

调用 PInvoke 函数 '...::Invoke' 使堆栈不平衡。这可能是因为托管 PInvoke 签名与非托管目标签名不匹配。检查 PInvoke 签名的调用约定和参数是否与目标非托管签名匹配。

我已经尝试了几乎所有我能想到的东西(不安全是最后的手段),但是我找不到任何方法来运行该函数而不会得到不平衡的堆栈。还有什么我可以尝试的吗?

4

3 回答 3

8

我知道这个问题现在已经有一年了,但是比动态构建类型更简单的方法是使用UnmanagedFunctionPointer委托上的属性声明调用约定,如下所示:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
unsafe delegate int MyFunc(char* param, int ret);

来自MSDN

控制作为非托管函数指针传入或传出非托管代码的委托签名的封送处理行为。

于 2011-09-15T20:07:30.810 回答
2

IIRC,您需要使用调用约定来装饰委托签名。不幸的是,这只能通过 IL 或使用 Reflection.Emit 生成存根来完成。

你可以试试这个:

protected static Type MakeDelegateType(Type returntype, List<Type> paramtypes)
{
  ModuleBuilder dynamicMod = ... ; // supply this

  TypeBuilder tb = dynamicMod.DefineType("delegate-maker" + Guid.NewGuid(), 
      TypeAttributes.Public | TypeAttributes.Sealed, typeof(MulticastDelegate));

  tb.DefineConstructor(MethodAttributes.RTSpecialName | 
       MethodAttributes.SpecialName | MethodAttributes.Public |
       MethodAttributes.HideBySig, CallingConventions.Standard,
       new Type[] { typeof(object), typeof(IntPtr) }). 
       SetImplementationFlags(MethodImplAttributes.Runtime);

  var inv = tb.DefineMethod("Invoke", MethodAttributes.Public | 
       MethodAttributes.Virtual | MethodAttributes.NewSlot | 
       MethodAttributes.HideBySig, 
       CallingConventions.Standard ,returntype,null, 
       new Type[] 
       { 
          // this is the important bit
          typeof(System.Runtime.CompilerServices.CallConvCdecl)
       }, 
       paramtypes.ToArray(), null, null);

  inv.SetImplementationFlags(MethodImplAttributes.Runtime);

  var t = tb.CreateType();
  return t;
}
于 2010-07-22T08:27:26.407 回答
-1

有两件事需要注意:C# 位和您的 DLL 之间的调用约定,以及 char * 数据如何跨此接口编组。如果您弄错了其中任何一个,那么您将收到堆栈损坏投诉。在定义接口时,如果您可以将数据块的大小限制为固定的大小,那么会容易得多,即。设置最大字符串长度。

这是静态版本,其中 DLL 名称是固定的,您的字符串作为 byte[] 处理,大小限制为 2Kbyte,您可以从中计算出动态版本:

    private const string MYDLL = @"my.dll";

    [DllImport(MYDLL, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
    public static extern int DataBlockDownload([MarshalAs(UnmanagedType.U4)] int A, [MarshalAs(UnmanagedType.LPArray, SizeConst = 2048)] byte[] B, [MarshalAs(UnmanagedType.U4)] int C);

    // NOTE: The data block byte array is fixed at 2Kbyte long!!
public delegate int DDataBlockCallback([MarshalAs(UnmanagedType.U4)] int A, [MarshalAs(UnmanagedType.LPArray, SizeConst = 2048)] byte[] B, [MarshalAs(UnmanagedType.U4)] int C);

如果您想保留 char 类型,您可能还想指定您正在使用的字符集,如上所述。

你不会说你在用你的 char * 数据做什么,如果它作为参数进入你的 C++ 代码,或者如果 C++ 代码将它传递回托管世界。阅读 C# 关键字 ref 和 out 作为避免 char * 类型和 unsafe 修饰符的方法。

通过一些谷歌搜索,您应该能够弄清楚。

于 2010-07-22T09:18:34.773 回答