diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h index d1596f91d3..ec7ab76086 100644 --- a/include/SDL3/SDL_iostream.h +++ b/include/SDL3/SDL_iostream.h @@ -286,8 +286,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons * certain size, for both read and write access. * * This memory buffer is not copied by the SDL_IOStream; the pointer you - * provide must remain valid until you close the stream. Closing the stream - * will not free the original buffer. + * provide must remain valid until you close the stream. * * If you need to make sure the SDL_IOStream never writes to the memory * buffer, you should use SDL_IOFromConstMem() with a read-only buffer of @@ -300,6 +299,13 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons * - `SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER`: this will be the `size` parameter * that was passed to this function. * + * Additionally, the following properties are recognized: + * + * - `SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC`: if this property is set to a non-NULL + * value it will be interpreted as a function of SDL_free_func type and called + * with the passed `mem` pointer when closing the stream. By default it is + * unset, i.e., the memory will not be freed. + * * \param mem a pointer to a buffer to feed an SDL_IOStream stream. * \param size the buffer size, in bytes. * \returns a pointer to a new SDL_IOStream structure or NULL on failure; call @@ -321,6 +327,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) #define SDL_PROP_IOSTREAM_MEMORY_POINTER "SDL.iostream.memory.base" #define SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER "SDL.iostream.memory.size" +#define SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC "SDL.iostream.memory.free" /** * Use this function to prepare a read-only memory buffer for use with @@ -333,8 +340,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) * without writing to the memory buffer. * * This memory buffer is not copied by the SDL_IOStream; the pointer you - * provide must remain valid until you close the stream. Closing the stream - * will not free the original buffer. + * provide must remain valid until you close the stream. * * If you need to write to a memory buffer, you should use SDL_IOFromMem() * with a writable buffer of memory instead. @@ -346,6 +352,13 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromMem(void *mem, size_t size) * - `SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER`: this will be the `size` parameter * that was passed to this function. * + * Additionally, the following properties are recognized: + * + * - `SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC`: if this property is set to a non-NULL + * value it will be interpreted as a function of SDL_free_func type and called + * with the passed `mem` pointer when closing the stream. By default it is + * unset, i.e., the memory will not be freed. + * * \param mem a pointer to a read-only buffer to feed an SDL_IOStream stream. * \param size the buffer size, in bytes. * \returns a pointer to a new SDL_IOStream structure or NULL on failure; call diff --git a/src/io/SDL_iostream.c b/src/io/SDL_iostream.c index 989f3b9c4c..2e63b9aefb 100644 --- a/src/io/SDL_iostream.c +++ b/src/io/SDL_iostream.c @@ -716,6 +716,7 @@ typedef struct IOStreamMemData Uint8 *base; Uint8 *here; Uint8 *stop; + SDL_PropertiesID props; } IOStreamMemData; static Sint64 SDLCALL mem_size(void *userdata) @@ -779,6 +780,11 @@ static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SD static bool SDLCALL mem_close(void *userdata) { + IOStreamMemData *iodata = (IOStreamMemData *) userdata; + SDL_free_func free_func = SDL_GetPointerProperty(iodata->props, SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC, NULL); + if (free_func) { + free_func(iodata->base); + } SDL_free(userdata); return true; } @@ -950,6 +956,7 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) } else { const SDL_PropertiesID props = SDL_GetIOProperties(iostr); if (props) { + iodata->props = props; SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, mem); SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); } @@ -990,6 +997,7 @@ SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) } else { const SDL_PropertiesID props = SDL_GetIOProperties(iostr); if (props) { + iodata->props = props; SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, (void *)mem); SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); } diff --git a/test/testautomation_iostream.c b/test/testautomation_iostream.c index d1e5877111..23a0f28f24 100644 --- a/test/testautomation_iostream.c +++ b/test/testautomation_iostream.c @@ -312,6 +312,52 @@ static int SDLCALL iostrm_testConstMem(void *arg) return TEST_COMPLETED; } +static int free_call_count; +void SDLCALL test_free(void* mem) { + free_call_count++; + SDL_free(mem); +} + +static int SDLCALL iostrm_testMemWithFree(void *arg) +{ + void *mem; + SDL_IOStream *rw; + int result; + + /* Allocate some memory */ + mem = SDL_malloc(sizeof(IOStreamHelloWorldCompString) - 1); + if (mem == NULL) { + return TEST_ABORTED; + } + + /* Open handle */ + rw = SDL_IOFromMem(mem, sizeof(IOStreamHelloWorldCompString) - 1); + SDLTest_AssertPass("Call to SDL_IOFromMem() succeeded"); + SDLTest_AssertCheck(rw != NULL, "Verify opening memory with SDL_IOFromMem does not return NULL"); + + /* Bail out if NULL */ + if (rw == NULL) { + return TEST_ABORTED; + } + + /* Set the free function */ + free_call_count = 0; + result = SDL_SetPointerProperty(SDL_GetIOProperties(rw), SDL_PROP_IOSTREAM_MEMORY_FREE_FUNC, test_free); + SDLTest_AssertPass("Call to SDL_SetPointerProperty() succeeded"); + SDLTest_AssertCheck(result == true, "Verify result value is true; got %d", result); + + /* Run generic tests */ + testGenericIOStreamValidations(rw, true); + + /* Close handle */ + result = SDL_CloseIO(rw); + SDLTest_AssertPass("Call to SDL_CloseIO() succeeded"); + SDLTest_AssertCheck(result == true, "Verify result value is true; got: %d", result); + SDLTest_AssertCheck(free_call_count == 1, "Verify the custom free function was called once; call count: %d", free_call_count); + + return TEST_COMPLETED; +} + /** * Tests dynamic memory * @@ -686,10 +732,14 @@ static const SDLTest_TestCaseReference iostrmTest9 = { iostrm_testCompareRWFromMemWithRWFromFile, "iostrm_testCompareRWFromMemWithRWFromFile", "Compare RWFromMem and RWFromFile IOStream for read and seek", TEST_ENABLED }; +static const SDLTest_TestCaseReference iostrmTest10 = { + iostrm_testMemWithFree, "iostrm_testMemWithFree", "Tests opening from memory with free on close", TEST_ENABLED +}; + /* Sequence of IOStream test cases */ static const SDLTest_TestCaseReference *iostrmTests[] = { &iostrmTest1, &iostrmTest2, &iostrmTest3, &iostrmTest4, &iostrmTest5, &iostrmTest6, - &iostrmTest7, &iostrmTest8, &iostrmTest9, NULL + &iostrmTest7, &iostrmTest8, &iostrmTest9, &iostrmTest10, NULL }; /* IOStream test suite (global) */