【问题标题】:Delayload Python3 dll from VC++从 VC++ 延迟加载 Python3 dll
【发布时间】:2021-11-23 20:53:38
【问题描述】:

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

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

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

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

由于导入数据符号,LNK1194 无法延迟加载“python3.dll” '__imp__PyType_Type';没有 /DELAYLOAD:python3.dll 的链接

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

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

谢谢。

【问题讨论】:

  • 我认为不可能,因为那不是函数。真正的问题是你为什么需要这种行为?似乎是一个 XY 问题。一种方法是动态加载 .dll(但您必须重新定义类型和函数)。
  • 我需要它来支持“.py”扩展。我们应该运行一个解释器实例,为此需要链接 python3.dll。是的,进行动态加载似乎是一个不错的方法,但我对 PyType_Type 有点担心,因为我对导入数据类型了解不多。
  • 很明显,您需要一些 Python 功能,否则不需要与 python3.lib 链接。我的问题是为什么要延迟加载?
  • 啊好吧!因为否则,如果系统上没有 python,并且它是一个可选功能,那么整个事情都会关闭......我想延迟加载检查 dll 是否存在,然后启用它。
  • 嗯,可能有办法。将 python.lib 链接到一个 .dll,它公开了一个(简单的)API,并且只(尝试)在您需要时动态加载它访问 Python 功能。

标签: python c++ visual-studio dll cpython


【解决方案1】:

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

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\StackOverflow\q069418904]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\pc032\MS\VisualStudioCommunity\2019\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\03.08\include" dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll /LIBPATH:"c:\Install\pc064\Python\Python\03.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\03.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\StackOverflow\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\StackOverflow\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% 中不存在延迟加载方法会崩溃

【讨论】:

    【解决方案2】:

    于是我找到了 Raymond Chen 的一篇关于 dll 转发的帖子。这可能是一个解决方案,所以我现在将其发布在这里,如果可行,稍后再更新。

    Raymond Chen 发帖:https://devblogs.microsoft.com/oldnewthing/20080204-00/?p=23593

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

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

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

    【讨论】:

      猜你喜欢
      • 2010-11-26
      • 1970-01-01
      • 2017-02-11
      • 1970-01-01
      • 2021-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-23
      相关资源
      最近更新 更多