Python 调用 C++ 动态链接库(ctypes)

该文章已生成可运行项目,

通过 ctypes 调用 C++的 DLL 函数。


一、C++ 头文件

C++ 头文件(HelloWorld.h)

#ifndef HELLO_WORLD_H
#define HELLO_WORLD_H

// 导出 HelloWorld 函数
extern "C" __declspec(dllexport) const char* HelloWorld(const char* str1, const char* str2);

// 导出 Add 函数
extern "C" __declspec(dllexport) int Add(int a, int b);

#endif // HELLO_WORLD_H#pragma once

一定要使用 extern "C",否则函数名会被 C++ 编译器“名称修饰”(mangling),Python 无法识别。

实现文件:

// HelloWorld.cpp
#include "pch.h" // 如果 VS 生成了预编译头文件
#include "HelloWorld.h" // 引入头文件
#include <iostream>
#include <string>

extern "C" __declspec(dllexport) const char* HelloWorld(const char* str1, const char* str2) {
    static std::string result; // 使用静态变量存储返回值,确保返回的指针有效
    result = std::string(str1) + "," + std::string(str2);
    return result.c_str(); // 返回拼接后的 C 字符串
}

// 一个简单的加法函数
extern "C" __declspec(dllexport) int Add(int a, int b) {
    return a + b;
}

二、Python 使用 ctypes 的封装类

Python 封装代码:

  • 单例模式,避免重复加载
  • 参数类型校验、异常封装
  • 编码转换(str -> char*)
  • 错误处理
import ctypes
import os
import threading


class DllLoadError(Exception):
    """自定义异常:DLL 加载失败"""
    pass


class DllCallError(Exception):
    """自定义异常:DLL 调用失败"""
    pass


class HelloWorldDllWrapper:
    _lock = threading.Lock()
    _instance = None

    def __new__(cls, dll_path=None):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
                cls._instance._init_dll(dll_path)
            return cls._instance

    def _init_dll(self, dll_path):
        if dll_path is None:
            dll_path = "E:\\vsproject\\HelloWorld\\x64\\Release\\HelloWorld.dll"

        if not os.path.exists(dll_path):
            raise DllLoadError(f"DLL 文件不存在:{dll_path}")

        try:
            self._dll = ctypes.CDLL(dll_path)

            # 函数声明
            self._dll.HelloWorld.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
            self._dll.HelloWorld.restype = ctypes.c_char_p

            self._dll.Add.argtypes = [ctypes.c_int, ctypes.c_int]
            self._dll.Add.restype = ctypes.c_int

        except Exception as e:
            raise DllLoadError(f"DLL 加载失败:{e}")

    def say_hello(self, str1: str, str2: str) -> str:
        try:
            b1 = str1.encode("utf-8")
            b2 = str2.encode("utf-8")
            result_ptr = self._dll.HelloWorld(b1, b2)
            if not result_ptr:
                raise DllCallError("HelloWorld 返回空指针")
            return result_ptr.decode("utf-8")
        except Exception as e:
            raise DllCallError(f"调用 say_hello 失败:{e}")

    def add(self, a: int, b: int) -> int:
        try:
            return self._dll.Add(a, b)
        except Exception as e:
            raise DllCallError(f"调用 add 失败:{e}")

三、使用示例

if __name__ == "__main__":
    try:
        dll = HelloWorldDllWrapper()

        result1 = dll.say_hello("Hello", "World")
        print("HelloWorld 返回结果:", result1)

        result2 = dll.add(52, 23)
        print("Add 返回结果:", result2)

    except (DllLoadError, DllCallError) as e:
        print("错误:", e)

输出:

HelloWorld 返回结果: Hello,World
Add 返回结果: 75

四、适用场景

  • 已有 C++ 动态库,快速与 Python 集成。
  • 跨语言调用需求,如硬件接口、图像处理库(OpenCV/FFmpeg)等。
  • DLL 接口简单稳定,不含复杂结构体或模板。
  • 性能要求中等偏低,函数调用频率不是极高。

五、注意事项

1. 位数必须一致(32 位 vs 64 位)

  • Python 是 64 位,DLL 也必须是 64 位(或都为 32 位)。

确认 Python 位数:

python -c "import platform; print(platform.architecture())"

2. 字符串需要编码/解码

  • Python str.encode('utf-8') → C 的 char*
  • 返回的 char*.decode('utf-8') 转为 Python 字符串。

3. DLL 中返回的字符串应是 静态内存

  • 不能返回局部变量地址(会悬空)。
  • 推荐使用 static std::string 或手动分配内存。

4. 函数签名必须正确

  • argtypesrestype 是类型安全保障。
  • 错误签名可能导致内存崩溃或类型错误。

5. 需要有异常处理

  • 调用 DLL 接口时建议封装异常,方便排查。

ctypes 是 Python 调用 C/C++ 动态库最直接有效的方式之一,适合已有C/C++动态库、快速集成的场景。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值