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

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

解决方案及修正代码

错误分析

  1. 结构体字段类型错误:原nfcTagCallback_t用IntPtr存储函数指针,Marshal无法正确计算结构体大小,需直接使用委托类型。
  2. 调用约定不匹配:Linux下C函数默认使用Cdecl调用约定,原代码用StdCall会导致栈异常。
  3. 委托GC回收风险:临时委托实例会被GC回收,导致回调时访问无效内存。
  4. 字符类型不匹配:C语言char是1字节ASCII,C#char是2字节Unicode,uid字段类型错误。
  5. 协议类型映射错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:35:35