From 251272c7083533b90d6bf494e5f60a687f896165 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 7 Jul 2026 19:04:51 -0600 Subject: [PATCH] Applying: Fix Windows 7 crash after connecting when Python plugin init fails --- .github/workflows/windows-build.yml | 41 ++++++++++++++++++----------- plugins/python/generate_plugin.py | 24 +++++++++++++++++ src/common/plugin.c | 26 +++++++++++++----- win32/installer/zoitechat.iss.tt | 16 ++++++----- win32/zoitechat.props | 2 +- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index a60b30da..edfe089f 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -126,11 +126,12 @@ jobs: $pyRoot = $env:pythonLocation if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" } - $pyDir = "C:\gtk-build\python-3.8" - New-Item -Path $pyDir -ItemType Directory -Force | Out-Null - $target = Join-Path $pyDir "${{ matrix.platform }}" - if (Test-Path $target) { Remove-Item $target -Recurse -Force } - New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null + foreach ($pyDir in @("C:\gtk-build\python-3.8.10", "C:\gtk-build\python-3.8")) { + New-Item -Path $pyDir -ItemType Directory -Force | Out-Null + $target = Join-Path $pyDir "${{ matrix.platform }}" + if (Test-Path $target) { Remove-Item $target -Recurse -Force } + New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null + } - name: Build run: | @@ -143,23 +144,33 @@ jobs: exit /b 1 ) - set "LIB=%PYTHON_DIR%\libs;%LIB%" - set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%" - - powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }" - - msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }} - set "PYTHON38_DIR=C:\gtk-build\python-3.8\${{ matrix.platform }}" if not exist "%PYTHON38_DIR%\libs\python38.lib" ( echo Missing %PYTHON38_DIR%\libs\python38.lib dir "%PYTHON38_DIR%\libs" exit /b 1 ) + if not exist "%PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd" ( + echo Missing %PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd + exit /b 1 + ) - msbuild plugins\python\python3.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }} /p:SolutionDir=%CD%\win32\ /p:YourPython3Path=C:\gtk-build\python-3.8 /p:Python3Lib=python38 /p:Python3Output=hcpython38 - copy /Y "%PYTHON38_DIR%\Lib\site-packages\_cffi_backend.cp38*.pyd" "..\zoitechat-build\${{ matrix.platform }}\bin\" - msbuild win32\installer\installer.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }} /p:SolutionDir=%CD%\win32\ + set "LIB=%PYTHON_DIR%\libs;%LIB%" + set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%" + + powershell -NoProfile -ExecutionPolicy Bypass -Command "$archiveLib='C:\gtk-build\gtk\x64\release\lib\libarchive.lib'; if (-not (Test-Path $archiveLib)) { $archiveDll = Get-ChildItem 'C:\gtk-build\gtk\x64\release\bin\libarchive*.dll' | Select-Object -First 1; if ($archiveDll) { Push-Location 'C:\gtk-build\gtk\x64\release\lib'; & 'C:\gtk-build\gendef\gendef.exe' $archiveDll.FullName | Out-Null; $archiveDef = Get-ChildItem 'libarchive*.def' | Select-Object -First 1; if ($archiveDef) { & lib /def:$archiveDef.Name /machine:${{ matrix.platform }} /out:libarchive.lib | Out-Null }; Pop-Location } }" + + msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }} + if errorlevel 1 exit /b 1 + + if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\plugins\hcpython38.dll" ( + echo hcpython38.dll was not built + exit /b 1 + ) + if not exist "..\zoitechat-build\${{ matrix.platform }}\rel\_cffi_backend.cp38*.pyd" ( + echo _cffi_backend for Python 3.8 was not staged for the installer + exit /b 1 + ) shell: cmd - name: Preparing Artifacts diff --git a/plugins/python/generate_plugin.py b/plugins/python/generate_plugin.py index 3ebbdbc4..0eb1ac2d 100755 --- a/plugins/python/generate_plugin.py +++ b/plugins/python/generate_plugin.py @@ -53,6 +53,16 @@ builder.set_source('_zoitechat_embedded', ''' #include "config.h" #include "zoitechat-plugin.h" +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + static zoitechat_plugin *ph; CFFI_DLLEXPORT int _on_plugin_init(char **, char **, char **, char *, char *); CFFI_DLLEXPORT int _on_plugin_deinit(void); @@ -61,6 +71,20 @@ int zoitechat_plugin_init(zoitechat_plugin *plugin_handle, char **name_out, char **description_out, char **version_out, char *arg) { +#ifdef _WIN32 + /* CPython cannot be removed from a process once Py_Initialize has run. + If the embedded interpreter fails to start (e.g. _cffi_backend is + missing), the host responds to our 0 return by g_module_close()ing + this plugin, which would also drop the last reference to pythonXY.dll + and unload it mid-flight; the leftover process-global state then + crashes the process later, e.g. when a server connection spawns the + next thread. Pin this module so FreeLibrary can never unload it. */ + HMODULE self_handle; + GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_PIN | + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (LPCWSTR)&zoitechat_plugin_init, &self_handle); +#endif + if (ph != NULL) { puts ("Python plugin already loaded\\n"); diff --git a/src/common/plugin.c b/src/common/plugin.c index 94f023f4..ba6528bf 100644 --- a/src/common/plugin.c +++ b/src/common/plugin.c @@ -59,18 +59,32 @@ typedef struct session zoitechat_context; #define DEBUG(x) {x;} #ifdef WIN32 +/* Python >= 3.9 dropped support for Windows versions before 10, so those + * systems get a Python 3.8 build of the plugin instead. RtlGetVersion is + * used because GetVersionEx reports a version capped by the compatibility + * manifest rather than the real one. */ static gboolean -plugin_windows_is_windows7 (void) +plugin_windows_needs_python38 (void) { - OSVERSIONINFOEXA version; + typedef LONG (WINAPI *rtl_get_version_func) (PRTL_OSVERSIONINFOW); + rtl_get_version_func rtl_get_version; + RTL_OSVERSIONINFOW version; + HMODULE ntdll; + + ntdll = GetModuleHandleW (L"ntdll.dll"); + if (ntdll == NULL) + return FALSE; + + rtl_get_version = (rtl_get_version_func) GetProcAddress (ntdll, "RtlGetVersion"); + if (rtl_get_version == NULL) + return FALSE; memset (&version, 0, sizeof (version)); version.dwOSVersionInfoSize = sizeof (version); - if (!GetVersionExA ((OSVERSIONINFOA *)&version)) + if (rtl_get_version (&version) != 0) return FALSE; - return version.dwPlatformId == VER_PLATFORM_WIN32_NT && - version.dwMajorVersion == 6 && version.dwMinorVersion == 1; + return version.dwMajorVersion < 10; } #endif @@ -508,7 +522,7 @@ plugin_auto_load (session *sess) for_files (lib_dir, "hcfishlim.dll", plugin_auto_load_cb); for_files(lib_dir, "hclua.dll", plugin_auto_load_cb); for_files (lib_dir, "hcperl.dll", plugin_auto_load_cb); - if (plugin_windows_is_windows7 ()) + if (plugin_windows_needs_python38 ()) for_files (lib_dir, "hcpython38.dll", plugin_auto_load_cb); else for_files (lib_dir, "hcpython3.dll", plugin_auto_load_cb); diff --git a/win32/installer/zoitechat.iss.tt b/win32/installer/zoitechat.iss.tt index f0bf41a3..1c8204ec 100644 --- a/win32/installer/zoitechat.iss.tt +++ b/win32/installer/zoitechat.iss.tt @@ -59,7 +59,7 @@ Name: "plugins\upd"; Description: "Update Checker"; Types: normal custom; Flags: Name: "langs"; Description: "Language Interfaces"; Types: custom; Flags: disablenouninstallwarning Name: "langs\lua"; Description: "Lua (LuaJIT 2.1)"; Types: normal custom; Flags: disablenouninstallwarning Name: "langs\perl"; Description: "Perl (Strawberry Perl 5.42.0.1)"; Types: custom; Flags: disablenouninstallwarning -Name: "langs\python"; Description: "Python (Python 3.14.3, or 3.8.10 on Windows 7)"; Types: custom; Flags: disablenouninstallwarning +Name: "langs\python"; Description: "Python (Python 3.14.3, or 3.8.10 before Windows 10)"; Types: custom; Flags: disablenouninstallwarning Name: "themes"; Description: "GTK3 Themes"; Types: normal minimal custom; Flags: disablenouninstallwarning Name: "themes\windows10"; Description: "Windows 10"; Types: normal minimal custom; Flags: disablenouninstallwarning Name: "themes\windows10dark"; Description: "Windows 10 Dark"; Types: normal minimal custom; Flags: disablenouninstallwarning @@ -181,8 +181,8 @@ Source: "plugins\hcperl.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Co Source: "python\*.py"; DestDir: "{app}\python"; Flags: ignoreversion; Components: langs\python -Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python; Check: not IsWindows7 -Source: "plugins\hcpython38.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion skipifsourcedoesntexist; Components: langs\python; Check: IsWindows7 +Source: "plugins\hcpython3.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion; Components: langs\python; Check: not NeedsLegacyPython +Source: "plugins\hcpython38.dll"; DestDir: "{app}\plugins"; Flags: ignoreversion skipifsourcedoesntexist; Components: langs\python; Check: NeedsLegacyPython Source: "_cffi_backend.cp3*.pyd"; DestDir: "{app}"; Flags: ignoreversion; Components: langs\python Source: "zoitechat.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: libs @@ -329,17 +329,19 @@ begin Result := DirExists(ExpandConstant('{localappdata}') + '\enchant\myspell'); end; -function IsWindows7(): Boolean; +// Python >= 3.9 dropped support for Windows versions before 10; +// those systems get Python 3.8 and the matching plugin instead. +function NeedsLegacyPython(): Boolean; var Version: TWindowsVersion; begin GetWindowsVersionEx(Version); - Result := Version.NTPlatform and (Version.Major = 6) and (Version.Minor = 1); + Result := Version.NTPlatform and (Version.Major < 10); end; function PythonURL(): String; begin - if IsWindows7() then + if NeedsLegacyPython() then Result := 'https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe' else Result := 'https://www.python.org/ftp/python/3.14.3/python-3.14.3-amd64.exe'; @@ -347,7 +349,7 @@ end; function PythonDLL(): String; begin - if IsWindows7() then + if NeedsLegacyPython() then Result := 'python38.dll' else Result := 'python314.dll'; diff --git a/win32/zoitechat.props b/win32/zoitechat.props index daf9bcfa..5513e3c4 100644 --- a/win32/zoitechat.props +++ b/win32/zoitechat.props @@ -8,7 +8,7 @@ c:\gtk-build\gendef c:\gtk-build\perl-5.42.0.1 c:\gtk-build\python-3.14 - c:\gtk-build\python-3.8.10 + c:\gtk-build\python-3.8 c:\gtk-build\WinSparkle