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

C# DllImport调用kt10_connect时事件回调封送问题排查

修复方案

你当前的代码存在5个直接导致内存访问冲突的封送错误,逐一修正即可正常运行:

  • 委托参数错误添加了ref修饰:C函数签名里的回调参数是函数指针值类型,传ref相当于传入二级指针,直接造成栈内存错位,调用时直接触发访问冲突。
  • 字符串参数封送错误:头文件中的char *是字符串指针,你写成ref char仅对应单个字符的引用,封送时会误读内存地址造成越界。
  • void* user_data封送逻辑完全错误:该参数是用户自定义上下文的泛型指针,不是函数指针,不需要定义对应委托,直接封送为IntPtr即可,无自定义上下文时直接传IntPtr.Zero。
  • 委托对象未被长期持有:你将委托声明为Connect方法的局部变量,方法执行完后GC会回收这些委托内存,等原生库后续触发回调时,函数指针已经指向被释放的内存,必然触发访问冲突。需要将委托实例存为类的静态/实例成员,保证在整个连接生命周期内不被GC回收。
  • 未显式指定DllImport的调用约定:C原生库默认使用Cdecl调用约定,不手动指定的话默认用StdCall,会造成栈清理不匹配的问题。

第一步:定义匹配的回调委托

所有委托必须标记UnmanagedFunctionPointer特性指定Cdecl调用约定,字符串参数标记为ANSI字符串指针:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int Kt10EventConnected(int connId, [MarshalAs(UnmanagedType.LPStr)] string msg, IntPtr userData);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int Kt10EventConnError(int connId, [MarshalAs(UnmanagedType.LPStr)] string errMsg, IntPtr userData);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int Kt10EventDisconnected(int connId, [MarshalAs(UnmanagedType.LPStr)] string reason, IntPtr userData);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int Kt10EventLiveData(int connId, [MarshalAs(UnmanagedType.LPStr)] string dataTag, int param1, int param2, float val1, float val2, IntPtr userData);

第二步:修正DllImport声明

移除委托参数的ref修饰,指定调用约定,字符串参数正确封送:

[DllImport("libkt10int.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int kt10_connect(
    [MarshalAs(UnmanagedType.LPStr)] string connString,
    ref uint connId,
    int timeout,
    Kt10EventConnected onConnected,
    Kt10EventConnError onConnError,
    Kt10EventDisconnected onDisconnected,
    Kt10EventLiveData onLiveData,
    IntPtr userData
);

第三步:正确调用方法

将委托实例存为类成员长期持有,避免GC回收:

// 类级别的成员变量,持有委托引用,保证连接生命周期内不被GC回收
private static Kt10EventConnected _connectedHandler;
private static Kt10EventConnError _connErrorHandler;
private static Kt10EventDisconnected _disconnectedHandler;
private static Kt10EventLiveData _liveDataHandler;

public static void Connect(string deviceConnStr)
{
    uint connId = 0;
    // 初始化回调委托
    _connectedHandler = (id, msg, ud) =>
    {
        Debug.WriteLine($"连接成功,连接ID:{id},消息:{msg}");
        return 0;
    };
    _connErrorHandler = (id, err, ud) =>
    {
        Debug.WriteLine($"连接错误,连接ID:{id},错误信息:{err}");
        return 0;
    };
    _disconnectedHandler = (id, reason, ud) =>
    {
        Debug.WriteLine($"连接断开,连接ID:{id},断开原因:{reason}");
        return 0;
    };
    _liveDataHandler = (id, tag, p1, p2, v1, v2, ud) =>
    {
        Debug.WriteLine($"收到实时数据,连接ID:{id},标签:{tag},参数:{p1}/{p2},数值:{v1}/{v2}");
        return 0;
    };

    // 调用原生方法,无自定义上下文时userData传IntPtr.Zero
    int ret = kt10_connect(
        deviceConnStr, 
        ref connId, 
        1000,
        _connectedHandler,
        _connErrorHandler,
        _disconnectedHandler,
        _liveDataHandler,
        IntPtr.Zero
    );

    Debug.WriteLine($"kt10_connect调用返回值:{ret},生成的连接ID:{connId}");
}

额外排障提示

如果修改后仍然出现字符串相关的访问错误,说明库使用的字符串编码不是默认ANSI,可以将所有字符串参数改为IntPtr类型,用Marshal.StringToHGlobalAnsi或者Marshal.StringToHGlobalUTF8手动分配内存传入,方法调用完成后再用Marshal.FreeHGlobal释放内存即可。
如果头文件中kt10_conn_string_t不是char*的类型别名,而是自定义结构体,需要先按照结构体成员布局定义对应的C#结构体,再作为参数传入。

内容的提问来源于stack exchange,提问作者Joel Page

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 20:57:16