python - Paradoxon : silent crash on Python's ctypes. CDLL 在导入时出现,但在直接运行时却没有 - 这怎么可能?

标签 python python-import ctypes

因此,作为一名 Linux 用户,我在 Windows 上遇到了一些我无法解释的非常令人费解的事情。

我有一个类似于此示例的项目结构:

D:\PROJECT
|
|   tolkien.py
|   __init__.py
|   
\---MiddleEarth
    |   gondor.py
    |   isengrad.c
    |   __init__.py
    |   
    \---lib
            isengrad.so

问题: 我将 isengrad.c 编译到共享库 isengrad.so 中,然后将其加载到 gondor.py。我的目标是将 gondor.py 导入 tolkien.py
虽然 gondor.py 直接运行时运行完美,但当我导入它时,代码在我通过 ctypes.CDLL 加载共享库时退出,没有任何错误信息。

复制: 文件的内容(添加了一些“状态消息”以跟踪问题发生的位置):

isengrad.c:

int isengrad(int hobbit){
    return hobbit/2;
}

然后用

编译成isengrad.so
D:\project>chdir MiddleEarth
D:\project\MiddleEarth>gcc -fPIC -shared -o lib/isengrad.so isengrad.c

然后在 gondor.py 中访问共享库:

print("started gondor")

import os, ctypes
path_to_isengrad = "D:/project/MiddleEarth/lib/isengrad.so"  

print("gondor loads isengrad")
gondor = ctypes.CDLL(path_to_isengrad)     # <--- crashes here when imported, not when ran directly
print("gondor loaded isengrad")


gondor.isengrad.argtypes = (ctypes.c_int,)

def faramir(hobbit):
    catched_hobbits = gondor.isengrad(hobbit)
    return catched_hobbits

if __name__ == '__main__':
    print(faramir(5))
    print("gondor ran")

print("gondor finished")

然后导入 tolkien.py:

print("started tolkien")
from MiddleEarth import gondor
print("tolkien imported gondor")

got = gondor.faramir(4)
print(got)

print("tolkien worked")

现在检查当我直接使用 gondor.py VS 当我将它导入 tolkien.py 时会发生什么:

D:\project>python MiddleEarth/gondor.py
started gondor
gondor loads isengrad
gondor loaded isengrad
2
gondor ran
gondor finished

D:\project>python tolkien.py
started tolkien
started gondor
gondor loads isengrad

D:\project>

直接运行完全没有问题。但是导入它会导致整个事情在加载共享库时崩溃而没有任何文字和回溯。这怎么会发生?我什至对共享库的路径进行了硬编码,所以不同的工作目录应该不是问题......我在 Kubuntu 上的同一个项目没有任何问题,所以这可能是一些与 Windows 相关的东西。

环境:

  • Python:Python 3.7.3(默认,2019 年 3 月 27 日,17:13:21)[MSC v.1915 64 位 (AMD64)]::Anaconda, Inc. on win32
  • 操作系统:Windows 10 10.0.17134 Build 17134(安装在 C:)
  • GCC:通过 Cygwin 安装,版本 7.4.0
  • 请询问是否需要任何其他详细信息。

最佳答案

从我看到这个问题的那一刻起,我就想说这是未定义的行为 (UB)。 Python 带有它的 C 运行时(UCRTLib),而 Cygwin .dll自带。在进程中混合使用编译器和 C 运行时,通常会导致灾难。
找到官方说法[Cygwin]: 6.15. Can I link with both MSVCRT*.DLL and cygwin1.dll? (强调是我的):

No, you must use one or the other, they are mutually exclusive.

检查 [SO]: How to circumvent Windows Universal CRT headers dependency on vcruntime.h (@CristiFati's answer)有关 MSVCRT*.DLL

的更多详细信息

现在,UB 的美妙之处在于它描述了一种看似随机的行为。

我准备了一个综合示例(稍微修改了您的代码)。

isengrad.c:

#if defined(_WIN32)
#  define ISENGRAD_EXPORT_API __declspec(dllexport)
#else
#  define ISENGRAD_EXPORT_API
#endif


ISENGRAD_EXPORT_API int isengrad(int hobbit) {
    return hobbit / 2;
}

script0.py:

#!/usr/bin/env python3

import sys
import ctypes


dll_name = "./lib/isengrad_{0:s}_{1:03d}.dll".format(sys.argv[1][:3] if sys.argv else sys.platform[:3].lower(), ctypes.sizeof(ctypes.c_void_p) * 8)
print("Attempting to load: {0:s}".format(dll_name))
isengrad_dll = ctypes.CDLL(dll_name)
print("DLL Loaded")


def main():
    isengrad_func = isengrad_dll.isengrad
    isengrad_func.argtypes = [ctypes.c_int]
    isengrad_func.restype = ctypes.c_int

    res = isengrad_func(46)
    print("{0:s} returned {1:}".format(isengrad_func.__name__, res))


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main()
    print("\nDone.")

script1.py:

#!/usr/bin/env python3

import sys
import script0


def main():
    pass


if __name__ == "__main__":
    print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    main()
    print("\nDone.")

输出:

  • 我将使用 3 个窗口:
    • cmd - Win(32 位64 位)
    • CygwinMintty:
      • 64 位
      • 32 位
  • 请注意,即使我将每个内容粘贴到一个 block 中(以避免分散它们),我也会在运行命令时在它们之间切换
  • Cygwin 32 位:

    [cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q056855348]> ~/sopr.sh
    *** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
    
    [032bit prompt]> gcc -shared -fPIC -o lib/isengrad_cyg_032.dll isengrad.c
    [032bit prompt]>  ls lib/*.dll
    lib/isengrad_cyg_032.dll  lib/isengrad_cyg_064.dll  lib/isengrad_win_032.dll  lib/isengrad_win_064.dll
    [032bit prompt]>
    [032bit prompt]> python3 script0.py cyg
    Attempting to load: ./lib/isengrad_cyg_032.dll
    DLL Loaded
    Python 3.6.4 (default, Jan  7 2018, 17:45:56) [GCC 6.4.0] 32bit on cygwin
    
    isengrad returned 23
    
    Done.
    [032bit prompt]>
    [032bit prompt]> python3 script1.py cyg
    Attempting to load: ./lib/isengrad_cyg_032.dll
    DLL Loaded
    Python 3.6.4 (default, Jan  7 2018, 17:45:56) [GCC 6.4.0] 32bit on cygwin
    
    
    Done.
    [032bit prompt]>
    [032bit prompt]> python3 script0.py win
    Attempting to load: ./lib/isengrad_win_032.dll
    DLL Loaded
    Python 3.6.4 (default, Jan  7 2018, 17:45:56) [GCC 6.4.0] 32bit on cygwin
    
    isengrad returned 23
    
    Done.
    [032bit prompt]>
    [032bit prompt]> python3 script1.py win
    Attempting to load: ./lib/isengrad_win_032.dll
    DLL Loaded
    Python 3.6.4 (default, Jan  7 2018, 17:45:56) [GCC 6.4.0] 32bit on cygwin
    
    
    Done.
    
  • Cygwin 64 位:

    [cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q056855348]> ~/sopr.sh
    *** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
    
    [064bit prompt]>  gcc -shared -fPIC -o lib/isengrad_cyg_064.dll isengrad.c
    [064bit prompt]> ls lib/*.dll
    lib/isengrad_cyg_032.dll  lib/isengrad_cyg_064.dll  lib/isengrad_win_032.dll  lib/isengrad_win_064.dll
    [064bit prompt]>
    [064bit prompt]> python3 script0.py cyg
    Attempting to load: ./lib/isengrad_cyg_064.dll
    DLL Loaded
    Python 3.6.8 (default, Feb 14 2019, 22:09:48) [GCC 7.4.0] 64bit on cygwin
    
    isengrad returned 23
    
    Done.
    [064bit prompt]>
    [064bit prompt]> python3 script1.py cyg
    Attempting to load: ./lib/isengrad_cyg_064.dll
    DLL Loaded
    Python 3.6.8 (default, Feb 14 2019, 22:09:48) [GCC 7.4.0] 64bit on cygwin
    
    
    Done.
    [064bit prompt]>
    [064bit prompt]> python3 script0.py win
    Attempting to load: ./lib/isengrad_win_064.dll
    DLL Loaded
    Python 3.6.8 (default, Feb 14 2019, 22:09:48) [GCC 7.4.0] 64bit on cygwin
    
    isengrad returned 23
    
    Done.
    [064bit prompt]>
    [064bit prompt]> python3 script1.py win
    Attempting to load: ./lib/isengrad_win_064.dll
    DLL Loaded
    Python 3.6.8 (default, Feb 14 2019, 22:09:48) [GCC 7.4.0] 64bit on cygwin
    
    
    Done.
    
  • 命令:

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q056855348]> sopr.bat
    *** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
    
    [prompt]> dir /b lib
    
    [prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
    **********************************************************************
    ** Visual Studio 2017 Developer Command Prompt v15.9.14
    ** Copyright (c) 2017 Microsoft Corporation
    **********************************************************************
    [vcvarsall.bat] Environment initialized for: 'x64'
    
    [prompt]> cl /nologo /DDLL isengrad.c  /link /NOLOGO /DLL /OUT:lib\isengrad_win_064.dll
    isengrad.c
       Creating library lib\isengrad_win_064.lib and object lib\isengrad_win_064.exp
    
    [prompt]>
    [prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x86
    **********************************************************************
    ** Visual Studio 2017 Developer Command Prompt v15.9.14
    ** Copyright (c) 2017 Microsoft Corporation
    **********************************************************************
    [vcvarsall.bat] Environment initialized for: 'x86'
    
    [prompt]> cl /nologo /DDLL isengrad.c  /link /NOLOGO /DLL /OUT:lib\isengrad_win_032.dll
    isengrad.c
       Creating library lib\isengrad_win_032.lib and object lib\isengrad_win_032.exp
    
    [prompt]> dir /b lib\*.dll
    isengrad_cyg_032.dll
    isengrad_cyg_064.dll
    isengrad_win_032.dll
    isengrad_win_064.dll
    
    [prompt]> set _PATH=%PATH%
    
    [prompt]> :: Python 32bit
    [prompt]> set PATH=%_PATH%;e:\Install\x86\Cygwin\Cygwin\Version\bin
    
    [prompt]> "e:\Work\Dev\VEnvs\py_032_03.07.03_test0\Scripts\python.exe" script0.py win
    Attempting to load: ./lib/isengrad_win_032.dll
    DLL Loaded
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)] 32bit on win32
    
    isengrad returned 23
    
    Done.
    
    [prompt]> "e:\Work\Dev\VEnvs\py_032_03.07.03_test0\Scripts\python.exe" script1.py win
    Attempting to load: ./lib/isengrad_win_032.dll
    DLL Loaded
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)] 32bit on win32
    
    
    Done.
    
    [prompt]> "e:\Work\Dev\VEnvs\py_032_03.07.03_test0\Scripts\python.exe" script0.py cyg
    Attempting to load: ./lib/isengrad_cyg_032.dll
    DLL Loaded
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)] 32bit on win32
    
    isengrad returned 23
    
    Done.
    
    [prompt]> "e:\Work\Dev\VEnvs\py_032_03.07.03_test0\Scripts\python.exe" script1.py cyg
    Attempting to load: ./lib/isengrad_cyg_032.dll
    DLL Loaded
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)] 32bit on win32
    
    
    Done.
    
    [prompt]> :: Python 64bit
    [prompt]> set PATH=%_PATH%;c:\Install\x64\Cygwin\Cygwin\AllVers\bin
    
    [prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" script0.py win
    Attempting to load: ./lib/isengrad_win_064.dll
    DLL Loaded
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    isengrad returned 23
    
    Done.
    
    [prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" script1.py win
    Attempting to load: ./lib/isengrad_win_064.dll
    DLL Loaded
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    
    Done.
    
    [prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" script0.py cyg
    Attempting to load: ./lib/isengrad_cyg_064.dll
    DLL Loaded
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    isengrad returned 23
    
    Done.
    
    [prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" script1.py cyg
    Attempting to load: ./lib/isengrad_cyg_064.dll
    
    [prompt]>
    [prompt]> echo %errorlevel%
    -1073741819
    

正如所见,交叉编译器 .exe - .dll 在 7 种(共 8 种)情况下工作(在 64 位 上崩溃 Win Python with script1.py),而同一个编译器在所有 8 个中都能正常工作。

因此,我建议在使用此类环境时,尽量使用于构建各个部分的编译器保持一致(或至少兼容)。



更新#0

我只是想到了在 64bit 上可能出错的一个原因:sizeof(long) 通常不同(以下大小以字节为单位):

  • 4 Win
  • 8Cygwin 上(通常在 Nix 上)

sizeof(long double) 也是一样(即 2 * sizeof(long))。

因此,如果 Cygwin .dll 公开了一些大于 2 ** 64long 值> (1 << 64),在Win 进程中会被截断,此时可能会发生崩溃.从理论上讲,这种情况也应该影响相反的情况,但事实并非如此。

还有其他因素可能会导致此行为,例如默认内存对齐等。

关于python - Paradoxon : silent crash on Python's ctypes. CDLL 在导入时出现,但在直接运行时却没有 - 这怎么可能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56855348/

相关文章:

python - Ctypes 优缺点

windows - 在 python 2.7 ctypes 中构建 UCS4 字符串缓冲区

python - NLTK WordNet 词形还原器 : Shouldn't it lemmatize all inflections of a word?

python - 导入 matplotlib.pyplot 挂起

python - 导入错误 : No module named statsmodels. 接口(interface)

python - 在 Docker 容器中导入 python 文件

python - 如何从 ctype 结构构建 python 字符串?

python - multiprocessing.Process - 为什么 .start() 方法在 IDLE 控制台中不起作用?

python - 当休息请求到达 flask 时如何更改元素的颜色(在 html 页面中)?

python - 如何在 Python 中导入 r 包