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
相关产品推荐
相关产品推荐

