轉載自:https://www.cnblogs.com/yanzi-meng/p/8066944.html
1、問題
Python模塊和C/C++的動態庫間相互調用在實際的應用中會有所涉及,在此做一總結。html
2、Python調用C/C++python
一、Python調用C動態連接庫linux
Python調用C庫比較簡單,不通過任何封裝打包成so,再使用python的ctypes調用便可。
(1)C語言文件:pycall.cios
/***gcc -o libpycall.so -shared -fPIC pycall.c*/
#include <stdio.h>
#include <stdlib.h>
int foo(int a, int b)
{
printf("you input %d and %d\n", a, b);
return a+b;
} 程序員
(2)gcc編譯生成動態庫libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++編譯生成C動態庫的代碼中的函數或者方法時,須要使用extern "C"來進行編譯。算法
(3)Python調用動態庫的文件:pycall.py
import ctypes
ll = ctypes.cdll.LoadLibrary
lib = ll("./libpycall.so")
lib.foo(1, 3)
print '***finish***'
(4)運行結果:數組
二、Python調用C++(類)動態連接庫
須要extern "C"來輔助,也就是說仍是隻能調用C函數,不能直接調用方法,可是能解析C++方法。不是用extern "C",構建後的動態連接庫沒有這些函數的符號表。
(1)C++類文件:pycallclass.cpp安全
#include <iostream>
using namespace std;
class TestLib
{
public:
void display();
void display(int a);
};
void TestLib::display() {
cout<<"First display"<<endl;
}
void TestLib::display(int a) {
cout<<"Second display:"<<a<<endl;
}
extern "C" {
TestLib obj;
void display() {
obj.display();
}
void display_int() {
obj.display(2);
}
}多線程
(2)g++編譯生成動態庫libpycall.so:g++ -o libpycallclass.so -shared -fPIC pycallclass.cpp。app
(3)Python調用動態庫的文件:pycallclass.py
import ctypes
so = ctypes.cdll.LoadLibrary
lib = so("./libpycallclass.so")
print 'display()'
lib.display()
print 'display(100)'
lib.display_int(100)
(4)運行結果:
三、Python調用C/C++可執行程序
(1)C/C++程序:main.cpp
#include <iostream>
using namespace std;
int test()
{
int a = 10, b = 5;
return a+b;
}
int main()
{
cout<<"---begin---"<<endl;
int num = test();
cout<<"num="<<num<<endl;
cout<<"---end---"<<endl;
}
(2)編譯成二進制可執行文件:g++ -o testmain main.cpp。
(3)Python調用程序:main.py
import commands
import os
main = "./testmain"
if os.path.exists(main):
rc, out = commands.getstatusoutput(main)
print 'rc = %d, \nout = %s' % (rc, out)
print '*'*10
f = os.popen(main)
data = f.readlines()
f.close()
print data
print '*'*10
os.system(main)
(4)運行結果:
四、擴展Python(C++爲Python編寫擴展模塊)
全部能被整合或導入到其它python腳本的代碼,均可以被稱爲擴展。能夠用Python來寫擴展,也能夠用C和C++之類的編譯型的語言來寫擴展。Python在設計之初就考慮到要讓模塊的導入機制足夠抽象。抽象到讓使用模塊的代碼沒法瞭解到模塊的具體實現細節。Python的可擴展性具備的優勢:方便爲語言增長新功能、具備可定製性、代碼能夠實現複用等。
爲 Python 建立擴展須要三個主要的步驟:建立應用程序代碼、利用樣板來包裝代碼和編譯與測試。
(1)建立應用程序代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int fac(int n)
{
if (n < 2) return(1); /* 0! == 1! == 1 */
return (n)*fac(n-1); /* n! == n*(n-1)! */
}
char *reverse(char *s)
{
register char t, /* tmp */
*p = s, /* fwd */
*q = (s + (strlen(s) - 1)); /* bwd */
while (p < q) /* if p < q */
{
t = *p; /* swap & move ptrs */
*p++ = *q;
*q-- = t;
}
return(s);
}
int main()
{
char s[BUFSIZ];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", \
reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", \
reverse(s));
return 0;
}
上述代碼中有兩個函數,一個是遞歸求階乘的函數fac();另外一個reverse()函數實現了一個簡單的字符串反轉算法,其主要目的是修改傳入的字符串,使其內容徹底反轉,但不須要申請內存後反着複製的方法。
(2)用樣板來包裝代碼
接口的代碼被稱爲「樣板」代碼,它是應用程序代碼與Python解釋器之間進行交互所必不可少的一部分。樣板主要分爲4步:a、包含Python的頭文件;b、爲每一個模塊的每個函數增長一個型如PyObject* Module_func()的包裝函數;c、爲每一個模塊增長一個型如PyMethodDef ModuleMethods[]的數組;d、增長模塊初始化函數void initModule()。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int fac(int n)
{
if (n < 2) return(1);
return (n)*fac(n-1);
}
char *reverse(char *s)
{
register char t,
*p = s,
*q = (s + (strlen(s) - 1));
while (s && (p < q))
{
t = *p;
*p++ = *q;
*q-- = t;
}
return(s);
}
int test()
{
char s[BUFSIZ];
printf("4! == %d\n", fac(4));
printf("8! == %d\n", fac(8));
printf("12! == %d\n", fac(12));
strcpy(s, "abcdef");
printf("reversing 'abcdef', we get '%s'\n", \
reverse(s));
strcpy(s, "madam");
printf("reversing 'madam', we get '%s'\n", \
reverse(s));
return 0;
}
#include "Python.h"
static PyObject *
Extest_fac(PyObject *self, PyObject *args)
{
int num;
if (!PyArg_ParseTuple(args, "i", &num))
return NULL;
return (PyObject*)Py_BuildValue("i", fac(num));
}
static PyObject *
Extest_doppel(PyObject *self, PyObject *args)
{
char *orig_str;
char *dupe_str;
PyObject* retval;
if (!PyArg_ParseTuple(args, "s", &orig_str))
return NULL;
retval = (PyObject*)Py_BuildValue("ss", orig_str,
dupe_str=reverse(strdup(orig_str)));
free(dupe_str); #防止內存泄漏
return retval;
}
static PyObject *
Extest_test(PyObject *self, PyObject *args)
{
test();
return (PyObject*)Py_BuildValue("");
}
static PyMethodDef
ExtestMethods[] =
{
{ "fac", Extest_fac, METH_VARARGS },
{ "doppel", Extest_doppel, METH_VARARGS },
{ "test", Extest_test, METH_VARARGS },
{ NULL, NULL },
};
void initExtest()
{
Py_InitModule("Extest", ExtestMethods);
}
Python.h頭文件在大多數類Unix系統中會在/usr/local/include/python2.x或/usr/include/python2.x目錄中,系統通常都會知道文件安裝的路徑。
增長包裝函數,所在模塊名爲Extest,那麼建立一個包裝函數叫Extest_fac(),在Python腳本中使用是先import Extest,而後調用Extest.fac(),當Extest.fac()被調用時,包裝函數Extest_fac()會被調用,包裝函數接受一個 Python的整數參數,把它轉爲C的整數,而後調用C的fac()函數,獲得一個整型的返回值,最後把這個返回值轉爲Python的整型數作爲整個函數調用的結果返回回去。其餘兩個包裝函數Extest_doppel()和Extest_test()相似。
從Python到C的轉換用PyArg_Parse*系列函數,int PyArg_ParseTuple():把Python傳過來的參數轉爲C;int PyArg_ParseTupleAndKeywords()與PyArg_ParseTuple()做用相同,可是同時解析關鍵字參數;它們的用法跟C的sscanf函數很像,都接受一個字符串流,並根據一個指定的格式字符串進行解析,把結果放入到相應的指針所指的變量中去,它們的返回值爲1表示解析成功,返回值爲0表示失敗。從C到Python的轉換函數是PyObject* Py_BuildValue():把C的數據轉爲Python的一個對象或一組對象,而後返回之;Py_BuildValue的用法跟sprintf很像,把全部的參數按格式字符串所指定的格式轉換成一個Python的對象。
C與Python之間數據轉換的轉換代碼:
爲每一個模塊增長一個型如PyMethodDef ModuleMethods[]的數組,以便於Python解釋器可以導入並調用它們,每個數組都包含了函數在Python中的名字,相應的包裝函數的名字以及一個METH_VARARGS常量,METH_VARARGS表示參數以tuple形式傳入。 若須要使用PyArg_ParseTupleAndKeywords()函數來分析命名參數的話,還須要讓這個標誌常量與METH_KEYWORDS常量進行邏輯與運算常量 。數組最後用兩個NULL來表示函數信息列表的結束。
全部工做的最後一部分就是模塊的初始化函數,調用Py_InitModule()函數,並把模塊名和ModuleMethods[]數組的名字傳遞進去,以便於解釋器能正確的調用模塊中的函數。
(3)編譯
爲了讓新Python的擴展能被建立,須要把它們與Python庫放在一塊兒編譯,distutils包被用來編譯、安裝和分發這些模塊、擴展和包。
建立一個setup.py 文件,編譯最主要的工做由setup()函數來完成:
#!/usr/bin/env python
from distutils.core import setup, Extension
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])
Extension()第一個參數是(完整的)擴展的名字,若是模塊是包的一部分的話,還要加上用'.'分隔的完整的包的名字。上述的擴展是獨立的,因此名字只要寫"Extest"就行;sources參數是全部源代碼的文件列表,只有一個文件Extest2.c。setup須要兩個參數:一個名字參數表示要編譯哪一個內容;另外一個列表參數列出要編譯的對象,上述要編譯的是一個擴展,故把ext_modules參數的值設爲擴展模塊的列表。
運行setup.py build命令就能夠開始編譯咱們的擴展了,提示部分信息:
creating build/lib.linux-x86_64-2.6
gcc -pthread -shared build/temp.linux-x86_64-2.6/Extest2.o -L/usr/lib64 -lpython2.6 -o build/lib.linux-x86_64-2.6/Extest.so
(4)導入和測試
你的擴展會被建立在運行setup.py腳本所在目錄下的build/lib.*目錄中,能夠切換到那個目錄中來測試模塊,或者也能夠用命令把它安裝到Python中:python setup.py install,會提示相應信息。
測試模塊:
(5)引用計數和線程安全
Python對象引用計數的宏:Py_INCREF(obj)增長對象obj的引用計數,Py_DECREF(obj)減小對象obj的引用計數。Py_INCREF()和Py_DECREF()兩個函數也有一個先檢查對象是否爲空的版本,分別爲Py_XINCREF()和Py_XDECREF()。
編譯擴展的程序員必需要注意,代碼有可能會被運行在一個多線程的Python環境中。這些線程使用了兩個C宏Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS,經過將代碼和線程隔離,保證了運行和非運行時的安全性,由這些宏包裹的代碼將會容許其餘線程的運行。
3、C/C++調用Python
C++能夠調用Python腳本,那麼就能夠寫一些Python的腳本接口供C++調用了,至少能夠把Python當成文本形式的動態連接庫,
須要的時候還能夠改一改,只要不改變接口。缺點是C++的程序一旦編譯好了,再改就沒那麼方便了。
(1)Python腳本:pytest.py
#test function
def add(a,b):
print "in python function add"
print "a = " + str(a)
print "b = " + str(b)
print "ret = " + str(a+b)
return
def foo(a):
print "in python function foo"
print "a = " + str(a)
print "ret = " + str(a * a)
return
class guestlist:
def __init__(self):
print "aaaa"
def p():
print "bbbbb"
def __getitem__(self, id):
return "ccccc"
def update():
guest = guestlist()
print guest['aa']
#update()
(2)C++代碼:
/**g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6**/
#include <Python.h>
int main(int argc, char** argv)
{
// 初始化Python
//在使用Python系統前,必須使用Py_Initialize對其
//進行初始化。它會載入Python的內建模塊並添加系統路
//徑到模塊搜索路徑中。這個函數沒有返回值,檢查系統
//是否初始化成功須要使用Py_IsInitialized。
Py_Initialize();
// 檢查初始化是否成功
if ( !Py_IsInitialized() ) {
return -1;
}
// 添加當前路徑
//把輸入的字符串做爲Python代碼直接運行,返回0
//表示成功,-1表示有錯。大多時候錯誤都是由於字符串
//中有語法錯誤。
PyRun_SimpleString("import sys");
PyRun_SimpleString("print '---import sys---'");
PyRun_SimpleString("sys.path.append('./')");
PyObject *pName,*pModule,*pDict,*pFunc,*pArgs;
// 載入名爲pytest的腳本
pName = PyString_FromString("pytest");
pModule = PyImport_Import(pName);
if ( !pModule ) {
printf("can't find pytest.py");
getchar();
return -1;
}
pDict = PyModule_GetDict(pModule);
if ( !pDict ) {
return -1;
}
// 找出函數名爲add的函數
printf("----------------------\n");
pFunc = PyDict_GetItemString(pDict, "add");
if ( !pFunc || !PyCallable_Check(pFunc) ) {
printf("can't find function [add]");
getchar();
return -1;
}
// 參數進棧
*pArgs;
pArgs = PyTuple_New(2);
// PyObject* Py_BuildValue(char *format, ...)
// 把C++的變量轉換成一個Python對象。當須要從
// C++傳遞變量到Python時,就會使用這個函數。此函數
// 有點相似C的printf,但格式不一樣。經常使用的格式有
// s 表示字符串,
// i 表示整型變量,
// f 表示浮點數,
// O 表示一個Python對象。
PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",3));
PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",4));
// 調用Python函數
PyObject_CallObject(pFunc, pArgs);
//下面這段是查找函數foo 並執行foo
printf("----------------------\n");
pFunc = PyDict_GetItemString(pDict, "foo");
if ( !pFunc || !PyCallable_Check(pFunc) ) {
printf("can't find function [foo]");
getchar();
return -1;
}
pArgs = PyTuple_New(1);
PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",2));
PyObject_CallObject(pFunc, pArgs);
printf("----------------------\n");
pFunc = PyDict_GetItemString(pDict, "update");
if ( !pFunc || !PyCallable_Check(pFunc) ) {
printf("can't find function [update]");
getchar();
return -1;
}
pArgs = PyTuple_New(0);
PyTuple_SetItem(pArgs, 0, Py_BuildValue(""));
PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pName);
Py_DECREF(pArgs);
Py_DECREF(pModule);
// 關閉Python
Py_Finalize();
return 0;
}
(3)C++編譯成二進制可執行文件:g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6,編譯選項須要手動指定Python的include路徑和連接接路徑(Python版本號根據具體狀況而定)。
(4)運行結果:
4、總結
(1)Python和C/C++的相互調用僅是測試代碼,具體的項目開發還得參考Python的API文檔。 (2)二者交互,C++可爲Python編寫擴展模塊,Python也可爲C++提供腳本接口,更加方便於實際應用。 (3)如有不足,請留言,在此先感謝! --------------------- 做者:HeroKern 來源:CSDN 原文:https://blog.csdn.net/qq_21792169/article/details/80690277 版權聲明:本文爲博主原創文章,轉載請附上博文連接!