天天看點

c++ 調用Python腳本或者動态庫——環境Ubuntu 16.04下用codeblocks1. C++調用python腳本檔案2. C++工程調用python生成的so庫

背景:因為使用的是python版本的程式,最終要內建到C++環境的架構中,也就是說架構是c++的,傳遞使用者為c++的接口,但是調用的是python的庫,是以需要學習在c++環境下調用python。因為對python不熟悉,可以說有點一抹黑,是以從簡到難逐漸探索。首先在c++的工程中實作調用單個簡單的python腳本(.py腳本檔案),然後再調用python編譯成的庫(.so),最後将複雜的python“工程”編譯成庫,用c++寫好接口被總工程可調用。使用的IDE是codeblocks。

翻看了很多部落格,都是假設我們已經知道了設定操作,對我這種操作小白來說及其痛苦。翻牆的油管視訊實在是太慢,國内的優酷實在沒什麼幹貨。。。遂這樣拆解任務由簡到難,也可以為不同的需求提供不同的方式。

1. C++調用python腳本檔案

(1)配置環境

用codeblocks(後面簡稱CB)建立一個console的c++工程,取名叫consoleUseSo,裡面隻有一個main.cpp檔案。

配置好Python的環境,具體配置方法請見:

https://blog.csdn.net/u014794992/article/details/52901147

總結起來基本就是:右鍵工程,點選build options,左欄選擇最上面的工程名(不要選Debug或者Release否則得設定兩遍)。我預設的編譯器是GNU GCC Compiler,這個不用去管。需要設定的是:點選linker settings頁籤,在Link Libraries裡面選擇python的庫檔案路徑,一般是usr/lib/python2.7/config-x86_64-linux-gnu/libpython2.7.so,CB可以選擇設為相對路徑。完成後點選search directories頁籤,在下面的complier頁籤下,選擇Python的include路徑,一般為:usr/include/python2.7;然後在Linker頁籤中,選擇lib路徑,一般為:usr/lib/python2.7。這樣編譯環境就設定好了。在main.cpp中,需要加上:

#include <python2.7/Python.h>
或者
#include <Python.h>
           

(2)簡單的python腳本檔案

寫一個簡單的Python腳本檔案,叫做:your_file.py

#-* -coding: UTF-8 -* -

def display(name):
    print "hi",name  

class test:
    def say(self):
        print 'hello'
           

裡面有簡單的螢幕列印函數。

将此your_file.py檔案放置在consoUseSo工程的目錄裡面,即和main.cpp在同一路徑下!後面會說怎麼設定路徑

(3)main.cpp中調用python腳本

在main.cpp中添加如下代碼:

#include <iostream>
#include "stdio.h"
#include <python2.7/Python.h> //隻寫<Python.h>也可以

int main()
{
    // 初始化Python
    //在使用Python系統前,必須使用Py_Initialize對其
    //進行初始化。它會載入Python的内模組化塊并添加系統路
    //徑到子產品搜尋路徑中。這個函數沒有傳回值,檢查系統
    //是否初始化成功需要使用Py_IsInitialized。
    Py_Initialize();

    // 檢查初始化是否成功
    if ( !Py_IsInitialized() ) {
        return -1;
    }

    // 添加目前路徑。這裡注意下面三句都不可少,
    //添加的是目前路徑。但是我列印了sys.path,
    //出來了好多路徑,有點類似環境變量路徑的東西。這點不太懂怎麼就成目前路徑了
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("print '---import sys---'");
    //下面這個./表示目前工程的路徑,如果使用../則為上級路徑,根據此來設定
    PyRun_SimpleString("print sys.path.append('./')"); 

    PyObject *pName,*pModule,*pDict,*pFunc,*pArgs;

    // 載入名為your_file的腳本
    pName = PyString_FromString("your_file");
    pModule = PyImport_Import(pName);
    if ( !pModule ) {
        printf("can't find your_file.py");
        getchar();
        return -1;
    }

    pDict = PyModule_GetDict(pModule);
    if ( !pDict ) {
        return -1;
    }
    printf("----------------------\n");

    // 找出函數名為display的函數
    pFunc = PyDict_GetItemString(pDict, "display");
    if ( !pFunc || !PyCallable_Check(pFunc) ) {
        printf("can't find function [display]");
        getchar();
        return -1;
     }

    //将參數傳進去。1代表一個參數。
    pArgs = PyTuple_New(1);

    //  PyObject* Py_BuildValue(char *format, ...)
    //  把C++的變量轉換成一個Python對象。當需要從
    //  C++傳遞變量到Python時,就會使用這個函數。此函數
    //  有點類似C的printf,但格式不同。常用的格式有
    //  s 表示字元串,
    //  i 表示整型變量,
    //  f 表示浮點數,
    //  O 表示一個Python對象。
    //這裡我要傳的是字元串是以用s,注意字元串需要雙引号!
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("s"," python in C++"));
    // 調用Python函數
    PyObject_CallObject(pFunc, pArgs);

    // 關閉Python
    Py_Finalize();
    return 0;
}
           

具體函數含義解釋還可見此,我也主要參考的這個部落格:https://www.cnblogs.com/apexchu/p/5015961.html

點選build,然後運作,如下:

c++ 調用Python腳本或者動态庫——環境Ubuntu 16.04下用codeblocks1. C++調用python腳本檔案2. C++工程調用python生成的so庫

不知道為什麼列印出來的sys.path.append就成了None,我隻列印sys.path出來了好多類似環境變量的路徑。此處不懂,可能是加了個”./"就表示目前工程路徑了?路徑可以設定為相對路徑,也可以設定為絕對路徑。比如我要是把python放在工程上一級目錄,路徑可以設定為:

PyRun_SimpleString("print sys.path.append('../')");

也可以設定為絕對路徑,比如:

PyRun_SimpleString("print sys.path.append('/home/y/Documents/aaa/code/sotest/')");

(4)調用包含main函數的腳本

題外話,供自己備忘...

這個PyRun_SimpleString()函數就是非常強大的調用腳本的函數。上面說的是指向了一個路徑,然後後面用PyString_FromString函數來尋找這個路徑下面的your_file.py檔案,然後進一步用PyDict_GetItemString函數來搜尋我們想要的函數。

如果是一個本身包含main函數的python腳本,可以用PyRun_SimpleString函數直接運作(用execfile)。假定我們的demo-video.py就是這樣的一個腳本,我們可以這樣調用:

PyRun_SimpleString("import sys");
PyRun_SimpleString("print sys.path.append('/home/y/py/tools/')");
PyRun_SimpleString("execfile('/home/y/py/tools/demo-video.py')");
           

前提當然是和(1)中一樣設定好路徑和頭檔案,build之後運作,可以自動運作腳本中的main函數内容。

官方文檔在這裡,然而沒有例子,沒有什麼卵用:https://docs.python.org/3/c-api/veryhigh.html#c.PyCompilerFlags

2. C++工程調用python生成的so庫

有了上面的鋪墊,此處就簡單了。其實用的是一個函數PyRun_SimpleString,隻要在這個函數中,設定好了so庫的路徑,就ok了。但是為了防止腦殘的我後面還會忘記,還是稍微的詳細說下。

以第1節中的工程為例,因為我們建立了your_file.py這個python腳本,然後我們利用已經内置在python裡的cython來将我們這個腳本打包成庫。方法是:建立另一個腳本檔案setup.py,跟your_file.py放在一個路徑下,setup.py裡面寫:

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize(["your_file.py"]))
           

這裡就是用cython來将Python腳本打包成動态連結庫.so,這裡的例子比較簡單,應該是導出了your_file.py中的所有函數。

接着說怎麼打包,寫好了這個setup.py後,在這兩個檔案路徑下,打開終端,輸入:

$ python setup.py build_ext

就可以發現在目前檔案夾裡多了一個“build”檔案夾,下面的lib.linux-x86_64-2.7檔案夾裡面的your_file.so就是我們編譯成功的動态連結庫了。

在第1節中的工程中調用它的方法就是,将路徑設定好,就這麼easy,其它的用法都一樣。

PyRun_SimpleString("print sys.path.append('/home/y/Documents/aaa/code/sotest/build/lib.linux-x86_64-2.7/')");

在build options裡竟然都不用設定....因為這裡代碼意思就是直接在路徑下找到這個檔案裡的某個函數進行調用。