Python调用dll库接口-ctypes方法

\color{green}{文章付费是对Copyleft精神的亵渎,阅后点赞、关注才是对作者最大的奖赏!---Devil}


背景

 最近需要用python写个脚本程序(win10 环境),需要调用现成的dll库完成这项任务,对于一直在Linux平台上开发程序的本人来说,从没有过使用dll的经历(不得不说还是so大法好),遇到了一些坎坷。最终搞定,记录一下,获取能对其他同样经历的人有所帮助。

制作dll库

 程序要win10下运行,不多说,直接安装VS2019,多年未见,不得不说vs2019果真不错,安装可以根据自己的需要做的很小。比起之前的vs IDE,运行速度貌似有一些提升(纯感觉,未实证)。对于习惯了用SourceInsight阅读源码的本人来说,对以前vs启动要半天,调试过程非常卡顿的坏印象根深蒂固。vs2019的一上手有种异常的顺畅感,难道仅因为最近升级了i7的PC?

 前面都是废话,首先上来【创建新项目】,选择【Windows桌面向导】,【创建】,应用程序选择【动态链接库(.dll)】,空项目,然后确定即可。具体参考如下图,是不是一样这么做?当然不是,但如果自己不这么配置选择,比如创建了新的空项目,那意味着自己写完程序又要做一些乱七八糟的配置,非常让人心烦。吐糟一下,为什么就不能像linux一样,把make的过程不加修饰的交给开发呢....

创建dll工程

创建代码

 本次测试,将涉及无参数函数、基础类型参数函数、结构体类型参数、回调函数。这些是C/C++基本的函数形态,本次脚本任务也确实需要这些调用,因此一并进行了测试。So,我们的动态库需要导出这些函数供我们使用python调用。

1.创建pycall.h头文件

 首先,创建新的.h文件,并将需要的函数声明为导出,由于python的ctypes只能识别C风格代码,因此导出函数应该以extern "C"来声明导出函数为C风格。头文件为:

pycall.h
#pragma once

struct stuTest {
    char* name;
    int age;
    char address[32];
};

typedef void  (*callbakc) (int a, int b);
extern "C"
{
    __declspec(dllexport) int __stdcall printTest(int a); //int参数
    __declspec(dllexport) void __stdcall setPointContent(char* b);//char*引用
    __declspec(dllexport) int __stdcall getStrLen(const char* str);//char*指针
    __declspec(dllexport) int __stdcall struckParamTest(stuTest* struck_pointer);//传结构体指针
    __declspec(dllexport) void __stdcall testCallback(int a, callbakc func);//回调函数
}

__declspec(dllexport)声明该函数为导出函数,只有导出函数可以被调用
__stdcall 声明传参方式,一般跨语言的调用尽量使用std,具体扩展知识网上查寻即可

2.创建pycall.cpp文件
#include <stdio.h>
#include"pycall.h"
#include <stdlib.h>
#include <string.h>
#include <string>
#include <iostream>
#pragma warning(disable:4996) //允许使用strcpy

using namespace std;

int __stdcall printTest(int a)
{
    FILE* stream;
    char tmp[128];
    memset(tmp,0,sizeof(tmp));
    errno_t err = fopen_s(&stream, "D:\\test.txt", "w+");
        
    snprintf(tmp,sizeof(tmp),"haha:%d\n",a);
    fprintf(stream,"%s", tmp);
    fputs("测试函数fputs输出... \n", stream);
    fclose(stream);
    printf("hello world!-%d-\n", a);
    return a + 10;
}

void __stdcall setPointContent(char* b)
{
    strcpy(b,"hello world!");
}

int __stdcall getStrLen(const char* str)
{
    std::string tmp(str);
    return tmp.length();
}


int __stdcall struckParamTest(stuTest* struck_pointer)
{
    char* str1 = new char[20];
    strcpy(str1, "name");
    strcpy(struck_pointer->address,"hehe");
    struck_pointer->age = 33;
    struck_pointer->name = str1;
    return 0;
}

 
void __stdcall testCallback(int a, callbakc func)
{
    if(a == 1)
        func(20,30);
    else if (a == 2) {
        for(int i = 0 ;i < 20;i++)
            func(20+i, 30+i);
    }

}

生成dll库文件

 VS2019上操作【生成】->【生成解决方案】,生成dll库文件。但这里有一点要非常注意:如果python安装的是32位的,那么生成dll时应该配置为x86;如果python安装的是64位的,那么生成的dll应该配置为x64,以生成64位的dll文件。
一定要注意位数的匹配,如果python是64位的,dll是32位的,会报错:非法的32位win程序

Python中调用dll中的函数

 python调用dll中的函数,本次使用ctypes模块来完成。这其中还有比较多的知识,简单介绍一下:
ctypes中的数据类型和C类型以及python类型均有对应关系,当调用一个C函数时,根据C函数的类型,调用ctypes方法时候需要匹配ctype的类型。例如:
当调用C函数char* fuc(int a)时,加载库得到pdll,python中调用时,则应该这么做:
pdll.func.argtypes = [c_int]
pdll.func.restype = c_char_p
pdll.func(10);
但对于基本类型,ctypes会尝试做类型转换,转换失败抛出异常ArgumentError.详细内容可以参见python手册的ctypes章节。类型之间的转换关系表简单参考:

ctypes type C type Python type
c_bool _Bool bool (1)
c_char char 1-character bytes object
c_wchar wchar_t 1-character string
c_byte char int
c_ubyte unsigned char int
c_short short int
c_ushort unsigned short int
c_int int int
c_uint unsigned int int
c_long long int
c_ulong unsigned long int
c_longlong __int64 or long long int
c_ulonglong unsigned __int64 or unsigned long long int
c_size_t size_t int
c_ssize_t ssize_t or Py_ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (NUL terminated) bytes object or None
c_wchar_p wchar_t * (NULterminated) string or None
c_void_p void * int or None
python测试脚本程序
# -*- coding: utf-8 -*-

from ctypes import *
pDll = windll.LoadLibrary("mydll.dll")

#传数字
printTnull = pDll.printTest
print(printTnull(28))

#传字符串
pStr = c_char_p(b"abcdef")
print(pDll.getStrLen(pStr))

#传字符串数组
pointSet = pDll.setPointContent
out = create_string_buffer(1024,0);
pointSet(out)
print(out.value)


#结构体引用
class PyStruct(Structure):
            _fields_=[("name",c_char_p),("age",c_int),("address",c_char*32)]

ptS1 = PyStruct()
ptS1.name = b"lwz"
ptS1.age = 20
ptS1.address = b"hangzhou"


pyS1_cstruct = byref(ptS1)
print(pDll.struckParamTest(pyS1_cstruct))
print(ptS1.age)
print(ptS1.address)
print(ptS1.name)

#回调函数
def callFuc(a,b):
            print("value is:",a,b)
            
# ctypes通过 CFUNCTYPE 支持回调函数,定义返回值与参数,第一个参数表示返回值,void为None,第二参数为回调函数的参数为结构体指针
CALLBACK = CFUNCTYPE(None,c_int,c_int)
# 定义回调函数
#callBackFunc = CALLBACK(callFuc)
pDll.testCallback(2,CALLBACK(callFuc))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容