[FFmpeg-devel] av_fopen_utf8 and cross-DLL CRT object sharing issue on Windows

Martin Storsjö martin at martin.st
Wed Apr 20 15:47:59 EEST 2022


Hi,

I just became aware of the av_fopen_utf8 function - which was introduced 
to fix path name translations on Windows - actually has a notable design 
flaw.


Background:

On Windows, a process can contain more than one C runtime (CRT); the 
system comes with two shared ones (UCRT and msvcrt.dll) and in MSVC 
builds, each DLL/EXE can have one statically linked in instead of linking 
against a shared library CRT (and that's actually the default 
configuration when building with MSVC).

This means that CRT objects (file descriptors from open(), FILE* opened 
with fopen/fdopen) mustn't be shared across DLLs; such an object must be 
opened, accessed and closed within the same DLL.


The issue:

This was fixed for the avpriv_open() function in 
e743e7ae6ee7e535c4394bec6fe6650d2b0dbf65 by duplicating the file_open.c 
source file in all libav* libraries (in MSVC builds, and renaming 
avpriv_open to ff_open), so that each library gets their own copy of 
ff_open (which isn't exported), so that all calls to open/read/write/close 
are done within the same DLL.

When av_fopen_utf8 was added afterwards, in 
85cabf1ca98fcc502fcf5b8d6bfb6d8061c2caef, this issue wasn't taken into 
account - although the issue is somewhat eased by lucky coincidence.

As av_fopen_utf8 is implemented in the same source file, 
libavutil/file_open.c, which gets duplicated in all libraries that use it, 
all uses of the function in other libraries (such as libavformat) actually 
end up using their own copy of it. (This also means that all the libav* 
DLLs export this function.) But for any users of the function outside of 
the ffmpeg libraries, this function (in libavutil, or whichever library it 
ends up imported from) returns a FILE* allocated by libavutil's CRT, which 
then can't be used with the fread/fwrite/fclose/whatever functions in the 
DLL/EXE that called it.

One concrete example of this is that the function is used for the twopass 
log file in fftools/ffmpeg_opt.c. To see the issue in action, build ffmpeg 
with MSVC with this config:

     ../ffmpeg/configure --enable-shared --toolchain=msvc --prefix=<dest>

Then try to do a twopass encoding with it:

     ffmpeg -i <input> -an -c:v ffv1 -pass 1 -f null -

     ffmpeg -i <input> -an -c:v ffv1 -pass 2 -y test-2pass.mkv

The same issue would appear anywhere this function is used from libavutil 
built as a DLL, from a caller built with a different CRT choice (e.g. 
often if mixing mingw/MSVC builds, which otherwise is supported just 
fine). (If built with a shared CRT, i.e. configured with 
--extra-cflags=-MD, it does work though.)


As this is a public function, we can't really do many tricks like we do 
within the libraries. (On the other hand, while it is a public function, 
it doesn't seem to be used much outside of ffmpeg, other than in ffmpeg 
API bindings for other languages.)

I guess the only really robust solution would be to turn av_fopen_utf8 
into a static inline function within the headers, essentially inlineing a 
copy of wchar_filename.h there, so that it expands to a call to the 
callers' _wfopen or similar. But that would end up polluting users' code 
by implicitly including windows.h everywhere, which really isn't nice to 
do either.

Or should we just document this issue, discourage further external use of 
the function, and fold this function into a ffmpeg-internal inline helper 
like libavutil/whcar_filename.h?


// Martin



More information about the ffmpeg-devel mailing list