Python3調(diào)用C程序(超詳解)
Python為什么要調(diào)用C丢氢?
1.要提高代碼的運算速度扔字,C比Python快50倍以上
2.對于C語言里很多傳統(tǒng)類庫变抽,不想用Python重寫哀澈,想對從內(nèi)存到文件接口這樣的底層資源進(jìn)行訪問
Python調(diào)用C的方法:
Python調(diào)用C的方法通常有3種:
1.SWIG,編寫一個額外的接口文件來作為SWIG(終端工具)的入口
2.通過CTypes調(diào)用
3.使用Python/C API方法
第一種方法大多數(shù)情況下會帶來不必要的麻煩卖词,我并沒有試驗,本文只針對2吏夯,3方法作詳細(xì)說明
通過CTypes調(diào)用:
Python中的ctypes模塊可能是Python調(diào)用C方法中最簡單的一種此蜈。ctypes模塊提供了和C語言兼容的數(shù)據(jù)類型和函數(shù)來加載dll文件即横,因此在調(diào)用時不需對源文件做任何的修改。也正是如此奠定了這種方法的簡單性裆赵。
下面是python文件的代碼:
from ctypes import * #pip ctypes庫令境,并導(dǎo)入庫
test = CDLL("./test.dll") #調(diào)用當(dāng)前目錄下叫test的dll文件,dll文件是C生成的動態(tài)鏈接庫
result =test.sum(5,10) #調(diào)用庫里的函數(shù)sum顾瞪,求和函數(shù)
print(result) #打印結(jié)果
接下來用C語言編寫dll動態(tài)鏈接庫舔庶,這里使用VS:
單擊頭文件,新建項:
添加源文件:
在頭文件test.h中加入如下代碼:
#pragma once
#ifdef BUILD_TEST
#define API_SYMBOL __declspec(dllexport)
#else
#define API_SYMBOL __declspec(dllimport)
#endif
//宏定義陈醒,導(dǎo)出或者導(dǎo)入//
extern "C" API_SYMBOL int sum(int x, int y);
//導(dǎo)出函數(shù)//
在源文件test.cpp中加入如下代碼:
#define BUILD_TEST //使導(dǎo)出函數(shù)生效
#include "test.h"
#include "stdio.h"
#include "pch.h"
int sum(int a, int b) {
return a + b;
}
注意惕橙,這里要點擊64位,再點擊生成(因為目前大部分電腦安裝Python解釋器是64位的钉跷,否則默認(rèn)生成32位的動態(tài)庫弥鹦,會導(dǎo)致無法調(diào)用)這是一個很隱蔽的坑!R蕖彬坏!
在生成的動態(tài)庫路徑下找到test.dll文件,并復(fù)制到python項目下
最后在python中運行代碼膝晾,出現(xiàn)如下問題:
為了尋找問題栓始,我將python文件中的代碼替換如下,發(fā)現(xiàn)調(diào)用動態(tài)庫是成功的血当,只是不能調(diào)用動態(tài)庫里面的函數(shù)
from ctypes import *
from ctypes import * #pip ctypes庫幻赚,并導(dǎo)入庫
test = CDLL("./test.dll") #調(diào)用當(dāng)前目錄下叫test的dll文件,dll文件是C生成的動態(tài)鏈接庫
print("加載成功")
# result =test.sum(5,10) #調(diào)用庫里的函數(shù)sum臊旭,求和函數(shù)
# print(result) #打印結(jié)果
以上流程我是參考B站一位UP主的具體教學(xué)落恼,但還是行不通,于是我將test.cpp源文件中的代碼更改如下:
#define BUILD_TEST
#include "test.h"
#include "stdio.h"
#include "pch.h"
#define DLLEXPORT extern "C" __declspec(dllexport) //直接在源文件定義導(dǎo)出
DLLEXPORT int sum(int a, int b) {
return a + b;
}//兩數(shù)相加
重復(fù)上述流程离熏,生成dll文件佳谦,將文件放置于python項目中,然后調(diào)用滋戳,終于成功
from ctypes import *
from ctypes import * #pip ctypes庫钻蔑,并導(dǎo)入庫
test = CDLL("./test.dll") #調(diào)用當(dāng)前目錄下叫test的dll文件,dll文件是C生成的動態(tài)鏈接庫
print("加載成功")
result =test.sum(5,10) #調(diào)用庫里的函數(shù)sum胧瓜,求和函數(shù)
print(result) #打印結(jié)果
使用Python/C API方法:
Python/C API可能是被最廣泛使用的方法矢棚。它不僅簡單,而且可以在C代碼中操作你的Python對象府喳。但要使用這種方法蒲肋,需要用特定的方式來編寫C代碼,所以C代碼不是原生的C(大伙要適應(yīng)一下),這樣才可以供python去調(diào)用兜粘。這里參照python進(jìn)階的說明和博客園的一篇文章
python文件的代碼如下:
import Test
x = 1
print(Test.add_one(x))
而這里的Test模塊申窘,則是需要我們自己用C語言寫,C文件代碼如下:
#include <Python.h>
int add_one(int a){
return a + 1;
}//這是C的原生函數(shù)孔轴,實現(xiàn)+1功能
static PyObject * py_add_one(PyObject *self, PyObject *args){
int num;
if (!PyArg_ParseTuple(args, "i", &num)) return NULL;
return PyLong_FromLong(add_one(num));
}//這一串代碼要實現(xiàn)的功能如下剃法,按照python規(guī)定的調(diào)用方式:
//1.定義一個新的靜態(tài)函數(shù),接收2個PyObject *參數(shù)路鹰,返回1個PyObject *值
//2.PyArg_ParseTuple方法將python輸入的變量變成C的變量贷洲,即上述args→num
//3.緊接著調(diào)用C原生函數(shù)add_one,傳入num
//4.最后將調(diào)用返回的C變量晋柱,轉(zhuǎn)換為PyObject*或其子類优构,并通過PyLong_FromLong方法,返回python值
static PyMethodDef Methods[] = {
{"add_one", py_add_one, METH_VARARGS},
{NULL, NULL}
};//這串代碼的目的是創(chuàng)建一個數(shù)組雁竞,來指明Python可以調(diào)用這個擴(kuò)展函數(shù):
//其中"add_one"钦椭,代表編譯后python調(diào)用時希望使用的函數(shù)名。而py_add_one碑诉,代表要調(diào)用當(dāng)前C代碼中的哪一個函數(shù)彪腔,即static PyObject * py_add_one。METH_VARARGS进栽,代表函數(shù)的參數(shù)傳遞形式德挣,主要包括位置參數(shù)和關(guān)鍵字參數(shù)兩種
//最后如果希望添加新的函數(shù),則在最后的{NULL, NULL}里按同樣格式填寫新的調(diào)用信息泪幌,比如加一些求和求乘積函數(shù)
static struct PyModuleDef cModule = {
PyModuleDef_HEAD_INIT,
"Test", /*模塊名盲厌,即是生成模塊后的名字,如numpy,opencv等等*/
"", /* 模塊文檔祸泪,可以為空NULL */
-1, /* 模塊中每個解釋器的狀態(tài)大小,模塊在全局變量中保持狀態(tài)建芙,則為-1 */
Methods//即上一步定義的Methods没隘,說明了可調(diào)用的函數(shù)集
};//創(chuàng)建module的信息
PyMODINIT_FUNC PyInit_Test(void){ return PyModule_Create(&cModule);}
//module初始化
將C文件放置在python項目的同文件目錄下,然后編寫setup.py文件禁荸,setup.py是用來將C打包成模塊的腳本文件右蒲,代碼如下:
from distutils.core import setup, Extension #這里要用到distutils庫
module1 = Extension('Test', sources = ['test.c']) #打包文件為test.c,這里沒有設(shè)置路徑赶熟,所有.c文件和setup.py放在同一目錄下
setup (name = 'Test', #打包名
version = '1.0', #版本
description = 'This is a demo package', #說明文字
ext_modules = [module1])
然后在pycharm的Terminal終端輸入(因為這里用的python3.5+瑰妄,windows平臺下python的C/C++擴(kuò)展不再支持gcc的編譯,并強(qiáng)制要求使用msvc進(jìn)行編譯映砖,如果版本低于3.5间坐,用python setup.py build)這是一個隱藏的坑!!竹宋!用python setup.py build一直出問題:
python setup.py build --compiler msvc #編譯代碼
python setup.py build --compiler msvc install #編譯代碼并直接將包放入當(dāng)前python環(huán)境的包的路徑以供調(diào)用
最后結(jié)果劳澄,順利打包,我們在python里安裝了自己的包并完成調(diào)用: