2023-01-18 python ctypes 引用 dll 动态连接库调用C函数


老林的C语言新课, 想快速入门点此 <C 语言编程核心突破>


python ctypes 引用 dll 动态连接库调用C函数


前言

python是典型的胶水语言, 有优雅的代码, C语言是典型的底层语言, 有极强的效率, 所以当涉及运算密集型项目, 可以用 python + C 的形式解决.

python 有标准的 ctypes 模块, 专门进行与 C 或 C++ 封装的 dll 动态连接库, 使用不难, 但有些坑还是要避一避.


一、ctypes 是什么?

ctypes 是 python 的自带模块, 用于连接 dll 文件, 从而使得 python 利用 C 语言高性能计算的性质.

通过 CDLL 或 WinDLL连接dll文件: 当cdll为小写时需要用到LoadLibrary, 如果为大写CDLL或WinDLL则直接引用即可.

import ctypes

# 引入dll
lib = ctypes.cdll.LoadLibrary('./answer/test179.dll')

lib2 = ctypes.WinDLL('./answer/test179.dll')

ctypes 可以设置结构, 对应C语言中的结构:同时从ctypes有C语言对应的数据类型.

# 设置结构
class Test(ctypes.Structure):
    _fields_ = [('a', ctypes.c_int),
                ('b', ctypes.c_int),
                ('c', ctypes.c_char*10),
                ('d', ctypes.c_char*10)]

通过获取函数指针, 设置函数形参和返回类型进行函数调用:

# 获取函数指针
fun = lib.ConstructTest

# 设置形参类型
fun.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)
# 设置函数返回值
fun.restype = Test

# 测试函数
tst = fun(1, 1, ctypes.create_string_buffer(
    b'abc'), ctypes.create_string_buffer(b'bbb'))

二、ctypes 使用上的一些问题

对于字符串, python和C语言的差别还是不小, 对于中文, python是可以无障碍的输出, 但调用dll输出中文, 那就是灾难.

在Windows下, 用utf8编码的dll需要另辟蹊径.

print('中文') # 对于python没有问题

url = ctypes.create_string_buffer("中文.go.com".encode('gbk'))  # 不可以用.encode('utf-8')
username = '汉字'.encode('gbk')  # 不可用.encode('utf-8')

对于C的char数组, python使用的是 c_char * 5 这种形式.

在CSDN的问答中, 有个关于 ctypes 调用函数返回 WindowsError: exception: access violation reading 0x00000000

发现连接库, 设置函数传参等都没有问题, 在StackOverflow出现过类似问题, 发现是 32 位 python 调用 64 位 dll 文件, 可以想见, 传参和返回值基本都是错的,

因为在C语言, 32位系统的基本数据类型和64位系统的基本数类型占用内存不一致, int在32位系统一般位 2 字节, 而64位系统是4字节, 指针32位系统是4字节, 如错误中 0x00000000 明显是四字节, 但64位系统, 指针一般为8字节,

无论传入参数还是传出返回值, 根本就对不上号, 所以读取的内存完全不是一回事, 直接返回错误. 当然, C语言确实不以兼容性见长.

以下是代码, CPP文件以注释形式给出:

# test178.h
# #ifndef TEST
# #define TEST
# #include <cstdio>
#
# #ifdef BUILD_DLL
# #define DLL_EXPORT __declspec(dllexport)
# #else
# #define DLL_EXPORT __declspec(dllimport)
#
# #endif
#
# #ifdef __cplusplus
# extern "C"
# {
# #endif
#
#     typedef struct
#     {
#         int a;
#         int b;
#         char c[10];
#         char d[10];
#     } Test;
#
#     DLL_EXPORT auto ConstructTest(int aa, int bb, const char *cc,
#                                   const char *dd) -> Test;
#
#     DLL_EXPORT void print(Test tst);
#
# #ifdef __cplusplus
# }
# #endif
#
# #endif

# test179.cpp
# #define BUILD_DLL
#
# #include "test178.h"
# #include <cstring>
#
# DLL_EXPORT auto ConstructTest(int aa, int bb, const char *cc, const char *dd)
#     -> Test
# {
#     Test tst;
#     tst.a = aa;
#     tst.b = bb;
#     strcpy(tst.c, cc);
#     strcpy(tst.d, dd);
#     return tst;
# }
#
# DLL_EXPORT void print(Test tst)
# {
#     // printf("%d-%d-%s", tst.a, tst.b, tst.c);
#     printf("%d-%d-%s-%s\n", tst.a, tst.b, tst.c, tst.d);
# }
#
# // int main()
# //{
# //     print(ConstructTest(1, 1, "dsdjl"));
# //     return 0;
# // }

# 引入ctypes模块
import ctypes

# 引入dll
lib = ctypes.cdll.LoadLibrary('./answer/test179.dll')

lib2 = ctypes.WinDLL('./answer/test179.dll')


# 设置结构
class Test(ctypes.Structure):
    _fields_ = [('a', ctypes.c_int),
                ('b', ctypes.c_int),
                ('c', ctypes.c_char*10),
                ('d', ctypes.c_char*10)]


# 获取函数指针
fun = lib.ConstructTest

# 设置形参类型
fun.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)
# 设置函数返回值
fun.restype = Test

# 测试函数
tst = fun(1, 1, ctypes.create_string_buffer(
    b'abc'), ctypes.create_string_buffer(b'bbb'))

# 测试结构
tst2 = Test(1, 1, b'abc', b'aaa')

# 打印函数返回值
lib.print(tst)
# 打印结构返回值
lib.print(tst2)

tst3 = fun(1, 1, b'abc', b'bbb')
lib.print(tst3)


tst4 = fun(1, 1, '中文'.encode('gbk'), '汉字'.encode('gbk'))
lib.print(tst4)

Cpp代码编译参数:

 E:\msys64\clang64\bin\clang++.exe -shared -std=c++17 test179.cpp -o e:\clangC++\answer\C++\test179.dll 

总结

有人用 python + C 可以高效完成各种功能代码, 具备python的优雅, C的高效, 不过还是有些坑需要注意.


老林的C语言新课, 想快速入门点此 <C 语言编程核心突破>


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不停感叹的老林_<C 语言编程核心突破>

不打赏的人, 看完也学不会.

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值