Я пишу оболочку C# для SDL3. В настоящее время я пытаюсь реализовать следующую структуру:
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;
Эта структура используется в следующей функции C:
extern SDL_DECLSPEC SDL_JoystickID SDLCALL SDL_AttachVirtualJoystick(const SDL_VirtualJoystickDesc *desc);
Моя (наивная) реализация C# для SDL_VirtualJoystickDesc
:
[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;
}
Я реализовал указатели функций с помощью делегатов, примерно так:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void SDL_VirtualJoystickUpdateCallback(void* userData);
Остальные делегаты реализовали то же самое.
Но при попытке реализовать функцию SDL_AttachVirtualJoystick
компилятор CS8500 предупреждает меня: «Это принимает адрес, размер или объявляет указатель на управляемый тип [...]»
Реализация функции на языке C# выглядит следующим образом:
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern SDL_JoystickId SDL_AttachVirtualJoystick(SDL_VirtualJoystickDesc* desc);
SDL_JoystickId
— это просто перечисление.
Я понимаю, что делегаты являются управляемыми типами, поэтому я не могу легко получить адреса их памяти. Я также знаю, что могу реализовать эти поля как nint
и использовать Marshal.GetFunctionPointerForDelegate()
, но я не уверен, что это наиболее подходящий способ.
Мой вопрос: каков будет правильный способ реализации этой структуры?
Вы слишком усложняете его, используя голые указатели. Пусть маршаллер разбирается, у него это неплохо получается.
UnmanagedType.FunctionPtr
— значение по умолчанию для делегатов, его не нужно указывать.CharSet
.[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SDL_VirtualJoystickDesc
{
public SDL_JoystickType Type;
private readonly ushort _padding1;
public ushort VendorId;
public ushort ProductId;
public ushort NAxes;
public ushort NButtons;
public ushort NBalls;
public ushort NHats;
public ushort NTouchpads;
public ushort NSensors;
private readonly ushort _padding2;
private readonly ushort _padding3;
public uint ButtonMask;
public uint AxisMask;
public string? Name;
public SDL_VirtualJoystickTouchpadDesc[]? Touchpads;
public SDL_VirtualJoystickSensorDesc[]? Sensors;
public IntPtr UserData;
public SDL_VirtualJoystickUpdateCallback? Update;
public SDL_VirtualJoysticSetPlayerIndexCallback? SetPlayerIndex;
public SDL_VirtualJoysticRumbleCallback? Rumble;
public SDL_VirtualJoysticRumbleTriggersCallback? RumbleTriggers;
public SDL_VirtualJoysticSetLEDCallback? SetLED;
public SDL_VirtualJoysticSendEffectCallback? SendEffect;
public SDL_VirtualJoysticSetSensorsEnabledCallback? SetSensorsEnabled;
}
void*
или любой другой тип указателя в указателях функций, в этом нет необходимости. Просто используйте IntPtr
вместо UserData
и передайте GCHandle
, если вам действительно нужны какие-то данные, или IntPtr.Zero
, если нет.[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SDL_VirtualJoystickUpdateCallback(IntPtr userData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SDL_VirtualJoysticSetPlayerIndexCallback(IntPtr userData, int player_index);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int SDL_VirtualJoysticRumbleCallback(IntPtr userData, ushort low_frequency_rumble, ushort high_frequency_rumble);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int SDL_VirtualJoysticRumbleTriggersCallback(IntPtr userData, , ushort left_rumble, ushort right_rumble);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int SDL_VirtualJoysticSetLEDCallback(IntPtr userData, Uint8 red, Uint8 green, Uint8 blue);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int SDL_VirtualJoysticSendEffectCallback(IntPtr userData, IntPtr data, int size);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int SDL_VirtualJoysticSetSensorsEnabledCallback(IntPtr userData, SDL_bool enabled);
[In] in
, а не как указатель *
.[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern SDL_JoystickId SDL_AttachVirtualJoystick([In] in SDL_VirtualJoystickDesc desc);
Спасибо за ответ :) но у меня вопрос: какова цель добавления атрибута [In] в параметр? Разве модификатора «in» недостаточно?
Нет, [In]
предназначен для маршаллера, но in
предназначен для компилятора C#, который компилирует его в ref
, маршаллер этого не видит. Если вы передаете по значению (т. е. без in
или ref
), то [In]
будет значением по умолчанию, но если вы добавите in
или ref
, вам также нужно будет указать [In]
, потому что значением по умолчанию для них является [In, Out]
, см. также stackoverflow.com/a/ 56098873/14868997
Понятно, спасибо за разъяснения!!
Добро пожаловать в StackOverflow. Пожалуйста, совершите экскурсию и посмотрите Как спросить . В частности, большая часть опубликованного кода, похоже, не требуется для минимально воспроизводимого примера (например, объявления многочисленных
Uint16
полей и более одного примера делегата).