From 702ed83f72a170aae176d370c43b68e1d152ec7f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 5 Sep 2024 16:28:48 -0700
Subject: [PATCH] Initialize interface structures so they can be extended in
the future
We guarantee that we will only add to the end of these interfaces, and any new fields will be optional.
---
docs/README-migration.md | 6 +--
include/SDL3/SDL_iostream.h | 26 ++++++++++---
include/SDL3/SDL_joystick.h | 16 +++++++-
include/SDL3/SDL_stdinc.h | 43 ++++++++++++++++++++++
include/SDL3/SDL_storage.h | 24 +++++++++++-
src/file/SDL_iostream.c | 17 ++++++---
src/joystick/virtual/SDL_virtualjoystick.c | 5 +++
src/storage/SDL_storage.c | 5 +++
src/storage/generic/SDL_genericstorage.c | 3 ++
src/storage/steam/SDL_steamstorage.c | 1 +
test/testautomation_iostream.c | 2 +-
test/testautomation_joystick.c | 2 +-
test/testcontroller.c | 2 +-
13 files changed, 130 insertions(+), 22 deletions(-)
diff --git a/docs/README-migration.md b/docs/README-migration.md
index 48a2a73abb943..29a5bbce63b0c 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 4e4ea1294dd46..b82116ddc818f 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 43bc1e98f7a50..da28a24460a52 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 dd9f7b5df0cba..9ae20d7c97c8a 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 91a90bc4ee9f5..d965cfd516029 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 7c04e65a685c8..59cb472259c94 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 0bbb0b51f62c2..bc3ef3f272434 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 5f8fb336c2118..069d9af799a20 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 430b93df2f958..4cd856cef7598 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 237334e2e9d84..7977ca71ab77d 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 782d643b046e8..7a5a23d2ec7a9 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 a7778e526f61a..ca4baebbfda60 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 2da043ae1f072..91400b913b264 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;