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

如何在Python3中通过ctypes向C++ Qt5函数传递参数?

问题:Qt5 C++ DLL与Python ctypes交互时参数传递错误导致崩溃

平台信息

$ for ic in "$(cmd //c ver)" "uname -s" "python3 --version" "g++ --version"; do echo "$ic:" $(echo "$(${ic})" | head -1); done 2>/dev/null

Microsoft Windows [版本 10.0.19045.4355]:
uname -s: MINGW64_NT-10.0-19045
python3 --version: Python 3.11.9
g++ --version: g++.exe (Rev6, Built by MSYS2 project) 13.2.0

$ python3 -c 'from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR; print("Qt: v", QT_VERSION_STR, "\tPyQt: v", PYQT_VERSION_STR)'
Qt: v 5.15.12   PyQt: v 5.15.10

需求是构建Qt5的C共享库(.dll),在Python中加载调用,最终结合PyQt5使用。尝试用ctypes实现时,C代码可正常编译为.exe和.dll,但Python调用时崩溃,参数传递完全错误:

  • 传入的Python字符串无法正确映射为C++的QString&参数,出现空指针
  • Python的False被解析为C++中的18,参数栈错位

原代码与问题重现

C++代码(QtTestLib.cpp)

// compile with:
// for .exe:
// g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic QtTestLib.cpp -o QtTestLib.exe -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll
// for lib:
// g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic -shared QtTestLib.cpp -o QtTestLib.dll -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll

#ifdef _WIN32
#   define API extern "C" __declspec(dllexport)
#else
#   define API extern "C"
#endif

#ifndef UNICODE
#define UNICODE
#define UNICODE_WAS_UNDEFINED
#endif

#include <QStringList>
#include <iostream>

API QString myStringTesterFunc(const QString &inString, bool specProcess)
{
  QString ret_str  = "Result: ";
  ret_str.append(QString("%1").arg(inString));
  ret_str.append(QString("%1").arg(" - "));
  if (specProcess)
  {
    ret_str.append(QString("%1").arg("Special"));
  }
  else
  {
    ret_str.append(QString("%1").arg("Regular"));
  }
  return ret_str;
}

// tester:
int main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[])
{
  QString test_str  = "->Main tester<-";
  QString out_str = myStringTesterFunc(test_str, true);
  std::wcout << "Got: " << reinterpret_cast<const wchar_t *>(out_str.utf16()) << std::endl;;
}

编译与测试结果

编译为exe可正常运行:

$ g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic QtTestLib.cpp -o QtTestLib.exe -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll
$ ./QtTestLib.exe
Got: Result: ->Main tester<- - Special

编译.dll无报错:

$ g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic -shared QtTestLib.cpp -o QtTestLib.dll -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll

Python测试脚本(QtTestLib.py)

from ctypes import *
mylib = cdll.LoadLibrary("./QtTestLib.dll")
datastr = "Hello World from .py tester"
ret = mylib.myStringTesterFunc(datastr, False)
print(f"{ret=}")

调试信息

gdb调试显示参数传递完全错误:

$ gdb --args python3 QtTestLib.py
...
Thread 1 received signal SIGSEGV, Segmentation fault.
0x00007fff59fd6225 in ?? () from C:\msys64\mingw64\bin\Qt5Core.dll

(gdb) bt
#0  0x00007fff59fd6225 in ?? () from C:\msys64\mingw64\bin\Qt5Core.dll
#1  0x00007fff803413df in myStringTesterFunc (inString=..., specProcess=18)
    at C:/msys64/mingw64/include/QtCore/qchar.h:55
...

(gdb) frame 1
#1  0x00007fff803413df in myStringTesterFunc (inString=..., specProcess=18)
    at C:/msys64/mingw64/include/QtCore/qchar.h:55
55          Q_DECL_CONSTEXPR inline ushort unicode() const noexcept { return ushort(uchar(ch)); }

(gdb) p inString
$1 = (const QString &) <error reading variable: Cannot access memory at address 0x0>
(gdb) p specProcess
$2 = 18

解决方案

方案1:修改C++接口为C兼容类型(适配ctypes)

ctypes只能处理C标准类型,无法直接识别C的QString类。需要将导出接口改为C兼容的宽字符串类型,在C内部完成QString的转换:

修改后的C++代码

// compile with:
// for .exe:
// g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic QtTestLib.cpp -o QtTestLib.exe -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll
// for lib:
// g++ -std=gnu++11 -g -Wall -Wextra -O -pedantic -shared QtTestLib.cpp -o QtTestLib.dll -I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll

#ifdef _WIN32
#   define API extern "C" __declspec(dllexport)
#else
#   define API extern "C"
#endif

#ifndef UNICODE
#define UNICODE
#define UNICODE_WAS_UNDEFINED
#endif

#include <QString>
#include <iostream>
#include <cstdlib>
#include <cstring>

// 内部逻辑函数(保持原Qt处理逻辑)
QString myStringTesterFuncImpl(const QString &inString, bool specProcess)
{
  QString ret_str  = "Result: ";
  ret_str.append(QString("%1").arg(inString));
  ret_str.append(" - ");
  ret_str.append(specProcess ? "Special" : "Regular");
  return ret_str;
}

// 导出的C兼容接口
API wchar_t* myStringTesterFunc(const wchar_t* inStr, bool specProcess)
{
    if (!inStr) return nullptr;
    QString inString(inStr);
    QString result = myStringTesterFuncImpl(inString, specProcess);
    
    // 分配内存存储结果,由Python端释放
    const wchar_t* utf16 = result.utf16();
    size_t len = wcslen(utf16) + 1;
    wchar_t* ptr = (wchar_t*)malloc(len * sizeof(wchar_t));
    if (ptr) {
        wcscpy(ptr, utf16);
    }
    return ptr;
}

// 测试main函数
int main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[])
{
  QString test_str  = "->Main tester<-";
  QString out_str = myStringTesterFuncImpl(test_str, true);
  std::wcout << "Got: " << reinterpret_cast<const wchar_t *>(out_str.utf16()) << std::endl;;
}

修改后的Python脚本

from ctypes import *

# 加载DLL
mylib = cdll.LoadLibrary("./QtTestLib.dll")
# 加载系统库用于内存释放
libc = cdll.msvcrt

# 显式指定函数参数和返回类型
mylib.myStringTesterFunc.argtypes = [c_wchar_p, c_bool]
mylib.myStringTesterFunc.restype = POINTER(c_wchar)

datastr = "Hello World from .py tester"
ret_ptr = mylib.myStringTesterFunc(datastr, False)
# 转换为Python字符串
ret = wstring_at(ret_ptr)
print(f"{ret=}")
# 释放C++分配的内存
libc.free(ret_ptr)

方案2:使用PyBind11(更简便的C++/Python绑定方案)

如果不想手动处理类型转换,PyBind11可以直接绑定Qt类型,自动完成QString与Python字符串的转换:

步骤1:编写绑定代码(QtTestLib_bind.cpp)

#include <pybind11/pybind11.h>
#include <pybind11/qt.h>
#include <QString>

namespace py = pybind11;

QString myStringTesterFunc(const QString &inString, bool specProcess)
{
  QString ret_str  = "Result: ";
  ret_str.append(QString("%1").arg(inString));
  ret_str.append(" - ");
  ret_str.append(specProcess ? "Special" : "Regular");
  return ret_str;
}

PYBIND11_MODULE(QtTestLib, m) {
    m.def("myStringTesterFunc", &myStringTesterFunc, 
          py::arg("inString"), py::arg("specProcess"), 
          "测试Qt字符串处理函数");
}

步骤2:编译为Python扩展模块

g++ -std=c++11 -shared -fPIC `python3 -m pybind11 --includes` \
-I/mingw64/include/QtCore /mingw64/bin/Qt5Core.dll \
QtTestLib_bind.cpp -o QtTestLib.pyd

步骤3:Python中直接使用

import QtTestLib

datastr = "Hello World from .py tester"
ret = QtTestLib.myStringTesterFunc(datastr, False)
print(f"{ret=}")

错误原因说明

  1. QString类型不兼容:ctypes无法识别C++的QString类,直接传递Python字符串会被当做char*,但函数期望的是QString对象的引用,导致内存地址错误。
  2. bool参数栈错位:ctypes默认将Python bool转换为C的int(4字节),而C++的bool是1字节,调用约定不匹配导致参数栈错位,第二个参数值被错误解析。

内容的提问来源于stack exchange,提问作者sdbbs

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:23:10