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

如何正确实现含委托字段的SDL_VirtualJoystickDesc C#结构体?

正确实现SDL3 SDL_VirtualJoystickDesc结构体的C#包装方案

CS8500错误的根源是:托管委托类型不能直接作为非托管结构体的成员传递给C函数——C#的委托是托管对象,无法直接获取其地址供非托管代码使用。你提到的nint结合Marshal.GetFunctionPointerForDelegate()是标准且最优的解决方案,下面是具体的实现步骤和代码示例:

1. 定义匹配SDL函数签名的委托类型

首先要为SDL_VirtualJoystickDesc中的每个函数指针定义对应的C#委托,必须添加[UnmanagedFunctionPointer]特性指定调用约定(SDL默认使用CallingConvention.Cdecl):

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int VirtualJoystickGetAxisDelegate(IntPtr userdata, int axis);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int VirtualJoystickGetButtonDelegate(IntPtr userdata, int button);

// 根据SDL_VirtualJoystickDesc的实际成员,补充其他需要的委托,比如GetHat、Rumble、Close等

2. 定义非托管兼容的结构体

用nint(对应C的void*/函数指针类型)代替直接使用委托作为结构体成员,同时指定结构体布局为顺序布局,确保和SDL的C结构体内存布局一致:

[StructLayout(LayoutKind.Sequential)]
public struct SDL_VirtualJoystickDesc
{
    public uint version; // 对应SDL_VIRTUAL_JOYSTICK_DESC_VERSION常量
    public nint userdata; // 自定义用户数据,会传递给各个回调函数
    public nint get_axis; // 对应SDL_VirtualJoystickGetAxis函数指针
    public nint get_button; // 对应SDL_VirtualJoystickGetButton函数指针
    // 补充结构体的其他成员,比如get_hat、rumble、close等,均用nint存储函数指针
}

3. 初始化结构体并绑定委托

在使用时,需要将委托转换为非托管函数指针,赋值给结构体的nint成员,关键是要保留委托的引用,防止被.NET垃圾回收器回收(一旦委托被回收,非托管代码调用函数指针会导致崩溃):

// 保留委托引用,放在类成员或全局变量中,不要用局部变量
private VirtualJoystickGetAxisDelegate _getAxisDelegate;
private VirtualJoystickGetButtonDelegate _getButtonDelegate;

public int AttachVirtualJoystick()
{
    // 初始化委托实例,实现具体的回调逻辑
    _getAxisDelegate = (userdata, axis) =>
    {
        // 此处编写轴数据获取逻辑,返回对应轴的数值(范围通常为-32768到32767)
        return 0; // 示例返回值
    };

    _getButtonDelegate = (userdata, button) =>
    {
        // 此处编写按钮状态获取逻辑,返回1表示按下,0表示松开
        return 0; // 示例返回值
    };

    // 初始化SDL_VirtualJoystickDesc结构体
    var desc = new SDL_VirtualJoystickDesc
    {
        version = SDL3.SDL_VIRTUAL_JOYSTICK_DESC_VERSION, // 替换为SDL3定义的版本常量
        userdata = IntPtr.Zero, // 可传递自定义用户数据指针
        get_axis = Marshal.GetFunctionPointerForDelegate(_getAxisDelegate),
        get_button = Marshal.GetFunctionPointerForDelegate(_getButtonDelegate)
        // 赋值其他函数指针成员
    };

    // 调用SDL_AttachVirtualJoystick,注意正确封送结构体
    return SDL3.SDL_AttachVirtualJoystick(ref desc);
}

4. 关键注意事项

  • 委托生命周期:必须保持委托的引用直到虚拟摇杆被SDL_DetachVirtualJoystick销毁,否则GC回收委托后,非托管函数指针会变成无效地址,导致程序崩溃。
  • 调用约定匹配:必须确保委托的CallingConvention和SDL的C函数调用约定一致,SDL默认是Cdecl,不匹配会导致栈损坏。
  • 结构体布局:[StructLayout(LayoutKind.Sequential)]保证结构体成员的内存顺序和C版本完全一致,避免内存偏移错误。
  • 版本字段:一定要正确设置version成员为SDL3定义的SDL_VIRTUAL_JOYSTICK_DESC_VERSION,否则SDL可能无法识别结构体格式。

这种方案是C#与非托管代码交互时处理函数指针结构体的标准做法,不存在更优的替代方案——因为托管类型和非托管类型的内存模型差异,必须通过Marshal.GetFunctionPointerForDelegate()完成转换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 15:40:08