Python3調(diào)用C程序(超詳解)

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:

file

file

單擊頭文件,新建項:

file

添加源文件:

file

在頭文件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)用)這是一個很隱蔽的坑?。?!

file

在生成的動態(tài)庫路徑下找到test.dll文件,并復制到python項目下

file
file

最后在python中運行代碼,出現(xiàn)如下問題:

file

為了尋找問題,我將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é)果
file

以上流程我是參考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é)果
file

使用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)用
file
file

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

file
file
file
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容