#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;
}
上述代碼中有兩個函數(shù),一個是遞歸求階乘的函數(shù)fac();另一個reverse()函數(shù)實現(xiàn)了一個簡單的字符串反轉(zhuǎn)算法,其主要目的是修改傳入的字符串,使其內(nèi)容完全反轉(zhuǎn),但不需要申請內(nèi)存后反著復制的方法。
(2)用樣板來包裝代碼
接口的代碼被稱為“樣板”代碼,它是應用程序代碼與Python解釋器之間進行交互所必不可少的一部分。樣板主要分為4步:a、包含Python的頭文件;b、為每個模塊的每一個函數(shù)增加一個型如PyObject*
Module_func()的包裝函數(shù);c、為每個模塊增加一個型如PyMethodDef
ModuleMethods[]的數(shù)組;d、增加模塊初始化函數(shù)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); #防止內(nèi)存泄漏
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頭文件在大多數(shù)類Unix系統(tǒng)中會在/usr/local/include/python2.x或/usr/include/python2.x目錄中,系統(tǒng)一般都會知道文件安裝的路徑。
增加包裝函數(shù),所在模塊名為Extest,那么創(chuàng)建一個包裝函數(shù)叫Extest_fac(),在Python腳本中使用是先import Extest,然后調(diào)用Extest.fac(),當Extest.fac()被調(diào)用時,包裝函數(shù)Extest_fac()會被調(diào)用,包裝函數(shù)接受一個 Python的整數(shù)參數(shù),把它轉(zhuǎn)為C的整數(shù),然后調(diào)用C的fac()函數(shù),得到一個整型的返回值,最后把這個返回值轉(zhuǎn)為Python的整型數(shù)做為整個函數(shù)調(diào)用的結(jié)果返回回去。其他兩個包裝函數(shù)Extest_doppel()和Extest_test()類似。
從Python到C的轉(zhuǎn)換用PyArg_Parse*系列函數(shù),int
PyArg_ParseTuple():把Python傳過來的參數(shù)轉(zhuǎn)為C;int
PyArg_ParseTupleAndKeywords()與PyArg_ParseTuple()作用相同,但是同時解析關(guān)鍵字參數(shù);它們的用法跟C的sscanf函數(shù)很像,都接受一個字符串流,并根據(jù)一個指定的格式字符串進行解析,把結(jié)果放入到相應的指針所指的變量中去,它們的返回值為1表示解析成功,返回值為0表示失敗。從C到Python的轉(zhuǎn)換函數(shù)是PyObject*
Py_BuildValue():把C的數(shù)據(jù)轉(zhuǎn)為Python的一個對象或一組對象,然后返回之;Py_BuildValue的用法跟sprintf很像,把所有的參數(shù)按格式字符串所指定的格式轉(zhuǎn)換成一個Python的對象。 C與Python之間數(shù)據(jù)轉(zhuǎn)換的轉(zhuǎn)換代碼:
為每個模塊增加一個型如PyMethodDef
ModuleMethods[]的數(shù)組,以便于Python解釋器能夠?qū)氩⒄{(diào)用它們,每一個數(shù)組都包含了函數(shù)在Python中的名字,相應的包裝函數(shù)的名字以及一個METH_VARARGS常量,METH_VARARGS表示參數(shù)以tuple形式傳入。
若需要使用PyArg_ParseTupleAndKeywords()函數(shù)來分析命名參數(shù)的話,還需要讓這個標志常量與METH_KEYWORDS常量進行邏輯與運算常量
。數(shù)組最后用兩個NULL來表示函數(shù)信息列表的結(jié)束。 所有工作的最后一部分就是模塊的初始化函數(shù),調(diào)用Py_InitModule()函數(shù),并把模塊名和ModuleMethods[]數(shù)組的名字傳遞進去,以便于解釋器能正確的調(diào)用模塊中的函數(shù)。 (3)編譯 為了讓新Python的擴展能被創(chuàng)建,需要把它們與Python庫放在一起編譯,distutils包被用來編譯、安裝和分發(fā)這些模塊、擴展和包。 創(chuàng)建一個setup.py 文件,編譯最主要的工作由setup()函數(shù)來完成:
#!/usr/bin/env python
from distutils.core import setup, Extension
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])
Extension()第一個參數(shù)是(完整的)擴展的名字,如果模塊是包的一部分的話,還要加上用'.'分隔的完整的包的名字。上述的擴展是獨立的,所以名字只要寫'Extest'就行;sources參數(shù)是所有源代碼的文件列表,只有一個文件Extest2.c。setup需要兩個參數(shù):一個名字參數(shù)表示要編譯哪個內(nèi)容;另一個列表參數(shù)列出要編譯的對象,上述要編譯的是一個擴展,故把ext_modules參數(shù)的值設(shè)為擴展模塊的列表。
運行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)導入和測試
你的擴展會被創(chuàng)建在運行setup.py腳本所在目錄下的build/lib.*目錄中,可以切換到那個目錄中來測試模塊,或者也可以用命令把它安裝到Python中:python setup.py install,會提示相應信息。 測試模塊:
(5)引用計數(shù)和線程安全
Python對象引用計數(shù)的宏:Py_INCREF(obj)增加對象obj的引用計數(shù),Py_DECREF(obj)減少對象obj的引用計數(shù)。Py_INCREF()和Py_DECREF()兩個函數(shù)也有一個先檢查對象是否為空的版本,分別為Py_XINCREF()和Py_XDECREF()。
編譯擴展的程序員必須要注意,代碼有可能會被運行在一個多線程的Python環(huán)境中。這些線程使用了兩個C宏P(guān)y_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS,通過將代碼和線程隔離,保證了運行和非運行時的安全性,由這些宏包裹的代碼將會允許其他線程的運行。
三、C/C 調(diào)用Python
C 可以調(diào)用Python腳本,那么就可以寫一些Python的腳本接口供C 調(diào)用了,至少可以把Python當成文本形式的動態(tài)鏈接庫, 需要的時候還可以改一改,只要不改變接口。缺點是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系統(tǒng)前,必須使用Py_Initialize對其
//進行初始化。它會載入Python的內(nèi)建模塊并添加系統(tǒng)路
//徑到模塊搜索路徑中。這個函數(shù)沒有返回值,檢查系統(tǒng)
//是否初始化成功需要使用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;
}
// 找出函數(shù)名為add的函數(shù)
printf('----------------------\n');
pFunc = PyDict_GetItemString(pDict, 'add');
if ( !pFunc || !PyCallable_Check(pFunc) ) {
printf('can't find function [add]');
getchar();
return -1;
}
// 參數(shù)進棧
*pArgs;
pArgs = PyTuple_New(2);
// PyObject* Py_BuildValue(char *format, ...)
// 把C 的變量轉(zhuǎn)換成一個Python對象。當需要從
// C 傳遞變量到Python時,就會使用這個函數(shù)。此函數(shù)
// 有點類似C的printf,但格式不同。常用的格式有
// s 表示字符串,
// i 表示整型變量,
// f 表示浮點數(shù),
// O 表示一個Python對象。
PyTuple_SetItem(pArgs, 0, Py_BuildValue('l',3));
PyTuple_SetItem(pArgs, 1, Py_BuildValue('l',4));
// 調(diào)用Python函數(shù)
PyObject_CallObject(pFunc, pArgs);
//下面這段是查找函數(shù)foo 并執(zhí)行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);
// 關(guān)閉Python
Py_Finalize();
return 0;
}
(3)C 編譯成二進制可執(zhí)行文件:g -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6,編譯選項需要手動指定Python的include路徑和鏈接接路徑(Python版本號根據(jù)具體情況而定)。
(4)運行結(jié)果:
四、總結(jié)
(1)Python和C/C 的相互調(diào)用僅是測試代碼,具體的項目開發(fā)還得參考Python的API文檔。 (2)兩者交互,C 可為Python編寫擴展模塊,Python也可為C 提供腳本接口,更加方便于實際應用。 (3)若有不足,請留言,在此先感謝!
|