Enable the use of UTF-8 filenames under Windows (#718)

While `fopen` (used through the macro `FLUID_FOPEN`) uses UTF-8 on *nix, it's restricted to ANSI on Windows. A change to enable using paths containing non-ANSI characters was suggested before in issue #128 but was rejected due to requiring large parts of both the public API and private implementation to be modified to accommodate Windows.

This PR instead changes the macro definition for `FLUID_FOPEN` from `fopen` to a new wrapper, `fluid_fopen`. This wrapper is defined in `fluidsynth_priv.h` and defined in `fluid_sys.c` (following the pattern of `fluid_alloc`). Under Windows, it converts the `const char*` UTF-8 parameters to Unicode `wchar_t*` strings using the Windows API function `MultiByteToWideChar` and opens the file using the Windows API-specific `_wfopen`. On all other platforms, it simply calls `fopen`.

The public API is unchanged. This solution will require Windows users of the API to convert UTF-16 strings to UTF-8 (which then get converted back into UTF-16 anyway), but that's still an improvement over only being able to use ANSI paths.

This PR also adds a new test, `test_utf8_open`, which tests `FLUID_FOPEN` directly and through `fluid_is_soundfont` and `fluid_synth_sfload` using a new soundfont file, `sf2/■VintageDreamsWaves-v2■.sf2`, which is just a copy of `VintageDreamsWaves-v2.sf2` with Unicode characters in the filename.
This commit is contained in:
getraid-gg 2020-12-22 04:34:44 -05:00 committed by derselbst
parent 468f6ff082
commit 1cdeebef37
7 changed files with 101 additions and 1 deletions

View file

@ -674,6 +674,7 @@ endif()
# manipulate some variables to setup a proper test env
set(TEST_SOUNDFONT "${CMAKE_SOURCE_DIR}/sf2/VintageDreamsWaves-v2.sf2")
set(TEST_SOUNDFONT_UTF8 "${CMAKE_SOURCE_DIR}/sf2/\\xE2\\x96\\xA0VintageDreamsWaves-v2\\xE2\\x96\\xA0.sf2")
set(TEST_SOUNDFONT_SF3 "${CMAKE_SOURCE_DIR}/sf2/VintageDreamsWaves-v2.sf3")
# Check for C99 float math

View file

@ -0,0 +1 @@
VintageDreamsWaves-v2.sf2

View file

@ -208,6 +208,9 @@
/* Soundfont to load for unit testing */
#cmakedefine TEST_SOUNDFONT "@TEST_SOUNDFONT@"
/* Soundfont to load for UTF-8 unit testing */
#cmakedefine TEST_SOUNDFONT_UTF8 "@TEST_SOUNDFONT_UTF8@"
/* SF3 Soundfont to load for unit testing */
#cmakedefine TEST_SOUNDFONT_SF3 "@TEST_SOUNDFONT_SF3@"

View file

@ -217,6 +217,60 @@ void* fluid_alloc(size_t len)
return ptr;
}
/**
* Open a file with a UTF-8 string, even in Windows
* @param filename The name of the file to open
* @param mode The mode to open the file in
*/
FILE *fluid_fopen(const char *filename, const char *mode)
{
#if defined(WIN32)
wchar_t *wpath, *wmode;
FILE *file;
int length;
if ((length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, NULL, 0)) == 0)
{
errno = EINVAL;
return NULL;
}
wpath = FLUID_MALLOC(length * sizeof(wchar_t));
if (wpath == NULL)
{
errno = EINVAL;
return NULL;
}
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, wpath, length);
if ((length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, NULL, 0)) == 0)
{
FLUID_FREE(wpath);
errno = EINVAL;
return NULL;
}
wmode = FLUID_MALLOC(length * sizeof(wchar_t));
if (wmode == NULL)
{
FLUID_FREE(wpath);
errno = EINVAL;
return NULL;
}
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, wmode, length);
file = _wfopen(wpath, wmode);
FLUID_FREE(wpath);
FLUID_FREE(wmode);
return file;
#else
return fopen(filename, mode);
#endif
}
/**
* Convenience wrapper for free() that satisfies at least C90 requirements.
*

View file

@ -191,10 +191,12 @@ typedef void (*fluid_rvoice_function_t)(void *obj, const fluid_rvoice_param_t pa
void* fluid_alloc(size_t len);
/* File access */
#define FLUID_FOPEN(_f,_m) fopen(_f,_m)
#define FLUID_FOPEN(_f,_m) fluid_fopen(_f,_m)
#define FLUID_FCLOSE(_f) fclose(_f)
#define FLUID_FREAD(_p,_s,_n,_f) fread(_p,_s,_n,_f)
FILE *fluid_fopen(const char *filename, const char *mode);
#ifdef WIN32
#define FLUID_FSEEK(_f,_n,_set) _fseeki64(_f,_n,_set)
#else

View file

@ -25,6 +25,7 @@ ADD_FLUID_TEST(test_seq_scale)
ADD_FLUID_TEST(test_seq_evt_order)
ADD_FLUID_TEST(test_seq_event_queue_remove)
ADD_FLUID_TEST(test_jack_obtaining_synth)
ADD_FLUID_TEST(test_utf8_open)
if ( LIBSNDFILE_HASVORBIS )
ADD_FLUID_TEST(test_sf3_sfont_loading)

38
test/test_utf8_open.c Normal file
View file

@ -0,0 +1,38 @@
#include "test.h"
#include "fluidsynth.h"
#include "utils/fluid_sys.h"
// this tests utf-8 file handling by loading the test .sf2 file
// manually and through the soundfont-related APIs
int main(void)
{
int id;
fluid_settings_t *settings;
fluid_synth_t *synth;
FILE *sfont_file;
sfont_file = FLUID_FOPEN(TEST_SOUNDFONT_UTF8, "rb");
TEST_ASSERT(sfont_file != NULL);
TEST_ASSERT(FLUID_FCLOSE(sfont_file) == 0);
settings = new_fluid_settings();
synth = new_fluid_synth(settings);
TEST_ASSERT(settings != NULL);
TEST_ASSERT(synth != NULL);
// no sfont loaded
TEST_ASSERT(fluid_synth_sfcount(synth) == 0);
TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT_UTF8) == TRUE);
// load a sfont to synth
TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT_UTF8, 1));
delete_fluid_synth(synth);
delete_fluid_settings(settings);
return EXIT_SUCCESS;
}