• 正文
    • 一、生成的dll文件提供接口了嗎?
    • 二、使用C++擴(kuò)展python模塊,方便調(diào)用
    • ?三、使用python調(diào)用
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

C++開發(fā)實(shí)戰(zhàn)(三):通過python調(diào)用C++接口

01/08 16:10
3871
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

一、生成的dll文件提供接口了嗎?

1、上一篇文章,我們生成了dll文件,現(xiàn)在我們來使用看看,要調(diào)用的類如下

class AutoTest {
//private:
public:
//USB5538數(shù)據(jù)采集器
HANDLE createUSB5538();
void releaseUSB5538(HANDLE hDevice);
void resetUSB5538(HANDLE hDevice); ?//復(fù)位,相當(dāng)于與PC重連,等同于重新插上USB
void getUSB5538DI_All(HANDLE hDevice, BYTE bDISts[16]); ?//bDISts[16]為output參數(shù)
void setUSB5538DO_All(HANDLE hDevice, BYTE bDOSts[16]); ?//bDOSts[16]為input參數(shù)
int getUSB5538DI_One(HANDLE hDevice, int iDI);
void setUSB5538DO_One(HANDLE hDevice, int iDO, int value);
};

(1)如下圖,嘗試以python的方式對AutoTest類進(jìn)行調(diào)用,沒有成功

(2)嘗試直接調(diào)用其方法,還是沒能成功

(3)問題分析

dll文件并沒有提供接口,應(yīng)該是再編譯的時(shí)候沒暴露出來導(dǎo)致的??山鉀Q的方式是重新編譯暴露該接口的編譯文件或者直接編譯成python可調(diào)用的模塊(即擴(kuò)展模塊)。

二、使用C++擴(kuò)展python模塊,方便調(diào)用

1、擴(kuò)展python模塊流程實(shí)踐

(1)先創(chuàng)建新的工程,并添加c文件

#include <Python.h> 

static PyObject* uniqueCombinations(PyObject* self) 
{ 
    return Py_BuildValue("s", "uniqueCombinations() return value (is of type 'string')"); 
} 

static char uniqueCombinations_docs[] = 
    "usage: uniqueCombinations(lstSortableItems, comboSize)n"; 

/* deprecated: 
static PyMethodDef uniqueCombinations_funcs[] = { 
    {"uniqueCombinations", (PyCFunction)uniqueCombinations, 
    METH_NOARGS, uniqueCombinations_docs}, 
    {NULL} 
}; 
use instead of the above: */ 

static PyMethodDef module_methods[] = { 
    {"uniqueCombinations", (PyCFunction) uniqueCombinations, 
    METH_NOARGS, uniqueCombinations_docs}, 
    {NULL} 
}; 


/* deprecated : 
PyMODINIT_FUNC init_uniqueCombinations(void) 
{ 
    Py_InitModule3("uniqueCombinations", uniqueCombinations_funcs, 
        "Extension module uniqueCombinations v. 0.01"); 
} 
*/ 

static struct PyModuleDef Combinations = 
{ 
    PyModuleDef_HEAD_INIT, 
    "Combinations", /* name of module */ 
    "usage: Combinations.uniqueCombinations(lstSortableItems, comboSize)n", /* module documentation, may be NULL */ 
    -1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */ 
    module_methods 
}; 

PyMODINIT_FUNC PyInit_Combinations(void) 
{ 
    return PyModule_Create(&Combinations); 
} 

(2)報(bào)“沒有頭文件”的錯(cuò)誤與處理

已啟動生成…
1>------ 已啟動生成: 項(xiàng)目: USB5538_python, 配置: Release x64 ------
1>bird.cpp
1>D:MinGWprojectsUSB5538_pythonsourcebird.cpp(1,10): fatal error C1083: 無法打開包括文件: “Python.h”: No such file or directory
1>已完成生成項(xiàng)目“USB5538_python.vcxproj”的操作 - 失敗。
========== 生成: 成功 0 個(gè),失敗 1 個(gè),最新 0 個(gè),跳過 0 個(gè) ==========

(3)查看頭文件,應(yīng)該是存在該文件的

(4)我的python環(huán)境其實(shí)也是有該文件的

(5)將該目錄添加進(jìn)去,并成功解決了該問題。

(6)但是,無法打開文件“python39.lib”

(7)文件倒是存在

(8)將庫目錄添加,并成功解決該問題。

(9)又暴露了其他錯(cuò)誤(沒一個(gè)省心的。。。)

已啟動生成…
1>------ 已啟動生成: 項(xiàng)目: USB5538_python, 配置: Release x64 ------
1>  正在創(chuàng)建庫 D:MinGWprojectsUSB5538_pythonx64ReleaseUSB5538_python.lib 和對象 D:MinGWprojectsUSB5538_pythonx64ReleaseUSB5538_python.exp
1>MSVCRT.lib(exe_main.obj) : error LNK2001: 無法解析的外部符號 main
1>D:MinGWprojectsUSB5538_pythonx64ReleaseUSB5538_python.exe : fatal error LNK1120: 1 個(gè)無法解析的外部命令
1>已完成生成項(xiàng)目“USB5538_python.vcxproj”的操作 - 失敗。
========== 生成: 成功 0 個(gè),失敗 1 個(gè),最新 0 個(gè),跳過 0 個(gè) ==========

(10)在上述文件末尾添加入口函數(shù)即可:

int main()
{

}

(11)接著設(shè)置如下:

?三、使用python調(diào)用

1、通過上述操作,應(yīng)該能生成.pyd文件,那么,我們嘗試打包成python庫吧!

2、新建的文件內(nèi)容與操作:

recursive-include EE *.pyd

import setuptools
setuptools.setup(
    name='EE',
    version='1.0',
    description='this is a program for EE',
    author='',
    author_email='',
    packages=setuptools.find_packages(),
	include_package_data=True,
   )

3、在當(dāng)前目錄下,打開cmd,執(zhí)行打包指令

pip install wheel
python setup.py bdist_wheel

recursive-include Release *.pyd

4、繼續(xù)生成后,拿去安裝看看

如圖,安裝成功,但是導(dǎo)入模塊的時(shí)候,還是錯(cuò)誤,因?yàn)樵撃夸浵聸]有py文件。按理說,應(yīng)該會生成兩個(gè)文件夾,一個(gè)庫文件夾,一個(gè)詳細(xì)信息文件夾,現(xiàn)在只有后者!

5、嘗試解決該問題

(1)先查看編譯時(shí)的提示,感覺可能會有問題

已啟動生成…
1>------ 已啟動生成: 項(xiàng)目: USB5538_python, 配置: Release x64 ------
1>bird.cpp
1>  正在創(chuàng)建庫 D:MinGWprojectsUSB5538_pythonx64ReleaseUSB5538_python.lib 和對象 D:MinGWprojectsUSB5538_pythonx64ReleaseUSB5538_python.exp
1>正在生成代碼
1>Previous IPDB not found, fall back to full compilation.
1>All 2 functions were compiled because no usable IPDB/IOBJ from previous compilation was found.
1>已完成代碼的生成
1>USB5538_python.vcxproj -> D:MinGWprojectsUSB5538_pythonx64ReleaseUSB5538_python.pyd
========== 生成: 成功 1 個(gè),失敗 0 個(gè),最新 0 個(gè),跳過 0 個(gè) ==========

(2)優(yōu)化鏈路后,不再有上述提示,不過即使這樣,也沒能成功安裝模塊

(3)研究C++文件,查閱官方文檔

Python - Extension Programming with C

如官方文檔所說,已經(jīng)添加了Python.h文件

The Header File Python.h
You need include Python.h header file in your C source file, which gives you access to the internal Python API used to hook your module into the interpreter.

Make sure to include Python.h before any other headers you might need. You need to follow the includes with the functions you want to call from Python.

根據(jù)文檔的要求,檢查了對象規(guī)范,沒有問題

The C Functions
The signatures of the C implementation of your functions always takes one of the following three forms ?

static PyObject *MyFunction( PyObject *self, PyObject *args );

static PyObject *MyFunctionWithKeywords(PyObject *self,
                                 PyObject *args,
                                 PyObject *kw);

static PyObject *MyFunctionWithNoArgs( PyObject *self );

檢查了函數(shù)映射,沒有問題

The Method Mapping Table
This method table is a simple array of PyMethodDef structures. That structure looks something like this ?

struct PyMethodDef {
   char *ml_name;
   PyCFunction ml_meth;
   int ml_flags;
   char *ml_doc;
};

檢查了模塊名稱,也沒有問題

The Initialization Function
The last part of your extension module is the initialization function. This function is called by the Python interpreter when the module is loaded. It is required that the function be named initModule, where Module is the name of the module.

The initialization function needs to be exported from the library you will be building. The Python headers define PyMODINIT_FUNC to include the appropriate incantations for that to happen for the particular environment in which we're compiling. All you have to do is use it when defining the function.

Your C initialization function generally has the following overall structure ?

官方提供了另一種編譯方式:

(4)嘗試官方提供的程序,直接運(yùn)行看看,還沒運(yùn)行就已經(jīng)報(bào)錯(cuò)了

(5)尋找最新文檔繼續(xù)嘗試

1. Extending Python with C or C++ — Python 3.10.0 documentation

根據(jù)新的文檔,對源文件做了如下注釋和規(guī)范上的修改,然而沒啥用。

#define PY_SSIZE_T_CLEAN  // 建議在包含Python.h之前總是定義PY_SSIZE_T_CLEAN。
#include <Python.h>  // 打包成python擴(kuò)展所需要的頭文件

// 函數(shù)的三種實(shí)現(xiàn)方式,C函數(shù)通常是通過將Python模塊和函數(shù)名組合在一起命名的,如模塊是Combinations(也是.cpp名稱),函數(shù)是uniqueCombinations,組成了一個(gè)函數(shù)名
static PyObject* Combinations_uniqueCombinations(PyObject* self)  // 定義功能有三種方式,詳細(xì)文檔(舊文檔)請查閱https://www.tutorialspoint.com/python/python_further_extensions.htm
{
    return Py_BuildValue("s", "uniqueCombinations() return value (is of type 'string')");
};

/*
函數(shù)的三種實(shí)現(xiàn)方式例子:
static PyObject *MyFunction( PyObject *self, PyObject *args );  // METH_VARARGS

static PyObject *MyFunctionWithKeywords(PyObject *self,PyObject *args,PyObject *kw);  // METH_KEYWORDS

static PyObject *MyFunctionWithNoArgs( PyObject *self );  // METH_NOARGS
*/

static char uniqueCombinations_docs[] ="usage: uniqueCombinations(lstSortableItems, comboSize)n";  // 隨意定義的文檔字符串描述

// 方法映射表,方法表是一個(gè)簡單的PyMethodDef結(jié)構(gòu)數(shù)組
static PyMethodDef module_methods[] = {
    {"uniqueCombinations", (PyCFunction)Combinations_uniqueCombinations, METH_NOARGS, uniqueCombinations_docs},
    { NULL, NULL, 0, NULL }
    // 格式解釋:{ml_name,ml_meth,ml_flags,ml_doc}
    // ml_name:這是Python解釋器在Python程序中使用的函數(shù)名。
    // ml_meth:這必須是函數(shù)名。
    // ml_flags:函數(shù)的三種實(shí)現(xiàn)方式,上述例子中已經(jīng)標(biāo)明對應(yīng)字段選擇
    // ml_doc:可以為空值,四個(gè)選項(xiàng)對應(yīng)的空值分別為:{ NULL, NULL, 0, NULL }
};

// 模塊后面加module,比較規(guī)范。這個(gè)結(jié)構(gòu)必須在模塊的初始化函數(shù)中傳遞給解釋器。初始化函數(shù)必須命名為PyInit_name(),其中name是模塊的名稱,并且應(yīng)該是模塊文件中定義的唯一非靜態(tài)項(xiàng)
static struct PyModuleDef Combinationsmodule =
{
    PyModuleDef_HEAD_INIT,
    "Combinations",  /* 模塊名稱 */
    "usage: Combinations.uniqueCombinations(lstSortableItems, comboSize)n", /* 模塊文檔*/
    -1, /* 模塊的每個(gè)解釋器狀態(tài)的大小,如果模塊在全局變量中保持狀態(tài),則為-1。*/
    module_methods  /* 方法映射表 , 即需要引用定義好的引射表*/
};

// 初始化函數(shù),之前的初始化方法 PyMODINIT_FUNC initModule() 已棄用,新文檔見 https://docs.python.org/3/extending/extending.html
PyMODINIT_FUNC PyInit_Combinations(void)
{
    return PyModule_Create(&Combinationsmodule);  // 它返回一個(gè)模塊對象,并根據(jù)模塊定義中的表(PyMethodDef結(jié)構(gòu)的數(shù)組)將內(nèi)置函數(shù)對象插入到新創(chuàng)建的模塊中。
}

感覺源文件沒啥問題了,看看生成安裝包的規(guī)范是怎樣的:

生成wheel文件后進(jìn)行安裝,導(dǎo)入模塊成功,但是沒有函數(shù)。不過,又推進(jìn)了一步?。?!

python setup.py bdist_wheel

(6)安裝的模塊為啥沒有方法?

查閱官方文檔,有這么描述:可以用c++編寫擴(kuò)展模塊。一些限制適用。如果主程序(Python解釋器)被C編譯器編譯并鏈接,則不能使用帶構(gòu)造函數(shù)的全局對象或靜態(tài)對象。如果主程序是由c++編譯器鏈接的,這不是一個(gè)問題。Python解釋器將調(diào)用的函數(shù)(特別是模塊初始化函數(shù))必須使用extern "C"聲明。沒有必要將Python頭文件放在extern "C"{…} -如果定義了__cplusplus符號,它們已經(jīng)使用了這種形式(所有最近的c++編譯器)

官方描述:
It is possible to write extension modules in C++. Some restrictions apply. If the main program (the Python interpreter) is compiled and linked by the C compiler, global or static objects with constructors cannot be used. This is not a problem if the main program is linked by the C++ compiler. Functions that will be called by the Python interpreter (in particular, module initialization functions) have to be declared using extern "C". It is unnecessary to enclose the Python header files in extern "C" {...} — they use this form already if the symbol __cplusplus is defined (all recent C++ compilers define this symbol).

出錯(cuò)的原因可能是官方說的需要使用extern "C"聲明?還是說我使用的setuptools編譯得有問題?畢竟以前的編譯方式如下:

咋們換種編譯方式試試:

任意新建py文件,寫入下面代碼:

from distutils.core import setup, Extension
setup(name='Combinations', version='1.01', ext_modules=[Extension('Combinations', ['D:MinGWprojectsUSB5538_pythonsourceCombinations.cpp'])])

進(jìn)行構(gòu)建和安裝:

python .mytest002.py build
python .mytest002.py install --record files.txt

運(yùn)行看看:

PyDev console: starting.
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32
import Combinations
dir(Combinations) 
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'uniqueCombinations']
Combinations.__doc__ 
'usage: Combinations.uniqueCombinations(lstSortableItems, comboSize)n'
Combinations.uniqueCombinations() 
"uniqueCombinations() return value (is of type 'string')"

相關(guān)推薦