Linux下C#调用C NFC回调结构体封送失败求助
Linux下C#调用.so中nfcManager_registerTagCallback失败的解决方案
问题背景
在Linux环境下通过C#控制台程序调用libnfc_nci_linux.so中的C语言函数nfcManager_registerTagCallback时,抛出封送异常,无法计算结构体大小或偏移量。
C语言相关定义
函数声明
/** * \brief 注册标签回调函数。 * \param callback: 标签回调函数结构体指针。 * \return 无 */ extern void nfcManager_registerTagCallback(nfcTagCallback_t *callback);
回调结构体定义
/** * \brief NFC标签回调函数结构体定义。 */ typedef struct { /** * \brief 检测到标签时的NFC标签回调函数。 * param pTagInfo 标签信息 */ void (*onTagArrival) (nfc_tag_info_t *pTagInfo); /** * \brief 标签移除时的NFC标签回调函数。 */ void (*onTagDeparture) (void); }nfcTagCallback_t;
标签信息结构体定义
/** * \brief NFC标签信息结构体定义。 */ typedef struct { /** * \brief 标签技术类型 */ unsigned int technology; /** * \brief 标签句柄 */ unsigned int handle; /** * \brief 标签UID */ char uid[32]; /** * \brief UID长度 */ unsigned int uid_length; /** * \brief 激活的协议 */ tNFC_PROTOCOL protocol; }nfc_tag_info_t;
原尝试的C#代码及报错
原C#代码
[DllImport("libnfc_nci_linux.so", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)] private static extern void nfcManager_registerTagCallback(IntPtr callback); [StructLayout(LayoutKind.Sequential)] public struct nfcTagCallback_t { [MarshalAs(UnmanagedType.FunctionPtr)] public IntPtr onTagArrival; [MarshalAs(UnmanagedType.FunctionPtr)] public IntPtr onTagDeparture; } public static void onArrival(IntPtr intPtr) { Console.WriteLine("Arrival"); } public static void onDeparture() { Console.WriteLine("Departure"); } [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate void CallbackOnArrival(IntPtr intPtr); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate void CallbackOnDeparture(); [StructLayout(LayoutKind.Sequential)] private struct nfc_tag_info_t { public uint technology; public uint handle; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public char[] uid; public uint uid_length; public byte protocol; }
调用代码
var nfcTagCallback = new nfcTagCallback_t(); nfcTagCallback.onTagDeparture = Marshal.GetFunctionPointerForDelegate(new CallbackOnDeparture(onDeparture)); nfcTagCallback.onTagArrival = Marshal.GetFunctionPointerForDelegate(new CallbackOnArrival(onArrival)); IntPtr testPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nfcTagCallback)); Marshal.StructureToPtr(nfcTagCallback, testPointer, true); nfcManager_registerTagCallback(testPointer); // <-- 抛出异常
报错信息
未处理的异常。System.ArgumentException: 类型‘PN7150_NFC.Program+nfcTagCallback_t’无法作为非托管结构进行封送;无法计算有意义的大小或偏移量。 at System.Runtime.InteropServices.Marshal.SizeOfHelper(Type t, Boolean throwIfNotMarshalable) at System.Runtime.InteropServices.Marshal.SizeOf[T](T structure) at PN7150_NFC.Program.Main(String[] args) in /home/pi/Desktop/PN7150-NFC/PN7150-NFC/Program.cs:line 95
解决方案及修正代码
错误分析
- 结构体字段类型错误:原
nfcTagCallback_t用IntPtr存储函数指针,Marshal无法正确计算结构体大小,需直接使用委托类型。 - 调用约定不匹配:Linux下C函数默认使用
Cdecl调用约定,原代码用StdCall会导致栈异常。 - 委托GC回收风险:临时委托实例会被GC回收,导致回调时访问无效内存。
- 字符类型不匹配:C语言
char是1字节ASCII,C#char是2字节Unicode,uid字段类型错误。 - 协议类型映射错误:
tNFC_PROTOCOL通常是无符号整数类型,原代码用byte可能导致内存布局错误。
修正后的完整代码
using System; using System.Runtime.InteropServices; namespace PN7150_NFC { class Program { // 修正DllImport:调用约定改为Cdecl,字符集改为Ansi,SetLastError在Linux下无效 [DllImport("libnfc_nci_linux.so", CharSet = CharSet.Ansi, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] private static extern void nfcManager_registerTagCallback(IntPtr callback); // 修正回调结构体:直接使用委托类型,标记FunctionPtr [StructLayout(LayoutKind.Sequential)] public struct nfcTagCallback_t { [MarshalAs(UnmanagedType.FunctionPtr)] public CallbackOnArrival onTagArrival; [MarshalAs(UnmanagedType.FunctionPtr)] public CallbackOnDeparture onTagDeparture; } // 修正委托调用约定为Cdecl [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void CallbackOnArrival(IntPtr pTagInfo); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void CallbackOnDeparture(); // 保存委托引用,防止GC回收 private static CallbackOnArrival _arrivalDelegate; private static CallbackOnDeparture _departureDelegate; // 标签信息结构体:uid改为byte[],protocol改为uint(匹配C的unsigned int) [StructLayout(LayoutKind.Sequential)] private struct nfc_tag_info_t { public uint technology; public uint handle; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] uid; public uint uid_length; public uint protocol; // 若tNFC_PROTOCOL是枚举,需定义对应枚举类型 } public static void onArrival(IntPtr pTagInfo) { Console.WriteLine("检测到标签"); // 将IntPtr转换为结构体 nfc_tag_info_t tagInfo = Marshal.PtrToStructure<nfc_tag_info_t>(pTagInfo); // 处理UID:截取有效长度 byte[] uid = new byte[tagInfo.uid_length]; Array.Copy(tagInfo.uid, uid, tagInfo.uid_length); Console.WriteLine($"UID: {BitConverter.ToString(uid).Replace("-", "")}"); } public static void onDeparture() { Console.WriteLine("标签移除"); } static void Main(string[] args) { RegisterNfcCallback(); Console.WriteLine("等待NFC标签..."); Console.ReadLine(); } private static void RegisterNfcCallback() { // 初始化委托并保存引用 _arrivalDelegate = new CallbackOnArrival(onArrival); _departureDelegate = new CallbackOnDeparture(onDeparture); var callbackStruct = new nfcTagCallback_t { onTagArrival = _arrivalDelegate, onTagDeparture = _departureDelegate }; // 分配非托管内存并拷贝结构体 IntPtr callbackPtr = Marshal.AllocHGlobal(Marshal.SizeOf(callbackStruct)); try { Marshal.StructureToPtr(callbackStruct, callbackPtr, false); nfcManager_registerTagCallback(callbackPtr); } catch (Exception ex) { Console.WriteLine($"注册回调失败: {ex.Message}"); Marshal.FreeHGlobal(callbackPtr); throw; } // 注意:如果.so库长期持有回调指针,不能释放callbackPtr,否则会导致访问错误 // 程序退出时再释放:AppDomain.CurrentDomain.ProcessExit += (s, e) => Marshal.FreeHGlobal(callbackPtr); } } }
关键注意事项
- 委托引用保存:必须将委托实例保存为静态字段,避免被GC回收,否则回调时会触发内存访问错误。
- 内存释放时机:如果.so库会长期持有回调结构体的指针,不能立即释放
callbackPtr,需在程序退出时再释放;若库仅保存函数指针(不依赖结构体),则可以在调用后释放。 - 协议类型确认:需根据
tNFC_PROTOCOL的实际定义调整nfc_tag_info_t中protocol字段的类型,若为枚举需定义对应的C#枚举并标记[Flags]或[EnumLayout(LayoutKind.Sequential)]。
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

