C#调用C DLL带int参数回调报非blittable泛型类型无法封送错误
调用时抛出的封送错误如下:
Cannot marshal 'parameter #1': Non-blittable generic types cannot be marshaled.
问题根因
封送报错的核心原因是泛型委托不支持作为P/Invoke的非托管函数指针参数直接封送:
- 无参数非泛型
Action属于特殊情况,.NET Interop层对其做了兼容处理,可以直接封送匹配无返回值无参数的C函数指针 Action<Int32>是泛型类型,属于非blittable类型,封送层没有对应的泛型封送逻辑,直接传入就会抛出上述错误。
修复方案
不要使用泛型Action声明带参数的回调P/Invoke签名,按照C侧的函数指针签名自定义非泛型委托,显式标记封送行为即可,C侧DLL代码不需要任何修改。
- 首先定义和C侧函数指针签名完全匹配的自定义委托,添加
UnmanagedFunctionPointer特性指定和C侧一致的Cdecl调用约定:
using System.Runtime.InteropServices; // 匹配C侧 typedef void(*RECV_CALLBACKINT_PARA)(int); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void RecvCallbackInt(int result); // 无参数回调也建议显式定义,避免不同.NET版本下Action隐式封送的行为差异 // 匹配C侧 typedef void(*RECV_CALLBACK)(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void RecvCallbackNoParam();
- 修改原有的P/Invoke导入声明,把参数类型从
Action/Action<Int32>替换为自定义的委托类型,原签名中无意义的CharSet = CharSet.Ansi可以删除(这几个接口没有字符串参数,该配置不生效):
[DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl)] static extern void MethodWithNo_Int_Para(RecvCallbackNoParam ptr); [DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl)] static extern void MethodWith_INT_Para(RecvCallbackInt ptr); [DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl)] static extern void Calling_INT_PARA(int x, int y); [DllImport("Sample.dll", CallingConvention = CallingConvention.Cdecl)] static extern void Calling_without_Para();
- 调用时需要额外注意:传入的委托需要在C侧可能触发回调的全生命周期内保持引用,不要让GC回收委托对象,否则会触发内存访问违例。简单场景可以直接把委托存为静态字段持有引用,测试代码如下:
// 静态字段持有委托引用,避免被GC回收 private static RecvCallbackNoParam _noParamCb = TestNoParam; private static RecvCallbackInt _intCb = TestWithInt; static void Main(string[] args) { Console.WriteLine("After calling Declare"); MethodWithNo_Int_Para(_noParamCb); Calling_without_Para(); MethodWith_INT_Para(_intCb); Calling_INT_PARA(1, 3); Console.ReadLine(); } static void TestNoParam() { Console.WriteLine("Without int para : called by Calling_without_Para"); } static void TestWithInt(int x) { Console.WriteLine($"With Int Para {x} : called by Calling_INT_PARA"); }
补充说明
- 不要尝试用
Marshal.GetFunctionPointerForDelegate绕开,本质和自定义委托封送的逻辑一致,还需要手动管理函数指针生命周期,更易出错 - 委托上必须显式标记调用约定和C侧一致,否则默认使用StdCall会导致栈不平衡,出现随机崩溃、参数值错乱的问题
内容的提问来源于stack exchange,提问作者pankaj Sketchup
相关产品推荐
相关产品推荐

