You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

加载PyQt等第三方模块后,使用ctypesgen生成绑定的含std::recursive_mutex的C++库触发Python崩溃问题

加载PyQt等第三方模块后,使用ctypesgen生成绑定的含std::recursive_mutex的C++库触发Python崩溃问题

我在Windows 10环境下,尝试从Python脚本调用C++库入口点时遇到了奇怪的崩溃,已经把问题简化成了极简示例,具体情况如下:

我的C++代码实现

sdetests_lib_bind_python.h

#pragma once

#ifdef SDETESTS_LIB_BIND_PYTHON_EXPORTS
    /** Export symbols statement */
    #define SDETESTS_LIB_BIND_PYTHON __declspec(dllexport)
#else
    /** Import symbols statement */
    #define SDETESTS_LIB_BIND_PYTHON __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif

SDETESTS_LIB_BIND_PYTHON void func(int lock);


#ifdef __cplusplus
}
#endif

sdetests_lib_bind_python.cpp

#include "sdetests_lib_bind_python.h"

#include <iostream>
#include <mutex>

SDETESTS_LIB_BIND_PYTHON void func( int lock )
{
    if (lock!=0)
    {
        std::cout << "Creating mutex" << std::endl;
        std::recursive_mutex mutex;
        std::cout << "Created mutex" << std::endl;
        std::cout << "Locking mutex" << std::endl;
        std::lock_guard<std::recursive_mutex> lock_guard(mutex);
        std::cout << "Locked mutex" << std::endl;
    }
    std::cout << "Hello world" << std::endl;
}

我用Visual Studio 2022(MSVC 19.43.34808.0)将其编译为共享库,随后用ctypesgen生成绑定文件sdetests_lib_bind_python.py,用以下简单Python脚本调用时完全正常:

import sdetests_lib_bind_python

print("Calling func without lock")
sdetests_lib_bind_python.func(0)

print("Calling func with lock")
sdetests_lib_bind_python.func(1)

输出结果:

Calling func without lock
Hello world
Calling func with lock
Creating mutex
Created mutex
Locking mutex
Locked mutex
Hello world

但如果先加载PyQt模块,脚本修改为:

from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
print("Qt: v", QT_VERSION_STR, "\tPyQt: v", PYQT_VERSION_STR)

import sdetests_lib_bind_python

print("Calling func without lock")
sdetests_lib_bind_python.func(0)

print("Calling func with lock")
sdetests_lib_bind_python.func(1)

程序就会崩溃,输出:

Qt: v 5.15.2    PyQt: v 5.15.9
Calling func without lock
Hello world
Calling func with lock
Creating mutex
Created mutex
Locking mutex
Traceback (most recent call last):
  File "example_sdetests_lib_bind_python_def.py", line 10, in <module>
    sdetests_lib_bind_python.func(1)
OSError: exception: access violation reading 0x0000000000000000

问题特征与排查细节

这个崩溃有几个关键规律:

  • 仅当PyQt(或rclpy这类其他模块)先于我的C++库绑定被导入时触发;若把import sdetests_lib_bind_python放在脚本第一行,后续加载其他模块完全正常。
  • 旧版本编译器(MSVC 19.38.33135.0)编译时无此问题,是升级编译器后才出现的。
  • 仅当库中使用std::recursive_mutex时崩溃;仅包含<mutex>头文件无影响,但创建并锁定mutex会触发访问违例。
  • Debug模式编译的库不会出现崩溃。

我需要给外部用户提供该库,要求他们必须先导入我的库或升级第三方模块并不现实,因此急需找到根本解决办法。


根据建议,我检查了库加载顺序,当PyQt5在我的库之后加载(无崩溃)时,加载顺序片段如下:

'python.exe' (Win32) : Chargé 'C:\Python38\python.exe'. Les symboles ont été chargés.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\ntdll.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\kernel32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\KernelBase.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\ucrtbase.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\python38.dll'. Les symboles ont été chargés.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\shlwapi.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\msvcrt.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\ws2_32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\rpcrt4.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\advapi32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\sechost.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\bcrypt.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\vcruntime140.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\version.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
Le thread 27884 s'est arrêté avec le code 0 (0x0).
'python.exe' (Win32) : Chargé 'C:\Windows\System32\cryptsp.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\rsaenh.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\cryptbase.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\bcryptprimitives.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\python3.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\DLLs\_ctypes.pyd'. Les symboles ont été chargés.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\ole32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\DLLs\libffi-7.dll'. Le module a été généré sans symboles.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\combase.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\gdi32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\win32u.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\gdi32full.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\msvcp_win.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\user32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\oleaut32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\imm32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\DLLs\_bz2.pyd'. Les symboles ont été chargés.
'python.exe' (Win32) : Chargé 'C:\Python38\DLLs\_lzma.pyd'. Les symboles ont été chargés.
'python.exe' (Win32) : Chargé 'C:\dev\vobs_sde\build\sdetests_python_binding\win64\stg\Release\sdetests_lib_bind_python.dll'. Le module a été généré sans symboles.
'python.exe' (Win32) : Chargé 'C:\Python38\vcruntime140_1.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\msvcp140.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\Lib\site-packages\PyQt5\QtCore.pyd'. Le module a été généré sans symboles.
'python.exe' (Win32) : Chargé 'C:\Python38\Lib\site-packages\PyQt5\Qt5\bin\Qt5Core.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\shell32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\mpr.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\netapi32.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Windows\System32\userenv.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.
'python.exe' (Win32) : Chargé 'C:\Python38\Lib\site-packages\PyQt5\Qt5\bin\msvcp140_1.dll'. Chargement des symboles désactivé par le paramètre Include/Exclude.

可能的原因与解决思路

看起来这个问题大概率是MSVC运行时库的版本冲突或加载顺序导致的。新版本MSVC对标准库组件的实现做了调整,而PyQt这类第三方模块可能自带旧版本的MSVC运行时库文件,当它们先被加载时,你的C++库会绑定到这些旧版本的运行时,而std::recursive_mutex的实现在新旧版本间不兼容,最终触发访问违例。

给你几个可以尝试的解决方向:

  1. 强制库使用静态链接的MSVC运行时
    在Visual Studio项目设置里,把C/C++ -> 代码生成 -> 运行库改成多线程(/MT)(Release模式)或多线程调试(/MTd)(Debug模式)。这样你的库会把运行时库打包到自身,不会和其他模块的运行时冲突。不过静态链接会增大库体积,若你的库还要和其他动态链接组件交互,需额外考虑兼容性。

  2. 替换std::recursive_mutex为Windows原生互斥量
    既然问题出在标准库的mutex上,可以用Windows API的CreateMutexW、WaitForSingleObject、ReleaseMutex实现递归互斥逻辑,绕开标准库的实现:

    #include <windows.h>
    
    SDETESTS_LIB_BIND_PYTHON void func( int lock )
    {
        if (lock!=0)
        {
            std::cout << "Creating mutex" << std::endl;
            HANDLE mutex = CreateMutexW(NULL, FALSE, NULL);
            if (mutex == NULL) {
                std::cerr << "Failed to create mutex" << std::endl;
                return;
            }
            std::cout << "Created mutex" << std::endl;
            std::cout << "Locking mutex" << std::endl;
            DWORD waitResult = WaitForSingleObject(mutex, INFINITE);
            if (waitResult != WAIT_OBJECT_0) {
                std::cerr << "Failed to lock mutex" << std::endl;
                CloseHandle(mutex);
                return;
            }
            std::cout << "Locked mutex" << std::endl;
    
            // 原有业务逻辑
    
            ReleaseMutex(mutex);
            CloseHandle(mutex);
        }
        std::cout << "Hello world" << std::endl;
    }
    

    这样完全不依赖标准库的mutex实现,也就不会有版本冲突问题。

  3. 显式指定运行时库的加载优先级(应急方案)
    可以在Python脚本里先显式加载你库依赖的MSVC运行时DLL,再加载其他模块,但这个方法比较hack,需确认具体依赖的运行时文件:

    import ctypes
    # 加载库依赖的运行时DLL
    ctypes.CDLL("vcruntime140_1.dll")
    ctypes.CDLL("msvcp140.dll")
    
    # 再加载PyQt等模块
    from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR
    print("Qt: v", QT_VERSION_STR, "\tPyQt: v", PYQT_VERSION_STR)
    
    import sdetests_lib_bind_python
    # 后续调用逻辑
    

另外你提到Debug模式没问题,是因为Debug模式的运行时库(如vcruntime140d.dll)和Release模式是分开的,PyQt这类模块一般不会携带Debug版本的运行时,所以不会冲突。

优先试试静态链接运行时的方案,对用户完全透明,不需要他们做任何额外操作。

备注:内容来源于stack exchange,提问作者jpo38

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 18:28:00