Question
How do I marshal a C# struct with delegate fields?
I'm writing a C# wrapper for SDL3. I'm currently trying to implement the following struct:
typedef struct SDL_VirtualJoystickDesc
{
Uint16 type; /**< `SDL_JoystickType` */
Uint16 padding; /**< unused */
Uint16 vendor_id; /**< the USB vendor ID of this joystick */
Uint16 product_id; /**< the USB product ID of this joystick */
Uint16 naxes; /**< the number of axes on this joystick */
Uint16 nbuttons; /**< the number of buttons on this joystick */
Uint16 nballs; /**< the number of balls on this joystick */
Uint16 nhats; /**< the number of hats on this joystick */
Uint16 ntouchpads; /**< the number of touchpads on this joystick, requires `touchpads` to point at valid descriptions */
Uint16 nsensors; /**< the number of sensors on this joystick, requires `sensors` to point at valid descriptions */
Uint16 padding2[2]; /**< unused */
Uint32 button_mask; /**< A mask of which buttons are valid for this controller
e.g. (1 << SDL_GAMEPAD_BUTTON_SOUTH) */
Uint32 axis_mask; /**< A mask of which axes are valid for this controller
e.g. (1 << SDL_GAMEPAD_AXIS_LEFTX) */
const char *name; /**< the name of the joystick */
const SDL_VirtualJoystickTouchpadDesc *touchpads; /**< A pointer to an array of touchpad descriptions, required if `ntouchpads` is > 0 */
const SDL_VirtualJoystickSensorDesc *sensors; /**< A pointer to an array of sensor descriptions, required if `nsensors` is > 0 */
void *userdata; /**< User data pointer passed to callbacks */
void (SDLCALL *Update)(void *userdata); /**< Called when the joystick state should be updated */
void (SDLCALL *SetPlayerIndex)(void *userdata, int player_index); /**< Called when the player index is set */
int (SDLCALL *Rumble)(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); /**< Implements SDL_RumbleJoystick() */
int (SDLCALL *RumbleTriggers)(void *userdata, Uint16 left_rumble, Uint16 right_rumble); /**< Implements SDL_RumbleJoystickTriggers() */
int (SDLCALL *SetLED)(void *userdata, Uint8 red, Uint8 green, Uint8 blue); /**< Implements SDL_SetJoystickLED() */
int (SDLCALL *SendEffect)(void *userdata, const void *data, int size); /**< Implements SDL_SendJoystickEffect() */
int (SDLCALL *SetSensorsEnabled)(void *userdata, SDL_bool enabled); /**< Implements SDL_SetGamepadSensorEnabled() */
} SDL_VirtualJoystickDesc;
This struct is used in the following C function:
extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc);
My (naive) C# implementation for SDL_VirtualJoystickDesc
is
[StructLayout(LayoutKind.Sequential)]
public unsafe struct SDL_VirtualJoystickDesc
{
/// <summary>
/// A value from <see cref="SDL_JoystickType"/>.
/// </summary>
public SDL_JoystickType Type;
private readonly ushort _padding1;
/// <summary>
/// The USB vendor ID of this joystick.
/// </summary>
public ushort VendorId;
/// <summary>
/// The USB product ID of this joystick.
/// </summary>
public ushort ProductId;
/// <summary>
/// The number of axes on this joystick.
/// </summary>
public ushort NAxes;
/// <summary>
/// The number of buttons on this joystick.
/// </summary>
public ushort NButtons;
/// <summary>
/// The number of balls on this joystick.
/// </summary>
public ushort NBalls;
/// <summary>
/// The number of hats on this joystick.
/// </summary>
public ushort NHats;
/// <summary>
/// The number of touchpads on this joystick, requires <see cref="Touchpads"/> to point at valid descriptions
/// </summary>
public ushort NTouchpads;
/// <summary>
/// The number of sensors on this joystick, requires <see cref="Sensors"/> to point at valid descriptions.
/// </summary>
public ushort NSensors;
private readonly ushort _padding2;
private readonly ushort _padding3;
/// <summary>
/// A mask of which buttons are valid for this controller, e.g. (1 << <see cref="SDL_GamepadButton.South"/>).
/// </summary>
public uint ButtonMask;
/// <summary>
/// A mask of which axes are valid for this controller, e.g. (1 << <see cref="SDL_GamepadAxis.LeftX"/>).
/// </summary>
public uint AxisMask;
/// <summary>
/// The name of the joystick.
/// </summary>
public readonly string Name => Marshal.PtrToStringUTF8((nint)_name)!;
private readonly byte* _name;
/// <summary>
/// A pointer to an array of touchpad descriptions, required if <see cref="NTouchpads"/> is > 0.
/// </summary>
public SDL_VirtualJoystickTouchpadDesc* Touchpads;
/// <summary>
/// A pointer to an array of sensor descriptions, required if <see cref="NSensors"/> is > 0.
/// </summary>
public SDL_VirtualJoystickSensorDesc* Sensors;
/// <summary>
/// User data pointer passed to callbacks.
/// </summary>
public void* UserData;
/// <summary>
/// Called when the joystick state should be updated.
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public SDL_VirtualJoystickUpdateCallback Update;
/// <summary>
/// Called when the player index is set.
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public SDL_VirtualJoysticSetPlayerIndexCallback SetPlayerIndex;
/// <summary>
/// Implements <see cref="SDL.RumbleJoystick(SDL_Joystick*, ushort, ushort, uint)"/>.
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public SDL_VirtualJoysticRumbleCallback Rumble;
/// <summary>
/// Implements <see cref="SDL.RumbleJoystickTriggers(SDL_Joystick*, ushort, ushort, uint)"/>.
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public SDL_VirtualJoysticRumbleTriggersCallback RumbleTriggers;
/// <summary>
/// Implements <see cref="SDL.SetJoystickLED(SDL_Joystick*, byte, byte, byte)"/>.
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public SDL_VirtualJoysticSetLEDCallback SetLED;
/// <summary>
/// Implements <see cref="SDL.SendJoystickEffect(SDL_Joystick*, void*, int)"/>.
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public SDL_VirtualJoysticSendEffectCallback SendEffect;
/// <summary>
/// Implements <see cref="SDL.SetGamepadSensorEnabled(SDL_Gamepad*, SDL_SensorType, bool)"/>.
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public SDL_VirtualJoysticSetSensorsEnabledCallback SetSensorsEnabled;
}
I implemented the function pointers using delegates, something like this:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void SDL_VirtualJoystickUpdateCallback(void* userData);
The other delegates implemented the same.
But when trying to implement the SDL_AttachVirtualJoystick
function, I get warned by the compiler CS8500 "This takes the address of, gets the size of, or declares a pointer to a managed type [...]"
The C# implementation of the function is as follows:
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern SDL_JoystickId SDL_AttachVirtualJoystick(SDL_VirtualJoystickDesc* desc);
SDL_JoystickId
is just an enum.
I understand that delegates are managed types, so I can't take its memory addresses that easily. I also know I can implement those fields as nint
s and use Marshal.GetFunctionPointerForDelegate()
, but I'm not sure if that's the most appropiate way.
My question is: what would be the correct way of implementing this struct?