从 VC++ 延迟加载 Python3 dll

Delayload Python3 dll from VC++

我正在开发一个支持 Python 扩展的程序,我注意到如果用户的机器上没有 Python 或使用 x64 版本而不是 x32,它就不会打开一。 (我无法更改最后一部分,因为它不取决于我)。

所以我一直在阅读延迟加载以稍后检查库是否可用并这样做:

// linker: /DELAYLOAD:python3.dll
#include <delayimp.h>
#include <Python.h>
#pragma comment(lib, "delayimp")
#pragma comment(lib, "python3")

...一切正常,直到工作室给我这个问题:

LNK1194 cannot delay-load 'python3.dll' due to import of data symbol '__imp__PyType_Type'; link without /DELAYLOAD:python3.dll

所以我的问题是:这个问题有解决方法吗?

我一直在考虑从他们的 GitHub 页面编辑包含并直接在我的程序中定义 PyType_Type,但我害怕破坏某些东西...

谢谢。

所以我找到了 Raymond Chen 写的一篇关于 dll 转发的 post。这可能是一个解决方案,所以我暂时 post 在这里,如果可行的话稍后更新。

陈百强post:https://devblogs.microsoft.com/oldnewthing/20080204-00/?p=23593

编辑:对于其他库来说这似乎是一个不错的选择,但对于 Python 却不是。他们已经在这样做了(python3 dll 是对 python3X 的转发)并且 __imp__PyType_Type 仍然是一个问题。

最后我只是创建了一个 lib 的副本并将其放在一个名为 PyLibrary 的文件夹中,该文件夹使用 Python.

编译

Edit2:最后我可能会 python 关闭并进入另一个模块并延迟加载该模块。

这是一个小演示(正如我在评论中所想的那样)

dll00.h:

#pragma once

#if defined(_WIN32)
#  if defined DLL00_EXPORTS
#    define DLL00_EXPORT_API __declspec(dllexport)
#  else
#    define DLL00_EXPORT_API __declspec(dllexport)
#  endif
#else
#  define DLL00_EXPORT_API
#endif


#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API int dllPyFunc(const char *pyStr);

#if defined(__cplusplus)
}
#endif

dll00.c:

#define DLL00_EXPORTS

#include "dll00.h"

#include <Python.h>

#include <stdio.h>


int dllPyFunc(const char *pyStr)
{
    if (!pyStr) {
        printf("NULL PY test!\n");
        return -1;
    }
    int res = 0;
    if (!Py_IsInitialized())
        Py_InitializeEx(0);
    res = PyRun_SimpleString(pyStr);
    res |= Py_FinalizeEx();
    return res;
}

main00.c:

#if defined(DELAYLOAD)
#  include "dll00.h"

#  pragma comment(lib, "delayimp")
#  pragma comment(lib, "dll00")
#endif

#include <Windows.h>

#include <stdio.h>

#if !defined(DELAYLOAD)
typedef int (*DllPyFuncFuncPtr)(const char *pyStr);
DllPyFuncFuncPtr dllPyFunc = NULL;
#endif


int main(int argc, char *argv[])
{
    int ret = 0;
    printf("Arg count: %d\n", argc);
    if (argc == 1) {
        printf("NO PYTHON WHATSOEVER!!!\n");
        ret = 0;
    } else {
        printf("Attempt to run [%s] from Python\n", argv[1]);
#if !defined(DELAYLOAD)
        HMODULE hDll00 = LoadLibrary("dll00.dll");
        if (hDll00 == NULL) {
            printf("Error loading dll: %d\n", GetLastError());
            return -1;
        }
        dllPyFunc = (DllPyFuncFuncPtr)GetProcAddress(hDll00, "dllPyFunc");
        if (dllPyFunc == NULL) {
            printf("Error getting function: %d\n", GetLastError());
            FreeLibrary(hDll00);
            hDll00 = NULL;
            return -2;
        }
#endif
        ret = dllPyFunc(argv[1]);
#if !defined(DELAYLOAD)
        FreeLibrary(hDll00);
        hDll00 = NULL;
#endif
    }
    printf("\nDone.\n");
    return ret;
}

输出:

[cfati@CFATI-W10PC064:e:\Work\Dev\Whosebug\q069418904]> sopr.bat
### Set shorter prompt to better fit when pasted in Whosebug (or other) pages ###

[prompt]> "c:\Install\pc032\MS\VisualStudioCommunity19\VC\Auxiliary\Build\vcvarsall.bat" x64 >nul

[prompt]> dir /b
dll00.c
dll00.h
main00.c

[prompt]> :: Build .dll
[prompt]> cl /nologo /MD /DDLL /I"c:\Install\pc064\Python\Python.08\include" dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll /LIBPATH:"c:\Install\pc064\Python\Python.08\libs"
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]> :: Build .exe (dynamic .dll load)
[prompt]> cl /nologo /MD /W0 main00.c  /link /NOLOGO /OUT:main00_pc064.exe
main00.c

[prompt]> :: Build .exe (delayed .dll load)
[prompt]> cl /nologo /MD /W0 /DDELAYLOAD main00.c  /link /NOLOGO /OUT:main00_dl_pc064.exe /DELAYLOAD:dll00.dll
main00.c

[prompt]> dir /b
dll00.c
dll00.dll
dll00.exp
dll00.h
dll00.lib
dll00.obj
main00.c
main00.obj
main00_dl_pc064.exe
main00_pc064.exe

[prompt]> :: Save current path (which doesn't have python38.dll's parent)
[prompt]> set _PATH=%PATH%
[prompt]> :: Add python38.dll's parent to PATH
[prompt]> set PATH=%_PATH%;c:\Install\pc064\Python\Python.08

[prompt]>
[prompt]> main00_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!

Done.

[prompt]> main00_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
e:\Work\Dev\Whosebug\q069418904

Done.

[prompt]> main00_dl_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!

Done.

[prompt]> main00_dl_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
e:\Work\Dev\Whosebug\q069418904

Done.

[prompt]> :: NO python38.dll
[prompt]> set PATH=%_PATH%

[prompt]> main00_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!

Done.

[prompt]> main00_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
Error loading dll: 126

[prompt]> main00_dl_pc064.exe
Arg count: 1
NO PYTHON WHATSOEVER!!!

Done.

[prompt]> main00_dl_pc064.exe "import os;print(os.getcwd())"
Arg count: 2
Attempt to run [import os;print(os.getcwd())] from Python
<<<<<<<< CRASH HERE >>>>>>>>

备注:

  • 虽然这个例子太琐碎无法证明,动态加载库需要更多代码(在 .exe 端)
  • 另一方面,如果 Python 是必需的但不存在于 %PATH%[ 中,则延迟加载方法会崩溃=44=]