diff --git a/docs/README-migration.md b/docs/README-migration.md index 48a2a73abb..29a5bbce63 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -920,7 +920,7 @@ The functions SDL_GetJoysticks(), SDL_GetJoystickNameForID(), SDL_GetJoystickPat SDL_AttachVirtualJoystick() now returns the joystick instance ID instead of a device index, and returns 0 if there was an error. -SDL_VirtualJoystickDesc no longer takes a struct version; if we need to extend this in the future, we'll make a second struct and a second SDL_AttachVirtualJoystickEx-style function that uses it. Just zero the struct and don't set a version. +SDL_VirtualJoystickDesc version should not be set to SDL_VIRTUAL_JOYSTICK_DESC_VERSION, instead the structure should be initialized using SDL_INIT_INTERFACE(). The following functions have been renamed: * SDL_JoystickAttachVirtualEx() => SDL_AttachVirtualJoystick() @@ -983,7 +983,7 @@ The following functions have been removed: * SDL_JoystickNameForIndex() - replaced with SDL_GetJoystickNameForID() * SDL_JoystickPathForIndex() - replaced with SDL_GetJoystickPathForID() * SDL_NumJoysticks() - replaced with SDL_GetJoysticks() -* SDL_VIRTUAL_JOYSTICK_DESC_VERSION - no longer needed, version info has been removed from SDL_VirtualJoystickDesc. +* SDL_VIRTUAL_JOYSTICK_DESC_VERSION - no longer needed The following symbols have been removed: * SDL_JOYBALLMOTION @@ -1568,7 +1568,7 @@ SDL_IOStream *SDL_RWFromFP(FILE *fp, SDL_bool autoclose) return NULL; } - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); /* There's no stdio_size because SDL_GetIOSize emulates it the same way we'd do it for stdio anyhow. */ iface.seek = stdio_seek; iface.read = stdio_read; diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h index 4e4ea1294d..b82116ddc8 100644 --- a/include/SDL3/SDL_iostream.h +++ b/include/SDL3/SDL_iostream.h @@ -83,10 +83,17 @@ typedef enum SDL_IOWhence * already offers several common types of I/O streams, via functions like * SDL_IOFromFile() and SDL_IOFromMem(). * + * This structure should be initialized using SDL_INIT_INTERFACE() + * * \since This struct is available since SDL 3.0.0. + * + * \sa SDL_INIT_INTERFACE */ typedef struct SDL_IOStreamInterface { + /* The version of this interface */ + Uint32 version; + /** * Return the number of bytes in this SDL_IOStream * @@ -138,6 +145,15 @@ typedef struct SDL_IOStreamInterface } SDL_IOStreamInterface; +/* Check the size of SDL_IOStreamInterface + * + * If this assert fails, either the compiler is padding to an unexpected size, + * or the interface has been updated and this should be updated to match and + * the code using this interface should be updated to handle the old version. + */ +SDL_COMPILE_TIME_ASSERT(SDL_IOStreamInterface_SIZE, + (sizeof(void *) == 4 && sizeof(SDL_IOStreamInterface) == 24) || + (sizeof(void *) == 8 && sizeof(SDL_IOStreamInterface) == 48)); /** * The read/write operation structure. @@ -347,20 +363,18 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromDynamicMem(void); * read/write a common data source, you should use the built-in * implementations in SDL, like SDL_IOFromFile() or SDL_IOFromMem(), etc. * - * You must free the returned pointer with SDL_CloseIO(). - * * This function makes a copy of `iface` and the caller does not need to keep - * this data around after this call. + * it around after this call. * - * \param iface the function pointers that implement this SDL_IOStream. - * \param userdata the app-controlled pointer that is passed to iface's - * functions when called. + * \param iface the interface that implements this SDL_IOStream, initialized using SDL_INIT_INTERFACE(). + * \param userdata the pointer that will be passed to the interface functions. * \returns a pointer to the allocated memory on success or NULL on failure; * call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * * \sa SDL_CloseIO + * \sa SDL_INIT_INTERFACE * \sa SDL_IOFromConstMem * \sa SDL_IOFromFile * \sa SDL_IOFromMem diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h index 43bc1e98f7..da28a24460 100644 --- a/include/SDL3/SDL_joystick.h +++ b/include/SDL3/SDL_joystick.h @@ -414,16 +414,18 @@ typedef struct SDL_VirtualJoystickSensorDesc /** * The structure that describes a virtual joystick. * - * All elements of this structure are optional and can be left 0. + * This structure should be initialized using SDL_INIT_INTERFACE(). All elements of this structure are optional. * * \since This struct is available since SDL 3.0.0. * * \sa SDL_AttachVirtualJoystick + * \sa SDL_INIT_INTERFACE * \sa SDL_VirtualJoystickSensorDesc * \sa SDL_VirtualJoystickTouchpadDesc */ typedef struct SDL_VirtualJoystickDesc { + Uint32 version; /**< the version of this interface */ Uint16 type; /**< `SDL_JoystickType` */ Uint16 padding; /**< unused */ Uint16 vendor_id; /**< the USB vendor ID of this joystick */ @@ -454,10 +456,20 @@ typedef struct SDL_VirtualJoystickDesc void (SDLCALL *Cleanup)(void *userdata); /**< Cleans up the userdata when the joystick is detached */ } SDL_VirtualJoystickDesc; +/* Check the size of SDL_VirtualJoystickDesc + * + * If this assert fails, either the compiler is padding to an unexpected size, + * or the interface has been updated and this should be updated to match and + * the code using this interface should be updated to handle the old version. + */ +SDL_COMPILE_TIME_ASSERT(SDL_VirtualJoystickDesc_SIZE, + (sizeof(void *) == 4 && sizeof(SDL_VirtualJoystickDesc) == 84) || + (sizeof(void *) == 8 && sizeof(SDL_VirtualJoystickDesc) == 136)); + /** * Attach a new virtual joystick. * - * \param desc joystick description. + * \param desc joystick description, initialized using SDL_INIT_INTERFACE(). * \returns the joystick instance ID, or 0 on failure; call SDL_GetError() for * more information. * diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index dd9f7b5df0..9ae20d7c97 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -529,6 +529,49 @@ SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int)); extern "C" { #endif +/** + * A macro to initialize an SDL interface. + * + * This macro will initialize an SDL interface structure and should be called before you fill out the fields with your implementation. + * + * You can use it like this: + * + * ```c + * SDL_IOStreamInterface iface; + * + * SDL_INIT_INTERFACE(&iface); + * + * // Fill in the interface function pointers with your implementation + * iface.seek = ... + * + * stream = SDL_OpenIO(&iface, NULL); + * ``` + * + * If you are using designated initializers, you can use the size of the interface as the version, e.g. + * + * ```c + * SDL_IOStreamInterface iface = { + * .version = sizeof(iface), + * .seek = ... + * }; + * stream = SDL_OpenIO(&iface, NULL); + * ``` + * + * \threadsafety It is safe to call this macro from any thread. + * + * \since This macro is available since SDL 3.0.0. + * + * \sa SDL_IOStreamInterface + * \sa SDL_StorageInterface + * \sa SDL_VirtualJoystickDesc + */ +#define SDL_INIT_INTERFACE(iface) \ + do { \ + SDL_zerop(iface); \ + (iface)->version = sizeof(*(iface)); \ + } while (0) + + #ifndef SDL_DISABLE_ALLOCA #define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) #define SDL_stack_free(data) diff --git a/include/SDL3/SDL_storage.h b/include/SDL3/SDL_storage.h index 91a90bc4ee..d965cfd516 100644 --- a/include/SDL3/SDL_storage.h +++ b/include/SDL3/SDL_storage.h @@ -52,10 +52,17 @@ extern "C" { * It is not usually necessary to do this; SDL provides standard * implementations for many things you might expect to do with an SDL_Storage. * + * This structure should be initialized using SDL_INIT_INTERFACE() + * * \since This struct is available since SDL 3.0.0. + * + * \sa SDL_INIT_INTERFACE */ typedef struct SDL_StorageInterface { + /* The version of this interface */ + Uint32 version; + /* Called when the storage is closed */ SDL_bool (SDLCALL *close)(void *userdata); @@ -90,6 +97,15 @@ typedef struct SDL_StorageInterface Uint64 (SDLCALL *space_remaining)(void *userdata); } SDL_StorageInterface; +/* Check the size of SDL_StorageInterface + * + * If this assert fails, either the compiler is padding to an unexpected size, + * or the interface has been updated and this should be updated to match and + * the code using this interface should be updated to handle the old version. + */ +SDL_COMPILE_TIME_ASSERT(SDL_StorageInterface_SIZE, + (sizeof(void *) == 4 && sizeof(SDL_StorageInterface) == 48) || + (sizeof(void *) == 8 && sizeof(SDL_StorageInterface) == 96)); /** * An abstract interface for filesystem access. * @@ -176,8 +192,11 @@ extern SDL_DECLSPEC SDL_Storage * SDLCALL SDL_OpenFileStorage(const char *path); * should use the built-in implementations in SDL, like SDL_OpenTitleStorage() * or SDL_OpenUserStorage(). * - * \param iface the function table to be used by this container. - * \param userdata the pointer that will be passed to the store interface. + * This function makes a copy of `iface` and the caller does not need to keep + * it around after this call. + * + * \param iface the interface that implements this storage, initialized using SDL_INIT_INTERFACE(). + * \param userdata the pointer that will be passed to the interface functions. * \returns a storage container on success or NULL on failure; call * SDL_GetError() for more information. * @@ -186,6 +205,7 @@ extern SDL_DECLSPEC SDL_Storage * SDLCALL SDL_OpenFileStorage(const char *path); * \sa SDL_CloseStorage * \sa SDL_GetStorageFileSize * \sa SDL_GetStorageSpaceRemaining + * \sa SDL_INIT_INTERFACE * \sa SDL_ReadStorageFile * \sa SDL_StorageReady * \sa SDL_WriteStorageFile diff --git a/src/file/SDL_iostream.c b/src/file/SDL_iostream.c index 7c04e65a68..59cb472259 100644 --- a/src/file/SDL_iostream.c +++ b/src/file/SDL_iostream.c @@ -419,7 +419,7 @@ static SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose) } SDL_IOStreamInterface iface; - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); // There's no stdio_size because SDL_GetIOSize emulates it the same way we'd do it for stdio anyhow. iface.seek = stdio_seek; iface.read = stdio_read; @@ -607,7 +607,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) } SDL_IOStreamInterface iface; - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); iface.size = Android_JNI_FileSize; iface.seek = Android_JNI_FileSeek; iface.read = Android_JNI_FileRead; @@ -636,7 +636,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) } SDL_IOStreamInterface iface; - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); iface.size = windows_file_size; iface.seek = windows_file_seek; iface.read = windows_file_read; @@ -698,7 +698,7 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) } SDL_IOStreamInterface iface; - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); iface.size = mem_size; iface.seek = mem_seek; iface.read = mem_read; @@ -732,7 +732,7 @@ SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) } SDL_IOStreamInterface iface; - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); iface.size = mem_size; iface.seek = mem_seek; iface.read = mem_read; @@ -832,7 +832,7 @@ SDL_IOStream *SDL_IOFromDynamicMem(void) } SDL_IOStreamInterface iface; - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); iface.size = dynamic_mem_size; iface.seek = dynamic_mem_seek; iface.read = dynamic_mem_read; @@ -868,6 +868,11 @@ SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata) SDL_InvalidParamError("iface"); return NULL; } + if (iface->version < sizeof(*iface)) { + // Update this to handle older versions of this interface + SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()"); + return NULL; + } SDL_IOStream *iostr = (SDL_IOStream *)SDL_calloc(1, sizeof(*iostr)); if (iostr) { diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c index 0bbb0b51f6..bc3ef3f272 100644 --- a/src/joystick/virtual/SDL_virtualjoystick.c +++ b/src/joystick/virtual/SDL_virtualjoystick.c @@ -142,6 +142,11 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des SDL_InvalidParamError("desc"); return 0; } + if (desc->version < sizeof(*desc)) { + // Update this to handle older versions of this interface + SDL_SetError("Invalid desc, should be initialized with SDL_INIT_INTERFACE()"); + return 0; + } hwdata = (joystick_hwdata *)SDL_calloc(1, sizeof(joystick_hwdata)); if (!hwdata) { diff --git a/src/storage/SDL_storage.c b/src/storage/SDL_storage.c index 5f8fb336c2..069d9af799 100644 --- a/src/storage/SDL_storage.c +++ b/src/storage/SDL_storage.c @@ -153,6 +153,11 @@ SDL_Storage *SDL_OpenStorage(const SDL_StorageInterface *iface, void *userdata) SDL_InvalidParamError("iface"); return NULL; } + if (iface->version < sizeof(*iface)) { + // Update this to handle older versions of this interface + SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()"); + return NULL; + } storage = (SDL_Storage *)SDL_calloc(1, sizeof(*storage)); if (storage) { diff --git a/src/storage/generic/SDL_genericstorage.c b/src/storage/generic/SDL_genericstorage.c index 430b93df2f..4cd856cef7 100644 --- a/src/storage/generic/SDL_genericstorage.c +++ b/src/storage/generic/SDL_genericstorage.c @@ -182,6 +182,7 @@ static Uint64 GENERIC_GetStorageSpaceRemaining(void *userdata) } static const SDL_StorageInterface GENERIC_title_iface = { + sizeof(SDL_StorageInterface), GENERIC_CloseStorage, NULL, // ready GENERIC_EnumerateStorageDirectory, @@ -224,6 +225,7 @@ TitleStorageBootStrap GENERIC_titlebootstrap = { }; static const SDL_StorageInterface GENERIC_user_iface = { + sizeof(SDL_StorageInterface), GENERIC_CloseStorage, NULL, // ready GENERIC_EnumerateStorageDirectory, @@ -259,6 +261,7 @@ UserStorageBootStrap GENERIC_userbootstrap = { }; static const SDL_StorageInterface GENERIC_file_iface = { + sizeof(SDL_StorageInterface), GENERIC_CloseStorage, NULL, // ready GENERIC_EnumerateStorageDirectory, diff --git a/src/storage/steam/SDL_steamstorage.c b/src/storage/steam/SDL_steamstorage.c index 237334e2e9..7977ca71ab 100644 --- a/src/storage/steam/SDL_steamstorage.c +++ b/src/storage/steam/SDL_steamstorage.c @@ -129,6 +129,7 @@ static Uint64 STEAM_GetStorageSpaceRemaining(void *userdata) } static const SDL_StorageInterface STEAM_user_iface = { + sizeof(SDL_StorageInterface), STEAM_CloseStorage, STEAM_StorageReady, NULL, // enumerate diff --git a/test/testautomation_iostream.c b/test/testautomation_iostream.c index 782d643b04..7a5a23d2ec 100644 --- a/test/testautomation_iostream.c +++ b/test/testautomation_iostream.c @@ -437,7 +437,7 @@ static int iostrm_testAllocFree(void *arg) SDL_IOStreamInterface iface; SDL_IOStream *rw; - SDL_zero(iface); + SDL_INIT_INTERFACE(&iface); rw = SDL_OpenIO(&iface, NULL); SDLTest_AssertPass("Call to SDL_OpenIO() succeeded"); SDLTest_AssertCheck(rw != NULL, "Validate result from SDL_OpenIO() is not NULL"); diff --git a/test/testautomation_joystick.c b/test/testautomation_joystick.c index a7778e526f..ca4baebbfd 100644 --- a/test/testautomation_joystick.c +++ b/test/testautomation_joystick.c @@ -27,7 +27,7 @@ static int TestVirtualJoystick(void *arg) SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); - SDL_zero(desc); + SDL_INIT_INTERFACE(&desc); desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; desc.naxes = SDL_GAMEPAD_AXIS_MAX; desc.nbuttons = SDL_GAMEPAD_BUTTON_MAX; diff --git a/test/testcontroller.c b/test/testcontroller.c index 2da043ae1f..91400b913b 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -1159,7 +1159,7 @@ static void OpenVirtualGamepad(void) return; } - SDL_zero(desc); + SDL_INIT_INTERFACE(&desc); desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; desc.naxes = SDL_GAMEPAD_AXIS_MAX; desc.nbuttons = SDL_GAMEPAD_BUTTON_MAX;