Added SDL_FlushIO()

Also added SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER and refactored the internal API to be able to create SDL_IOStream objects from native file handles.
This commit is contained in:
Sam Lantinga 2024-09-13 14:13:53 -07:00
parent 93caf1cd21
commit 6c83491116
6 changed files with 205 additions and 76 deletions

View File

@ -133,6 +133,17 @@ typedef struct SDL_IOStreamInterface
*/
size_t (SDLCALL *write)(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status);
/**
* If the stream is buffering, make sure the data is written out.
*
* On failure, you should set `*status` to a value from the
* SDL_IOStatus enum. You do not have to explicitly set this on
* a successful flush.
*
* \return SDL_TRUE if successful or SDL_FALSE on write error when flushing data.
*/
SDL_bool (SDLCALL *flush)(void *userdata, SDL_IOStatus *status);
/**
* Close and free any allocated resources.
*
@ -152,8 +163,8 @@ typedef struct SDL_IOStreamInterface
* 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));
(sizeof(void *) == 4 && sizeof(SDL_IOStreamInterface) == 28) ||
(sizeof(void *) == 8 && sizeof(SDL_IOStreamInterface) == 56));
/**
* The read/write operation structure.
@ -233,6 +244,7 @@ typedef struct SDL_IOStream SDL_IOStream;
* than your app, trying to use this pointer will almost certainly result in
* a crash! This is mostly a problem on Windows; make sure you build SDL and
* your app with the same compiler and settings to avoid it.
* - `SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER`: a file descriptor that this SDL_IOStream is using to access the filesystem.
* - `SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER`: a pointer, that can be cast
* to an Android NDK `AAsset *`, that this SDL_IOStream is using to access
* the filesystem. If SDL used some other method to access the filesystem,
@ -247,6 +259,7 @@ typedef struct SDL_IOStream SDL_IOStream;
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CloseIO
* \sa SDL_FlushIO
* \sa SDL_ReadIO
* \sa SDL_SeekIO
* \sa SDL_TellIO
@ -256,6 +269,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons
#define SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER "SDL.iostream.windows.handle"
#define SDL_PROP_IOSTREAM_STDIO_FILE_POINTER "SDL.iostream.stdio.file"
#define SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER "SDL.iostream.file_descriptor"
#define SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER "SDL.iostream.android.aasset"
/**
@ -282,6 +296,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons
*
* \sa SDL_IOFromConstMem
* \sa SDL_CloseIO
* \sa SDL_FlushIO
* \sa SDL_ReadIO
* \sa SDL_SeekIO
* \sa SDL_TellIO
@ -465,8 +480,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_GetIOSize(SDL_IOStream *context);
* negative.
* \param whence any of `SDL_IO_SEEK_SET`, `SDL_IO_SEEK_CUR`,
* `SDL_IO_SEEK_END`.
* \returns the final offset in the data stream after the seek or a negative
* error code on failure; call SDL_GetError() for more information.
* \returns the final offset in the data stream after the seek or -1 on failure; call SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
@ -539,6 +553,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_ReadIO(SDL_IOStream *context, void *ptr,
* \sa SDL_IOprintf
* \sa SDL_ReadIO
* \sa SDL_SeekIO
* \sa SDL_FlushIO
* \sa SDL_GetIOStatus
*/
extern SDL_DECLSPEC size_t SDLCALL SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size);
@ -580,6 +595,22 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOprintf(SDL_IOStream *context, SDL_PRINT
*/
extern SDL_DECLSPEC size_t SDLCALL SDL_IOvprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(2);
/**
* Flush any buffered data in the stream.
*
* This function makes sure that any buffered data is written to the stream. Normally this isn't necessary but if the stream is a pipe or socket it guarantees that any pending data is sent.
*
* \param context SDL_IOStream structure to flush.
* \returns SDL_TRUE on success or SDL_FALSE on failure; call SDL_GetError()
* for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_OpenIO
* \sa SDL_WriteIO
*/
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_FlushIO(SDL_IOStream *context);
/**
* Load all the data from an SDL data stream.
*
@ -590,7 +621,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOvprintf(SDL_IOStream *context, SDL_PRIN
* The data should be freed with SDL_free().
*
* \param src the SDL_IOStream to read all available data from.
* \param datasize if not NULL, will store the number of bytes read.
* \param datasize a pointer filled in with the number of bytes read, may be NULL.
* \param closeio if SDL_TRUE, calls SDL_CloseIO() on `src` before returning,
* even in the case of an error.
* \returns the data or NULL on failure; call SDL_GetError() for more

View File

@ -169,6 +169,7 @@ SDL3_0.0.0 {
SDL_FlushAudioStream;
SDL_FlushEvent;
SDL_FlushEvents;
SDL_FlushIO;
SDL_FlushRenderer;
SDL_GDKResumeGPU;
SDL_GDKSuspendComplete;

View File

@ -194,6 +194,7 @@
#define SDL_FlushAudioStream SDL_FlushAudioStream_REAL
#define SDL_FlushEvent SDL_FlushEvent_REAL
#define SDL_FlushEvents SDL_FlushEvents_REAL
#define SDL_FlushIO SDL_FlushIO_REAL
#define SDL_FlushRenderer SDL_FlushRenderer_REAL
#define SDL_GDKResumeGPU SDL_GDKResumeGPU_REAL
#define SDL_GDKSuspendComplete SDL_GDKSuspendComplete_REAL

View File

@ -214,6 +214,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_FlipSurface,(SDL_Surface *a, SDL_FlipMode b),(a,b),
SDL_DYNAPI_PROC(SDL_bool,SDL_FlushAudioStream,(SDL_AudioStream *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_FlushEvent,(Uint32 a),(a),)
SDL_DYNAPI_PROC(void,SDL_FlushEvents,(Uint32 a, Uint32 b),(a,b),)
SDL_DYNAPI_PROC(SDL_bool,SDL_FlushIO,(SDL_IOStream *a),(a),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_FlushRenderer,(SDL_Renderer *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_GDKResumeGPU,(SDL_GPUDevice *a),(a),)
SDL_DYNAPI_PROC(void,SDL_GDKSuspendComplete,(void),(),)

View File

@ -58,11 +58,12 @@ struct SDL_IOStream
typedef struct IOStreamWindowsData
{
bool append;
HANDLE h;
void *data;
size_t size;
size_t left;
bool append;
bool autoclose;
} IOStreamWindowsData;
@ -73,7 +74,7 @@ typedef struct IOStreamWindowsData
#define READAHEAD_BUFFER_SIZE 1024
static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *filename, const char *mode)
static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode)
{
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
UINT old_error_mode;
@ -83,9 +84,6 @@ static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *f
DWORD must_exist, truncate;
int a_mode;
SDL_zerop(iodata);
iodata->h = INVALID_HANDLE_VALUE; // mark this as unusable
// "r" = reading, file must exist
// "w" = writing, truncate existing, file may not exist
// "r+"= reading or writing, file must exist
@ -100,18 +98,13 @@ static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *f
w_right = (a_mode || SDL_strchr(mode, '+') || truncate) ? GENERIC_WRITE : 0;
if (!r_right && !w_right) {
return false; // inconsistent mode
return INVALID_HANDLE_VALUE; // inconsistent mode
}
// failed (invalid call)
iodata->data = (char *)SDL_malloc(READAHEAD_BUFFER_SIZE);
if (!iodata->data) {
return false;
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
// Do not open a dialog box if failure
old_error_mode =
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
old_error_mode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
#endif
{
@ -132,15 +125,9 @@ static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *f
#endif
if (h == INVALID_HANDLE_VALUE) {
SDL_free(iodata->data);
iodata->data = NULL;
SDL_SetError("Couldn't open %s", filename);
return false; // failed (CreateFile)
}
iodata->h = h;
iodata->append = a_mode ? true : false;
return true; // ok
return h;
}
static Sint64 SDLCALL windows_file_size(void *userdata)
@ -184,7 +171,7 @@ static Sint64 SDLCALL windows_file_seek(void *userdata, Sint64 offset, SDL_IOWhe
windowsoffset.QuadPart = offset;
if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, windowswhence)) {
return WIN_SetError("windows_file_seek");
return WIN_SetError("Error seeking in datastream");
}
return windowsoffset.QuadPart;
}
@ -215,7 +202,9 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
if (total_need < READAHEAD_BUFFER_SIZE) {
if (!ReadFile(iodata->h, iodata->data, READAHEAD_BUFFER_SIZE, &bytes, NULL)) {
SDL_SetError("Error reading from datastream");
if (GetLastError() != ERROR_HANDLE_EOF && GetLastError() != ERROR_BROKEN_PIPE) {
WIN_SetError("Error reading from datastream");
}
return 0;
}
read_ahead = SDL_min(total_need, bytes);
@ -225,7 +214,9 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
total_read += read_ahead;
} else {
if (!ReadFile(iodata->h, ptr, (DWORD)total_need, &bytes, NULL)) {
SDL_SetError("Error reading from datastream");
if (GetLastError() != ERROR_HANDLE_EOF && GetLastError() != ERROR_BROKEN_PIPE) {
WIN_SetError("Error reading from datastream");
}
return 0;
}
total_read += bytes;
@ -241,7 +232,7 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t
if (iodata->left) {
if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) {
SDL_SetError("Error seeking in datastream");
WIN_SetError("Error seeking in datastream");
return 0;
}
iodata->left = 0;
@ -252,13 +243,13 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t
LARGE_INTEGER windowsoffset;
windowsoffset.QuadPart = 0;
if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) {
SDL_SetError("Error seeking in datastream");
WIN_SetError("Error seeking in datastream");
return 0;
}
}
if (!WriteFile(iodata->h, ptr, (DWORD)total_bytes, &bytes, NULL)) {
SDL_SetError("Error writing to datastream");
WIN_SetError("Error writing to datastream");
return 0;
}
@ -269,13 +260,58 @@ static SDL_bool SDLCALL windows_file_close(void *userdata)
{
IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
if (iodata->h != INVALID_HANDLE_VALUE) {
CloseHandle(iodata->h);
if (iodata->autoclose) {
CloseHandle(iodata->h);
}
iodata->h = INVALID_HANDLE_VALUE; // to be sure
}
SDL_free(iodata->data);
SDL_free(iodata);
return true;
}
SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose)
{
IOStreamWindowsData *iodata = (IOStreamWindowsData *) SDL_calloc(1, sizeof (*iodata));
if (!iodata) {
if (autoclose) {
CloseHandle(handle);
}
return NULL;
}
SDL_IOStreamInterface iface;
SDL_INIT_INTERFACE(&iface);
if (GetFileType(handle) == FILE_TYPE_DISK) {
iface.size = windows_file_size;
iface.seek = windows_file_seek;
}
iface.read = windows_file_read;
iface.write = windows_file_write;
iface.close = windows_file_close;
iodata->h = handle;
iodata->append = (SDL_strchr(mode, 'a') != NULL);
iodata->autoclose = autoclose;
iodata->data = (char *)SDL_malloc(READAHEAD_BUFFER_SIZE);
if (!iodata->data) {
iface.close(iodata);
return NULL;
}
SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
if (!iostr) {
iface.close(iodata);
} else {
const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
if (props) {
SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h);
}
}
return iostr;
}
#endif // defined(SDL_PLATFORM_WINDOWS)
#if defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINDOWS)
@ -357,12 +393,12 @@ static Sint64 SDLCALL stdio_seek(void *userdata, Sint64 offset, SDL_IOWhence whe
if (is_noop || fseek(iodata->fp, (fseek_off_t)offset, stdiowhence) == 0) {
const Sint64 pos = ftell(iodata->fp);
if (pos < 0) {
SDL_SetError("Couldn't get stream offset");
SDL_SetError("Couldn't get stream offset: %s", strerror(errno));
return -1;
}
return pos;
}
SDL_SetError("Error seeking in datastream");
SDL_SetError("Error seeking in datastream: %s", strerror(errno));
return -1;
}
@ -371,7 +407,12 @@ static size_t SDLCALL stdio_read(void *userdata, void *ptr, size_t size, SDL_IOS
IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
const size_t bytes = fread(ptr, 1, size, iodata->fp);
if (bytes == 0 && ferror(iodata->fp)) {
SDL_SetError("Error reading from datastream");
if (errno == EAGAIN) {
*status = SDL_IO_STATUS_NOT_READY;
clearerr(iodata->fp);
} else {
SDL_SetError("Error reading from datastream: %s", strerror(errno));
}
}
return bytes;
}
@ -381,28 +422,50 @@ static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size,
IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
const size_t bytes = fwrite(ptr, 1, size, iodata->fp);
if (bytes == 0 && ferror(iodata->fp)) {
SDL_SetError("Error writing to datastream");
if (errno == EAGAIN) {
*status = SDL_IO_STATUS_NOT_READY;
clearerr(iodata->fp);
} else {
SDL_SetError("Error writing to datastream: %s", strerror(errno));
}
}
return bytes;
}
static SDL_bool SDLCALL stdio_flush(void *userdata, SDL_IOStatus *status)
{
IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
if (fflush(iodata->fp) != 0) {
if (errno == EAGAIN) {
*status = SDL_IO_STATUS_NOT_READY;
return false;
} else {
return SDL_SetError("Error flushing datastream: %s", strerror(errno));
}
}
return true;
}
static SDL_bool SDLCALL stdio_close(void *userdata)
{
IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
bool status = true;
if (iodata->autoclose) {
if (fclose(iodata->fp) != 0) {
status = SDL_SetError("Error writing to datastream");
status = SDL_SetError("Error writing to datastream: %s", strerror(errno));
}
}
SDL_free(iodata);
return status;
}
static SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
{
IOStreamStdioData *iodata = (IOStreamStdioData *) SDL_malloc(sizeof (*iodata));
IOStreamStdioData *iodata = (IOStreamStdioData *) SDL_calloc(1, sizeof (*iodata));
if (!iodata) {
if (autoclose) {
fclose(fp);
}
return NULL;
}
@ -412,6 +475,7 @@ static SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
iface.seek = stdio_seek;
iface.read = stdio_read;
iface.write = stdio_write;
iface.flush = stdio_flush;
iface.close = stdio_close;
iodata->fp = fp;
@ -424,6 +488,7 @@ static SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
if (props) {
SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_STDIO_FILE_POINTER, fp);
SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fileno(fp));
}
}
@ -542,7 +607,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
SDL_SetError("%s is not a regular file or pipe", file);
return NULL;
}
return SDL_IOFromFP(fp, 1);
return SDL_IOFromFP(fp, true);
}
} else if (SDL_strncmp(file, "content://", 10) == 0) {
// Try opening content:// URI
@ -573,7 +638,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
SDL_SetError("%s is not a regular file or pipe", path);
return NULL;
}
return SDL_IOFromFP(fp, 1);
return SDL_IOFromFP(fp, true);
}
}
}
@ -605,32 +670,9 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
}
#elif defined(SDL_PLATFORM_WINDOWS)
IOStreamWindowsData *iodata = (IOStreamWindowsData *) SDL_malloc(sizeof (*iodata));
if (!iodata) {
return NULL;
}
if (!windows_file_open(iodata, file, mode)) {
windows_file_close(iodata);
return NULL;
}
SDL_IOStreamInterface iface;
SDL_INIT_INTERFACE(&iface);
iface.size = windows_file_size;
iface.seek = windows_file_seek;
iface.read = windows_file_read;
iface.write = windows_file_write;
iface.close = windows_file_close;
iostr = SDL_OpenIO(&iface, iodata);
if (!iostr) {
windows_file_close(iodata);
} else {
const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
if (props) {
SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h);
}
HANDLE handle = windows_file_open(file, mode);
if (handle != INVALID_HANDLE_VALUE) {
iostr = SDL_IOFromHandle(handle, mode, true);
}
#elif defined(HAVE_STDIO_H)
@ -669,7 +711,7 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size)
return NULL;
}
IOStreamMemData *iodata = (IOStreamMemData *) SDL_malloc(sizeof (*iodata));
IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata));
if (!iodata) {
return NULL;
}
@ -703,7 +745,7 @@ SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size)
return NULL;
}
IOStreamMemData *iodata = (IOStreamMemData *) SDL_malloc(sizeof (*iodata));
IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata));
if (!iodata) {
return NULL;
}
@ -803,7 +845,7 @@ static SDL_bool SDLCALL dynamic_mem_close(void *userdata)
SDL_IOStream *SDL_IOFromDynamicMem(void)
{
IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) SDL_malloc(sizeof (*iodata));
IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) SDL_calloc(1, sizeof (*iodata));
if (!iodata) {
return NULL;
}
@ -816,11 +858,6 @@ SDL_IOStream *SDL_IOFromDynamicMem(void)
iface.write = dynamic_mem_write;
iface.close = dynamic_mem_close;
iodata->data.base = NULL;
iodata->data.here = NULL;
iodata->data.stop = NULL;
iodata->end = NULL;
SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
if (iostr) {
iodata->stream = iostr;
@ -922,6 +959,10 @@ void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, SDL_bool closeio)
if (size_read > 0) {
size_total += size_read;
continue;
} else if (SDL_GetIOStatus(src) == SDL_IO_STATUS_NOT_READY) {
// Wait for the stream to be ready
SDL_Delay(1);
continue;
}
// The stream status will remain set for the caller to check
@ -981,9 +1022,11 @@ Sint64 SDL_GetIOSize(SDL_IOStream *context)
Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence)
{
if (!context) {
return SDL_InvalidParamError("context");
SDL_InvalidParamError("context");
return -1;
} else if (!context->iface.seek) {
return SDL_Unsupported();
SDL_Unsupported();
return -1;
}
return context->iface.seek(context->userdata, offset, whence);
}
@ -1086,6 +1129,26 @@ size_t SDL_IOvprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char
return bytes;
}
SDL_bool SDL_FlushIO(SDL_IOStream *context)
{
bool result = true;
if (!context) {
return SDL_InvalidParamError("context");
}
context->status = SDL_IO_STATUS_READY;
SDL_ClearError();
if (context->iface.flush) {
result = context->iface.flush(context->userdata, &context->status);
}
if (!result && (context->status == SDL_IO_STATUS_READY)) {
context->status = SDL_IO_STATUS_ERROR;
}
return result;
}
// Functions for dynamically reading and writing endian-specific values
SDL_bool SDL_ReadU8(SDL_IOStream *src, Uint8 *value)

32
src/file/SDL_iostream_c.h Normal file
View File

@ -0,0 +1,32 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_iostream_c_h_
#define SDL_iostream_c_h_
#if defined(SDL_PLATFORM_WINDOWS)
SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose);
#elif defined(HAVE_STDIO_H)
extern SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose);
#endif
#endif // SDL_iostream_c_h_