Как мне маршалировать структуру C# с полями делегата?

Я пишу оболочку 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(), но я не уверен, что это наиболее подходящий способ.

Мой вопрос: каков будет правильный способ реализации этой структуры?

Добро пожаловать в StackOverflow. Пожалуйста, совершите экскурсию и посмотрите Как спросить . В частности, большая часть опубликованного кода, похоже, не требуется для минимально воспроизводимого примера (например, объявления многочисленных Uint16 полей и более одного примера делегата).

wohlstad 03.07.2024 10:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
1
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы слишком усложняете его, используя голые указатели. Пусть маршаллер разбирается, у него это неплохо получается.

  • 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» недостаточно?

lumPalette 04.07.2024 21:04

Нет, [In] предназначен для маршаллера, но in предназначен для компилятора C#, который компилирует его в ref, маршаллер этого не видит. Если вы передаете по значению (т. е. без in или ref), то [In] будет значением по умолчанию, но если вы добавите in или ref, вам также нужно будет указать [In], потому что значением по умолчанию для них является [In, Out], см. также stackoverflow.com/a/ 56098873/14868997

Charlieface 04.07.2024 22:36

Понятно, спасибо за разъяснения!!

lumPalette 05.07.2024 06:23

Другие вопросы по теме