You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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代码不需要任何修改。

  1. 首先定义和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();
  1. 修改原有的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();
  1. 调用时需要额外注意:传入的委托需要在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:24:22