Bläddra i källkod

iostream: Add optional free_func pointer property to memory streams

Fixes https://github.com/libsdl-org/SDL/issues/13368

Signed-off-by: Marcin Serwin <[email protected]>
Marcin Serwin 1 vecka sedan
förälder
incheckning
8451ce86c1
3 ändrade filer med 76 tillägg och 5 borttagningar
  1. 17 4
      include/SDL3/SDL_iostream.h
  2. 8 0
      src/io/SDL_iostream.c
  3. 51 1
      test/testautomation_iostream.c

+ 17 - 4
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

+ 8 - 0
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);
         }

+ 51 - 1
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) */