ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.5k stars 2.52k forks source link

Unable to dynamically load library on Windows using relative path or module name #5289

Open strangebug opened 4 years ago

strangebug commented 4 years ago

Windows only, WindowsDynLib.openW assumes that the path will start with \??\. This prevents loading of dll using relative path or the module name.

    pub fn openW(path_w: [*:0]const u16) !WindowsDynLib {
        return WindowsDynLib{
            // + 4 to skip over the \??\
            .dll = try windows.LoadLibraryW(path_w + 4),
        };
    }

All the following forms should work

var library = try DynLib.open("SDL2");
var library = try DynLib.open("SDL2.dll");
var library = try DynLib.open("./SDL2.dll");

But I get the following error

error: FileNotFound
D:\devel\zig-wk\zig\build-release\lib\zig\std\os\windows.zig:1098:31: 0x7ff678aa8d13 in std.os.windows.LoadLibraryW (shoot.obj)
            .MOD_NOT_FOUND => return error.FileNotFound,
                              ^
D:\devel\zig-wk\zig\build-release\lib\zig\std\dynamic_library.zig:344:20: 0x7ff678aa7a07 in std.dynamic_library.WindowsDynLib::std.dynamic_library.WindowsDynLib.openW (shoot.obj)
            .dll = try windows.LoadLibraryW(path_w + 4),
                   ^
D:\devel\zig-wk\zig\build-release\lib\zig\std\dynamic_library.zig:331:9: 0x7ff678aa2213 in std.dynamic_library.WindowsDynLib::std.dynamic_library.WindowsDynLib.open (shoot.obj)
        return openW(path_w.span().ptr);
        ^
D:\devel\zig-wk\mountain-robot\src\main.zig:58:15: 0x7ff678aa1d4d in main (shoot.obj)
    var lib = try DynLib.open("SDL2.dll");

Zig: 0.6.0+3aa259d11 Platform: Windows 10 Pro See: lib/std/dynamic_library.zig

strangebug commented 4 years ago

@daurnimator pointed out that we shouldn't strip the prefix \??\ because it allows support for NTFS long filename.

Unfortunately the function LoadLibraryW does not support this syntax, in order to pass long filename to that function we need to use the prefix \\?\.

Excerpt from the documentation, the full version is available here.

The Windows API has many functions that also have Unicode versions to permit an extended-length path for a maximum total path length of 32,767 characters. This type of path is composed of components separated by backslashes, each up to the value returned in the lpMaximumComponentLength parameter of the GetVolumeInformation function (this value is commonly 255 characters). To specify an extended-length path, use the "\\?\" prefix. For example, "\\?\D:\very long path".

Here a simple code to demonstrate the issue

// compile with: cl main.cpp /link ole32.lib
#include <stdio.h>
#include <windows.h>

DWORD TryLoadLibrary(LPCWSTR lpLibFileName) {
  HMODULE module = LoadLibraryW(lpLibFileName);
  DWORD err_code = GetLastError();
  if (err_code == 0) {
    FreeLibrary(module);
  }
  return err_code;
}

int main() {
  LPCWSTR path = L"D:\\devel\\SDL2-2.0.12\\lib\\x64\\SDL2.dll";
  LPCWSTR path_with_nt_prefix = L"\\??\\D:\\devel\\SDL2-2.0.12\\lib\\x64\\SDL2.dll";
  LPCWSTR path_with_prefix = L"\\\\?\\D:\\devel\\SDL2-2.0.12\\lib\\x64\\SDL2.dll";
  DWORD err_code;

  err_code = TryLoadLibrary(path);
  wprintf_s(L"LoadLibraryW( %s ) then GetLastError: %d\n", path, int(err_code));

  err_code = TryLoadLibrary(path_with_nt_prefix);
  wprintf_s(L"LoadLibraryW( %s ) then GetLastError: %d\n", path_with_nt_prefix, int(err_code));

  err_code = TryLoadLibrary(path_with_prefix);
  wprintf_s(L"LoadLibraryW( %s ) then GetLastError: %d\n", path_with_prefix, int(err_code));
}

This code ouput the following

LoadLibraryW( D:\devel\SDL2-2.0.12\lib\x64\SDL2.dll ) then GetLastError: 0
LoadLibraryW( \??\D:\devel\SDL2-2.0.12\lib\x64\SDL2.dll ) then GetLastError: 126
LoadLibraryW( \\?\D:\devel\SDL2-2.0.12\lib\x64\SDL2.dll ) then GetLastError: 0
kubkon commented 4 years ago

Just for posterity, here's a link to LoadLibraryExW implemented in ReactOS which might be useful when we move from kernel32.LoadLibraryW to our own wrapper of NT syscalls which will also fix this issue: LoadLibraryExW.