From: "cfis (Charlie Savage) via ruby-core" Date: 2025-11-17T02:19:54+00:00 Subject: [ruby-core:123816] [Ruby Bug#21498] Windows - Ruby Overrides C Library APIs thus breaking them Issue #21498 has been updated by cfis (Charlie Savage). Ah yes, you are correct @yO4. I used MSVC 2026 to build both Ruby and the extension (a dll). By default Ruby builds with /Md. This is the generated `Makefile.sub` in the win32 directory: ``` !if !defined(RUNTIMEFLAG) RUNTIMEFLAG = -MD !endif ``` When the extension is built in debug mode it will link with `/MDd` and in release mode `/MD`. I tested using a debug build and release build for the extension. The debug results in a bad bitmap file and release mode results in a good one. So this is same issue I brought up earlier point about mixing runtime versions (I mentioned static libraries). By overriding `fclose`, Ruby is a causing a file descriptor created by the debug UCRT in the extension to be closed by the release URCT in Ruby. Microsoft specifically discusses this here and here: https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-170#what-problems-exist-if-an-application-uses-more-than-one-crt-version https://learn.microsoft.com/en-us/cpp/c-runtime-library/potential-errors-passing-crt-objects-across-dll-boundaries?view=msvc-170 It is ok for executables and different extensions to use different versions of the UCRT but you have to be careful to not pass memory allocations or CRT objects across UCRT boundaries. This is similar to building Ruby extensions before UCRT came out in 2015 (if you used Ruby to allocate memory you would have to call xfree, etc). By overriding `fclose` and other ucrt functions, Ruby causes UCRT objects to be incorrectly passed between UCRT versions. Which gets back to the bug - Ruby should not do this. I believe my MR fixes this - see https://github.com/ruby/ruby/pull/15205. As to your other question, the file is opened to by a call to `_Fiopen` ```cpp basic_filebuf* open(const wchar_t* _Filename, ios_base::openmode _Mode, int _Prot = ios_base::_Default_open_prot) { // in standard as const std::filesystem::path::value_type *; _Prot is an extension if (_Myfile) { return nullptr; } const auto _File = _Fiopen(_Filename, _Mode, _Prot); if (!_File) { return nullptr; // open failed } _Init(_File, _Openfl); _Initcvt(_STD use_facet<_Cvt>(_Mysb::getloc())); return this; // open succeeded } ``` Where `_fiopen` is referenced like this: ```cpp extern "C++" _CRTIMP2_PURE FILE* __CLRCALL_PURE_OR_CDECL _Fiopen(const wchar_t*, ios_base::openmode, int); ``` Summary - don't pass memory/crt objects across boundaries. By overriding `fclose` and other UCRT functions Ruby causes this to happen. ---------------------------------------- Bug #21498: Windows - Ruby Overrides C Library APIs thus breaking them https://bugs.ruby-lang.org/issues/21498#change-115219 * Author: cfis (Charlie Savage) * Status: Open * Assignee: windows * ruby -v: *all* on windows (testing with ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [x64-mswin64_140]) * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I am trying to wrap a simple C++ library, https://github.com/baderouaich/BitmapPlusPlus, as a Ruby extension. However when I use the extension to write a bitmap to disk the bitmap is corrupted. This is because the library uses std::ofstream which eventually uses the C API fclose to write the final bytes to the bitmap file and then closes it. The problem is that Ruby overrides `fclose` and replaces it with `rb_w32_fclose`. It then *exports* its custom version from from x64-vcruntime140-ruby340.dll. And the exported version is broken (at least from the standpoint of the C standard library). Note this has been a long standing issue. The first report I see is from 2013: https://bugs.ruby-lang.org/issues/8569 More recently in 2020 (which explains the issue very well): https://github.com/NREL/OpenStudio/issues/3942#issuecomment-610673401 I understand that Ruby is trying to provide a platform independent API. But the problem is this solution breaks any third party libraries that rely on these C APIs (which of course are very common). And there is no good workaround (see https://github.com/NREL/OpenStudio/issues/3942#issuecomment-611072774). So would it be possible for Ruby to stop exporting custom versions of basic C APIs? The code that does it is here: https://github.com/ruby/ruby/blob/master/win32/mkexports.rb#L41 Ruby of course could still use its custom versions of `fclose`, read, write etc within ruby.exe and x64-vcruntime140-ruby340.dll. But they should not be exported from x64-vcruntime140-ruby340.dll and thus be off limits to extensions. If a case comes up where an extension really needs access to rb_w32_fclose instead of fclose then an extension developer can use an `#ifdef _WIN32` to do so and work across platforms. That at least puts the developer in control versus now where I don't see any way I can wrap the bitmap library as a Ruby extension. >From my experience the biggest problem is the replacing of fclose with rb_w32_fclose. This is the list of generated overrides: FD_CLR=rb_w32_fdclr FD_ISSET=rb_w32_fdisset Sleep=rb_w32_Sleep accept=rb_w32_accept access=rb_w32_uaccess bind=rb_w32_bind close=rb_w32_close connect=rb_w32_connect dup2=rb_w32_dup2 fclose=rb_w32_fclose fstat=rb_w32_fstati128 get_osfhandle=rb_w32_get_osfhandle getcwd=rb_w32_ugetcwd getenv=rb_w32_ugetenv gethostbyaddr=rb_w32_gethostbyaddr gethostbyname=rb_w32_gethostbyname gethostname=rb_w32_gethostname getpeername=rb_w32_getpeername getpid=rb_w32_getpid getppid=rb_w32_getppid getprotobyname=rb_w32_getprotobyname getprotobynumber=rb_w32_getprotobynumber getservbyname=rb_w32_getservbyname getservbyport=rb_w32_getservbyport getsockname=rb_w32_getsockname getsockopt=rb_w32_getsockopt inet_ntop=rb_w32_inet_ntop inet_pton=rb_w32_inet_pton ioctlsocket=rb_w32_ioctlsocket isatty=rb_w32_isatty listen=rb_w32_listen lseek=rb_w32_lseek lstat=rb_w32_ulstati128 mkdir=rb_w32_umkdir mmap=rb_w32_mmap mprotect=rb_w32_mprotect munmap=rb_w32_munmap pipe=rb_w32_pipe pread=rb_w32_pread pwrite=rb_w32_pwrite read=rb_w32_read recv=rb_w32_recv recvfrom=rb_w32_recvfrom rename=rb_w32_urename rmdir=rb_w32_urmdir select=rb_w32_select send=rb_w32_send sendto=rb_w32_sendto setsockopt=rb_w32_setsockopt shutdown=rb_w32_shutdown socket=rb_w32_socket stati128=rb_w32_ustati128 strcasecmp=msvcrt.stricmp strerror=rb_w32_strerror strncasecmp=msvcrt.strnicmp times=rb_w32_times unlink=rb_w32_uunlink utime=rb_w32_uutime utimensat=rb_w32_uutimensat utimes=rb_w32_uutimes write=rb_w32_write ---Files-------------------------------- Screenshot 2025-11-06 233918.png (223 KB) primitives.bmp (360 KB) primitives_bad.bmp (360 KB) -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/