Python3調(diào)用C程序(超詳解)
Python為什么要調(diào)用C?
1.要提高代碼的運算速度,C比Python快50倍以上
2.對于C語言里很多傳統(tǒng)類庫,不想用Python重寫,想對從內(nèi)存到文件接口這樣的底層資源進行訪問
Python調(diào)用C的方法:
Python調(diào)用C的方法通常有3種:
1.SWIG,編寫一個額外的接口文件來作為SWIG(終端工具)的入口
2.通過CTypes調(diào)用
3.使用Python/C API方法
第一種方法大多數(shù)情況下會帶來不必要的麻煩,我并沒有試驗,本文只針對2,3方法作詳細說明
通過CTypes調(diào)用:
Python中的ctypes模塊可能是Python調(diào)用C方法中最簡單的一種。ctypes模塊提供了和C語言兼容的數(shù)據(jù)類型和函數(shù)來加載dll文件,因此在調(diào)用時不需對源文件做任何的修改。也正是如此奠定了這種方法的簡單性。
下面是python文件的代碼:
from ctypes import * #pip ctypes庫,并導入庫
test = CDLL("./test.dll") #調(diào)用當前目錄下叫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
//宏定義,導出或者導入//
extern "C" API_SYMBOL int sum(int x, int y);
//導出函數(shù)//
在源文件test.cpp中加入如下代碼:
#define BUILD_TEST //使導出函數(shù)生效
#include "test.h"
#include "stdio.h"
#include "pch.h"
int sum(int a, int b) {
return a + b;
}
注意,這里要點擊64位,再點擊生成(因為目前大部分電腦安裝Python解釋器是64位的,否則默認生成32位的動態(tài)庫,會導致無法調(diào)用)這是一個很隱蔽的坑?。?!

在生成的動態(tài)庫路徑下找到test.dll文件,并復制到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庫,并導入庫
test = CDLL("./test.dll") #調(diào)用當前目錄下叫test的dll文件,dll文件是C生成的動態(tài)鏈接庫
print("加載成功")
# result =test.sum(5,10) #調(diào)用庫里的函數(shù)sum,求和函數(shù)
# print(result) #打印結(jié)果

以上流程我是參考B站一位UP主的具體教學,但還是行不通,于是我將test.cpp源文件中的代碼更改如下:
#define BUILD_TEST
#include "test.h"
#include "stdio.h"
#include "pch.h"
#define DLLEXPORT extern "C" __declspec(dllexport) //直接在源文件定義導出
DLLEXPORT int sum(int a, int b) {
return a + b;
}//兩數(shù)相加
重復上述流程,生成dll文件,將文件放置于python項目中,然后調(diào)用,終于成功
from ctypes import *
from ctypes import * #pip ctypes庫,并導入庫
test = CDLL("./test.dll") #調(diào)用當前目錄下叫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(大伙要適應一下),這樣才可以供python去調(diào)用。這里參照python進階的說明和博客園的一篇文章
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)用這個擴展函數(shù):
//其中"add_one",代表編譯后python調(diào)用時希望使用的函數(shù)名。而py_add_one,代表要調(diào)用當前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,這里沒有設置路徑,所有.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++擴展不再支持gcc的編譯,并強制要求使用msvc進行編譯,如果版本低于3.5,用python setup.py build)這是一個隱藏的坑?。?!用python setup.py build一直出問題:
python setup.py build --compiler msvc #編譯代碼
python setup.py build --compiler msvc install #編譯代碼并直接將包放入當前python環(huán)境的包的路徑以供調(diào)用


最后結(jié)果,順利打包,我們在python里安裝了自己的包并完成調(diào)用:


