From 30193884fa83690082dc545daf7475a9e17d5c14 Mon Sep 17 00:00:00 2001 From: "Zeyi (Rice) Fan" Date: Wed, 1 Jul 2020 13:16:21 -0700 Subject: [PATCH] make fb_py_win_main to dynamically find Python3.dll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: In EdenFS's latest Windows package. We are seeing DLL import errors coming from `asyncio` as it requires a system native module `_overlapped.pyd`. The underlying cause is because when we build EdenFS CLI on Sandcastle, we are linking with Python 3.6.2. The Python36.dll shipped with the EdenFS package is also coming from that version. However, on Windows laptop. We have Python 3.6.3. Since we are not shipping the Python system libraries with us. It uses the libraries installed in the system, and it attempts to import the `_overlapped.pyd` located at `C:\Pythone36\DLLs\`. This version is compiled against Python 3.6.3, which is incompatible with the Python36.dll we are using. ---- To resolve this, we need either ship an embedded copy of Python along with EdenFS, or teach EdenFS to use the Python distribution installed in the system. This commit tweaks the executable we prepend to the archive created with zipapp to locate `Python3.dll` dynamically. This allows us to use the Python installed in the system so we can avoid the version mismatch issue. With this setup, we can also be shipping an embedded Python version along with EdenFS, and the Python loader can look for that path. This is demonstrated with the relative DLL loading `..\python`. In theory, we can have a package structure like this: ``` . ├── python │ ├── .... │ └── python3.dll └── bin ├── ... ├── edenfsctl.exe └── edenfs.exe ``` Reviewed By: xavierd Differential Revision: D22325210 fbshipit-source-id: 96a3f9503e7865a5f9d95710ff13f019afcf04f1 --- build/fbcode_builder/CMake/fb_py_win_main.c | 68 +++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/build/fbcode_builder/CMake/fb_py_win_main.c b/build/fbcode_builder/CMake/fb_py_win_main.c index 6630262be..41645ad68 100644 --- a/build/fbcode_builder/CMake/fb_py_win_main.c +++ b/build/fbcode_builder/CMake/fb_py_win_main.c @@ -1,12 +1,72 @@ // Copyright (c) Facebook, Inc. and its affiliates. -#define Py_LIMITED_API 1 #define WIN32_LEAN_AND_MEAN #include -#include +#include #include +#define PATH_SIZE 1024 + +typedef int (*Py_Main)(int, wchar_t**); + +// Add the given path to Windows's DLL search path. +// For Windows DLL search path resolution, see: +// https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order +void add_search_path(const wchar_t* path) { + wchar_t buffer[PATH_SIZE]; + wchar_t** lppPart = NULL; + + if (!GetFullPathNameW(path, PATH_SIZE, buffer, lppPart)) { + fwprintf(stderr, L"warning: %d unable to expand path %s\n", GetLastError(), path); + return; + } + + if (!AddDllDirectory(buffer)) { + DWORD error = GetLastError(); + if (error != ERROR_FILE_NOT_FOUND) { + fwprintf(stderr, L"warning: %d unable to set DLL search path for %s\n", GetLastError(), path); + } + } +} + +int locate_py_main(int argc, wchar_t **argv) { + /* + * We have to dynamically locate Python3.dll because we may be loading a + * Python native module while running. If that module is built with a + * different Python version, we will end up a DLL import error. To resolve + * this, we can either ship an embedded version of Python with us or + * dynamically look up existing Python distribution installed on user's + * machine. This way, we should be able to get a consistent version of + * Python3.dll and .pyd modules. + */ + HINSTANCE python_dll; + Py_Main pymain; + + // last added directory has highest priority + add_search_path(L"C:\\Python36\\"); + add_search_path(L"C:\\Python37\\"); + add_search_path(L"..\\python\\"); + + python_dll = LoadLibraryExW(L"python3.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + + if (python_dll != NULL) { + pymain = (Py_Main) GetProcAddress(python_dll, "Py_Main"); + + if (pymain != NULL) { + (pymain)(argc, argv); + } else { + fprintf(stderr, "error: %d unable to load Py_Main\n", GetLastError()); + } + + FreeLibrary(python_dll); + } else { + fprintf(stderr, "error: %d unable to locate python3.dll\n", GetLastError()); + return 1; + } + return 0; +} + int wmain() { /* * This executable will be prepended to the start of a Python ZIP archive. @@ -44,8 +104,6 @@ int wmain() { * "-h" * } */ - -#define PATH_SIZE 1024 wchar_t full_path_to_argv0[PATH_SIZE]; DWORD len = GetModuleFileNameW(NULL, full_path_to_argv0, PATH_SIZE); if (len == 0 || @@ -63,5 +121,5 @@ int wmain() { pyargv[0] = full_path_to_argv0; pyargv[1] = full_path_to_argv0; - return Py_Main(__argc + 1, pyargv); + return locate_py_main(__argc + 1, pyargv); }