悖论:导入时在Python的ctypes.CDLL上发生无提示崩溃,但在直接运行时却不崩溃-这怎么可能? [英] Paradoxon: silent crash on Python's ctypes.CDLL when importing, but not when running directly - how is this possible?

查看:60
本文介绍了悖论:导入时在Python的ctypes.CDLL上发生无提示崩溃,但在直接运行时却不崩溃-这怎么可能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

So, being a Linux guy I stumbled into something pretty puzzling on Windows that I just can't explain.

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

I have a project structure analougus to this example:

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加载共享库时退出。 code>,没有任何错误消息。

Problem: I compile isengrad.c into the shared libary isengrad.so, then load it in gondor.py. My aim is to import gondor.py into tolkien.py.
While gondor.py runs flawlessly when it is run directly, when I import it, the code exits at the point when I load the shared library via ctypes.CDLL, without any error messages.

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

Reproduction: The content of the files (added some "status messages" to follow where the problem happens):

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

Now check what happens when I use gondor.py directly VS when I import it in 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相关的东西。

Directly running it causes no problem at all. But importing it causes the whole thing to crash without any word and traceback when loading the shared library. How is this even happening? I even hard-coded the path to the shared library, so different working directory shouldn't be a problem... I didn't have any problem with the very same project on Kubuntu, so this is probably some Windows-related stuff.

环境:


  • Python: Python 3.7.3(默认值,2019年3月27日,17:13:21)[MSC v.1915 64位(AMD64)] ::在Win32上的Anaconda,Inc。

  • 操作系统: Windows 10 10.0.17134 Build 17134 (安装在C:上)

  • GCC:已安装通过Cygwin,版本7.4.0

  • 请询问是否需要其他详细信息。

  • Python: Python 3.7.3 (default, Mar 27 2019, 17:13:21) [MSC v.1915 64 bit (AMD64)] :: Anaconda, Inc. on win32
  • OS: Windows 10 10.0.17134 Build 17134 (installed on C:)
  • GCC: Installed via Cygwin, version 7.4.0
  • Please ask if any other details needed.

推荐答案

从我看到这个问题的那一刻起,我想说的是未定义行为 UB )。 Python 带有其 C 运行时( UCRTLib ),而 Cygwin .dll 带有自己的。在进程中混合使用编译器和 C 运行时通常是灾难的根源。
我找到了一份官方声明 [Cygwin]: 6.15。我可以同时链接MSVCRT * .DLL和cygwin1.dll吗?重点是我的):

From the moment I saw this question, I wanted to say it's Undefined Behavior (UB). Python comes with its C runtime (UCRTLib), while the Cygwin .dll comes with its own. Mixing compilers and C runtimes in a process, is generally a recipe for disaster.
I found an official statement [Cygwin]: 6.15. Can I link with both MSVCRT*.DLL and cygwin1.dll? (emphasis is mine):


不,您必须使用一个或另一个,它们是互斥的

检查 [SO]:如何避开Windows通用CRT标头对vcruntime.h的依赖(@CristiFati的回答),以获取有关 MSVCRT * .DLL

现在, UB 的优点在于它描述了看似随机的行为。

Now, the beauty of UB is that it describes a seemingly random behavior.

我已经准备了一个完整的示例(稍微修改您的代码)。

I've prepared a comprehensive example (slightly modifying your code).

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.")

脚本1。 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 32bit 64bit

    • Cygwin Mintty


      • 64bit

      • 32bit

      • I'll be using 3 windows:
        • cmd - Win (32bit and 64bit)
        • Cygwin's Mintty:
          • 64bit
          • 32bit

          • Cygwin 32bit


          [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.
          



        • cmd


          [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种)中起作用(在 64bit Win Python script1.py ),而同一个编译器在所有这8种工具中均能正常工作。

          As seen, cross compiler .exe - .dll worked in 7 (out of 8) cases (crashed on 64bit Win Python with script1.py), while the same compiler worked in all 8 of them.

          因此,我建议在这种环境下玩耍时,请尽量使用于构建各个部分的编译器保持一致(或兼容)。

          So, I'd advise that when playing with such environments, try to keep the compilers used to build various parts consistent (or compatible at least).


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

          I just thought of a reason why things could go wrong on 64bit: sizeof(long) generally differs (sizes below are in bytes):


          • 4 Win

          • 8 Cygwin (通常在 Nix 上)

          • 4 on Win
          • 8 on Cygwin (on Nix, in general)

          sizeof(long双) (这是 2 * sizeof(long))。

          因此,如果 Cygwin .dll 暴露的一些 long 值大于 2 ** 64 1< ;< 64 ),它将在 Win 进程中被截断,在这种情况下可能会发生崩溃。从理论上讲,这种情况也应该影响相反的情况,但是不会。

          So, if the Cygwin .dll exposes some long value greater than 2 ** 64 (1 << 64), it will be truncated in the Win process, and in this case a crash might occur. Theoretically, this situation should affect the reverse scenario as well, but it doesn't.

          还有其他因素可能导致此现象,例如默认的内存对齐和等等。

          There are other factors that might lead to this behavior, like default memory alignment, and so on.

          这篇关于悖论:导入时在Python的ctypes.CDLL上发生无提示崩溃,但在直接运行时却不崩溃-这怎么可能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆