patch 9.0.2026: win32: python3 dll loading can be improved

Problem:  win32: python3 dll loading can be improved
Solution: Load DLL from registry path

Support loading python3.dll and/or python3xx.dll from the path written
in the registry.  To support Stable ABI's forwarder DLL (python3.dll),
use the `LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR` flag for `LoadLibraryExW()`
because python3xx.dll is placed in the same directory of python3.dll.

If Stable ABI is used, search the latest version from the registry (both
from HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE).  If Stable ABI is not
used, search only the matching version.

closes: #13315

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Ken Takata <kentkt@csc.jp>
diff --git a/src/if_python3.c b/src/if_python3.c
index c390089..a177371 100644
--- a/src/if_python3.c
+++ b/src/if_python3.c
@@ -835,17 +835,16 @@
  * Look up the library "libname" using the InstallPath registry key.
  * Return NULL when failed.  Return an allocated string when successful.
  */
-    static char *
+    static WCHAR *
 py3_get_system_libname(const char *libname)
 {
+    const WCHAR	*pythoncore = L"Software\\Python\\PythonCore";
     const char	*cp = libname;
-    char	subkey[128];
+    WCHAR	subkey[128];
     HKEY	hKey;
-    char	installpath[MAXPATHL];
-    LONG	len = sizeof(installpath);
-    LSTATUS	rc;
-    size_t	sysliblen;
-    char	*syslibname;
+    int		i;
+    DWORD	j, len;
+    LSTATUS	ret;
 
     while (*cp != '\0')
     {
@@ -857,35 +856,95 @@
 	}
 	++cp;
     }
-    vim_snprintf(subkey, sizeof(subkey),
-#  ifdef _WIN64
-		 "Software\\Python\\PythonCore\\%d.%d\\InstallPath",
-#  else
-		 "Software\\Python\\PythonCore\\%d.%d-32\\InstallPath",
+
+    WCHAR   keyfound[32];
+    HKEY    hKeyTop[] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
+    HKEY    hKeyFound = NULL;
+#  ifdef USE_LIMITED_API
+    long    maxminor = -1;
 #  endif
-		 PY_MAJOR_VERSION, PY_MINOR_VERSION);
-    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, subkey, 0, KEY_QUERY_VALUE, &hKey)
-							      != ERROR_SUCCESS)
+    for (i = 0; i < ARRAY_LENGTH(hKeyTop); i++)
+    {
+	long	major, minor;
+
+	ret = RegOpenKeyExW(hKeyTop[i], pythoncore, 0, KEY_READ, &hKey);
+	if (ret != ERROR_SUCCESS)
+	    continue;
+	for (j = 0;; j++)
+	{
+	    WCHAR   keyname[32];
+	    WCHAR   *wp;
+
+	    len = ARRAY_LENGTH(keyname);
+	    ret = RegEnumKeyExW(hKey, j, keyname, &len,
+						    NULL, NULL, NULL, NULL);
+	    if (ret == ERROR_NO_MORE_ITEMS)
+		break;
+
+	    major = wcstol(keyname, &wp, 10);
+	    if (*wp == L'.')
+		minor = wcstol(wp + 1, &wp, 10);
+#  ifdef _WIN64
+	    if (*wp != L'\0')
+		continue;
+#  else
+	    if (wcscmp(wp, L"-32") != 0)
+		continue;
+#  endif
+
+	    if (major != PY_MAJOR_VERSION)
+		continue;
+#  ifdef USE_LIMITED_API
+	    // Search the latest version.
+	    if ((minor > maxminor)
+		    && (minor >= ((Py_LIMITED_API >> 16) & 0xff)))
+	    {
+		maxminor = minor;
+		wcscpy(keyfound, keyname);
+		hKeyFound = hKeyTop[i];
+	    }
+#  else
+	    // Check if it matches with the compiled version.
+	    if (minor == PY_MINOR_VERSION)
+	    {
+		wcscpy(keyfound, keyname);
+		hKeyFound = hKeyTop[i];
+		break;
+	    }
+#  endif
+	}
+	RegCloseKey(hKey);
+#  ifdef USE_LIMITED_API
+	if (hKeyFound != NULL)
+	    break;
+#  endif
+    }
+    if (hKeyFound == NULL)
 	return NULL;
-    rc = RegQueryValueA(hKey, NULL, installpath, &len);
-    RegCloseKey(hKey);
-    if (ERROR_SUCCESS != rc)
+
+    swprintf(subkey, ARRAY_LENGTH(subkey), L"%ls\\%ls\\InstallPath",
+							pythoncore, keyfound);
+    ret = RegGetValueW(hKeyFound, subkey, NULL, RRF_RT_REG_SZ,
+							    NULL, NULL, &len);
+    if (ret != ERROR_MORE_DATA && ret != ERROR_SUCCESS)
 	return NULL;
-    cp = installpath + len;
-    // Just in case registry value contains null terminators.
-    while (cp > installpath && *(cp-1) == '\0')
-	--cp;
+    size_t len2 = len / sizeof(WCHAR) + 1 + strlen(libname);
+    WCHAR *path = alloc(len2 * sizeof(WCHAR));
+    if (path == NULL)
+	return NULL;
+    ret = RegGetValueW(hKeyFound, subkey, NULL, RRF_RT_REG_SZ,
+							    NULL, path, &len);
+    if (ret != ERROR_SUCCESS)
+    {
+	vim_free(path);
+	return NULL;
+    }
     // Remove trailing path separators.
-    while (cp > installpath && (*(cp-1) == '\\' || *(cp-1) == '/'))
-	--cp;
-    // Ignore if InstallPath is effectively empty.
-    if (cp <= installpath)
-	return NULL;
-    sysliblen = (cp - installpath) + 1 + STRLEN(libname) + 1;
-    syslibname = alloc(sysliblen);
-    vim_snprintf(syslibname, sysliblen, "%.*s\\%s",
-				(int)(cp - installpath), installpath, libname);
-    return syslibname;
+    size_t len3 = wcslen(path);
+    if ((len3 > 0) && (path[len3 - 1] == L'/' || path[len3 - 1] == L'\\'))
+	--len3;
+    swprintf(path + len3, len2 - len3, L"\\%hs", libname);
+    return path;
 }
 # endif
 
@@ -923,11 +982,13 @@
     if (!hinstPy3)
     {
 	// Attempt to use the path from InstallPath as stored in the registry.
-	char *syslibname = py3_get_system_libname(libname);
+	WCHAR *syslibname = py3_get_system_libname(libname);
 
 	if (syslibname != NULL)
 	{
-	    hinstPy3 = load_dll(syslibname);
+	    hinstPy3 = LoadLibraryExW(syslibname, NULL,
+		    LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
+		    LOAD_LIBRARY_SEARCH_SYSTEM32);
 	    vim_free(syslibname);
 	}
     }