1. 程式人生 > >Python與C之間的相互呼叫(Python C API及Python ctypes庫)

Python與C之間的相互呼叫(Python C API及Python ctypes庫)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

討論新聞組及檔案

我實現onekeycodehighlighter"中碰到的一些小問題,需要實現全域性快捷鍵,但是是事實上Qt並沒有對全域性快捷鍵提供支援,那麼用Qt的話就只能通過Win32Api來完成了,而我,用的是PyQt,還需要用Python來呼叫win32 API,事實上,都沒有什麼難的。

因為Python如此的流行,導致,開源社群按照自己的愛好,對於Python與C之間互相呼叫上,各自開發了自己想要的呼叫方式,其中包括用Python C API來完成,包括ctypes這個Python標準庫,還有那一大堆的各式各樣的繫結方案如SIP,Boost::Python等,要知道,Python流行到什麼程式,Boost庫號稱C++準標準庫,唯一對C++以外的一種語言提供了支援,那就是Python,Python還是Symbian除C++,JAVA外支援的第3種語言,當年在原來的公司,我還一直以為Python是個新鮮的小玩意兒,要我鼓搗Python C API的時候很新鮮,(事實上原公司的確沒有用Python的人)到了新公司一看,啊~~~公司只允許使用3中語言,C++,JAVA,還有Python,而大家對Python那都是駕輕就熟,信手拈來,常用來開發一些工具及指令碼,呵呵,世界原來與我想象的並不同。
這裡將以前工作中用到的Python C API知識,及最近用到的ctypes庫的知識梳理一下。

Python C API

此部分可以參考我原來的文章《python c api 使用心得...》,這裡只是會有一些實際的例子,原來那是一個大概流程的描述。
某年某月,在我開始學習Python古老的歲月中(我不是倚老賣老啊)。。。。ctypes還不存在,那時候我們都是老實的用C語言,呼叫Python CAPI來完成從Python中呼叫C語言函式的任務,我學習Python的時候還在想,哈哈哈哈哈,我以前學過C/C++,我可以很熟練的呼叫Python C API來完成Python呼叫Win32 API這樣的任務,我多了不起啊:)這個時候的感覺就像,嘿,Python你不是了不起嗎。。。。還不是沒有辦法逃離C語言的魔掌。。。。此時,畫面中出現的是K&R嘿嘿嘿嘿的冷笑。。。。Guido van Rossum在他們腳下抱著頭哭了。。。。。。。
那時候,情況大概是這樣的:

準備工作:

閒話少說,看看Python C API。事實上,Python C API比起Lua的API了來說,清晰了很多,這也符合Pythonic的風格,就算這時Python C API是設計給C語言使用者使用的,還是這樣的風格,比起Lua API那種彙編式的介面,(據說為了效率,可以直接操作每個資料)強了太多了。
要使用Python C API,用普通的二進位制包是不行的,得下原始碼包。這裡我用3.1.1的原始碼包為例:Source Distribution
Python的原始碼在Windows的版本中已經完全換到VS2008了,直接用VS2008開啟在PCbuild目錄下的工程即可,對於VS2005及以前的使用者開啟PC目錄下的其他版本工程。我們編譯debug版本的pythoncore會得到python31_d.lib,python31_d.dll兩個檔案,需要的標頭檔案在Include目錄下,還需要將pyconfig.h檔案從PCBuild目錄下拷貝到Include中,(硬要直接指定也可以)這樣準備工作就已經齊了。

Python C API有兩個方向的使用方式,從C中呼叫Python指令碼及利用C擴充套件Python。
先講簡單的從C中呼叫Python,也就是常說的在C中內嵌Python。

C中內嵌Python

新建立一個工程,首先需要將工作目錄設定到Python-3.1.1PCbuild中,以獲取到動態庫,至於靜態庫的包含,Include目錄的指定,那自然也是少不了的。檔案中需要包含Python.h檔案,這也是必須的。
介面中
    Py_Initialize();
    Py_Finalize();
一對的呼叫是必須的,一個用於初始化Python的動態庫,一個用於釋放。釋放時會輸出[31818 refs],意義不明。

PyRun_SimpleString

可用於執行簡單的Python語句。如下:


#include "python.h"

int main(int argc, char* argv[])
{
    Py_Initialize();

    PyRun_SimpleString("print("Hello World")");
    Py_Finalize();

    system("PAUSE");
    return 0;
}

 

此時,輸出為:

Hello World
[31829 refs]
請按任意鍵繼續. . .

 

此時可以執行一些Python語句了,並且,特別需要注意的是,在一個Py_Initialize();與Py_Finalize();之間,Python語句執行是在同一個執行環境中,不懂什麼意思?看個示例就知道了。


int main(int argc, char* argv[])
{
    Py_Initialize();

    PyRun_SimpleString("str = "Hello World"");
    PyRun_SimpleString("print(str)");

    Py_Finalize();

    system("PAUSE");
    return 0;
}

此例與上例輸出是一樣的,懂我的意思了吧?意思就是以前執行的語句對後面的語句是有效的,相當於在同一個互動式命令列中順序執行語句。

獲取返回值

PyRun_SimpleString有的缺點,文件中的描述是:

Returns 0 on success or -1 if an exception was raised.

那麼你就無法在Python及C語言中傳遞任何資訊。我們需要高階點的函式才行。

 

PyObject* PyRun_String(const char *str, int start, PyObject *globals, PyObject *locals)
就是幹這個的。
但是需要注意的是此函式的一些引數的獲取,按照想當然的給他們置空可是不行的,如下例所示:

#include "python.h"

int main(int argc, char* argv[])
{
    Py_Initialize();

    PyRun_SimpleString("x = 10");
    PyRun_SimpleString("y = 20");
    PyObject* mainModule = PyImport_ImportModule("__main__");
    PyObject* dict = PyModule_GetDict(mainModule);
    PyObject* resultObject = PyRun_String("x + y", Py_eval_input, dict, dict);

    if(resultObject)
    {
        long result = PyLong_AsLong(resultObject);
        printf("%d", result);
        Py_DECREF(resultObject);
    }

    Py_Finalize();

    system("PAUSE");
    return 0;
}
這裡我利用了一個知識,那就是
PyRun_SimpleString實際是將所有的程式碼都放在__main__ 模組中執行,注意啊,沒有匯入正確的模組及其dict,你會執行失敗,失敗的很慘。至此,C語言已經於Python來了個互動了。
呵呵,突然覺得深入下去就沒有盡頭了。。。。。。。還是點到為止吧。
稍微深入點的可以去看《Programming Python》一書。在啄木鳥上有此書及一些譯文。Part VI: Integration 部分Chapter 23. Embedding Python,有相關的知識。

利用C擴充套件Python

此部分在《Programming Python》的Chapter 22. Extending Python 部分有介紹。
這裡也只能開個頭了,最多告訴你,其實,這些都沒有什麼難的。稍微複雜點的情況《python c api 使用心得...》一文中有介紹。
配置上與前面講的類似,一般來說,利用C擴充套件Python最後會生成一個動態庫,不過這個動態庫的字尾會設為.pyd,只有這樣,import的時候才會自動的查詢到。
另外,為Python寫擴充套件要遵循Python的那套規則,固定的幾個命名。
首先看自帶的例子:

#include "Python.h"

static PyObject *
ex_foo(PyObject *self, PyObject *args)
{
    printf("Hello, worldn");
    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef example_methods[] = {
    {"foo", ex_foo, METH_VARARGS, "foo() doc string"},
    {NULL, NULL}
};

static struct PyModuleDef examplemodule = {
    PyModuleDef_HEAD_INIT,
    "example",
    "example module doc string",
    -1,
    example_methods,
    NULL,
    NULL,
    NULL,
    NULL
};

PyMODINIT_FUNC
PyInit_example(void)
{
    return PyModule_Create(&examplemodule);
}
這個例子包含了全部C語言為Python寫擴充套件時的基本資訊:
1.PyInit_example是最後的出口,其中需要注意的是example不僅僅代表example的意思,還代表了最後生成的庫會用example命名,也就是你呼叫此庫會需要使用
import example

的形式。
2.static struct PyModuleDef examplemodule的存在也是必須的,指定了整個模組的資訊,比如上面的"example module doc string", 模組的說明文字。每個引數的含義上面已經有些演示了。全部內容可以參考文件中關於PyModuleDef的說明
3.example_methods是一個函式列表,事實上表示此模組中含有的函式。此例中僅含有foo一個函式。
static PyObject *
ex_foo(PyObject *self, PyObject *args)
{
    printf("Hello, worldn");
    Py_INCREF(Py_None);
    return Py_None;
}

就是整個函式的具體實現了,此函式表示輸出"Hello, world",還是hello world。。。。。。。。這個world還真忙啊。。。。天天有人say hello。

這個Python本身附帶的例子有點太簡單了,我給出一個稍微複雜點的例子,還是我最喜歡的MessageBox,最後的效果自然還是Hello world。。。。。。。。。。。

#include

static PyObject *
MessageBox(PyObject *self, PyObject *args)
{
    LPCSTR lpText;
    LPCSTR lpCaption;
    UINT uType;

    PyArg_ParseTuple(args, "ssi", &lpText, &lpCaption, &uType);

    int result = MessageBoxA(0, lpText, lpCaption, uType);

    PyObject* resultObject = Py_BuildValue("%i", result);

    return resultObject;
}

static PyMethodDef c_methods[] = {
    {"MessageBox", MessageBox, METH_VARARGS, "MessageBox() "},
    {NULL, NULL}
};

static struct PyModuleDef win32module = {
    PyModuleDef_HEAD_INIT,
    "Win32API",
    "Win32 API MessageBox",
    -1,
    c_methods,
    NULL,
    NULL,
    NULL,
    NULL
};

PyMODINIT_FUNC
PyInit_Win32API(void)
{
    return PyModule_Create(&win32module);
}

需要注意的還是需要注意,唯一有點區別的是這裡我有從Python中傳進來的引數及從C中傳出去的返回值了。
PyArg_ParseTuple 用於解析引數
Py_BuildValue 用於構建一個Python的值返回
他們的構建和解析形式有點類似於sprintf等C常見的形式,可是每個字元代表的東西不一定一樣,需要注意,文件中比較詳細,此例中展示的是String及int的轉換。

以生成動態庫的方式編譯此檔案後,並指定為Win32API.pyd檔案,然後將其拷貝到Python_d所在的目錄(用Python3.1.1原始碼生成的除錯版本Python),此時import會首先查詢*_d.pyd形式的動態庫,不然只會搜尋release版。
首先看看庫的資訊:
>>> import Win32API
[44692 refs]
>>> dir(Win32API)
['MessageBox', '__doc__', '__file__', '__name__', '__package__']
[44705 refs]
>>> help(Win32API)
Help on module Win32API:

NAME
    Win32API - Win32 API MessageBox

FILE
    d:python-3.1.1pcbuildwin32api_d.pyd

FUNCTIONS
    MessageBox(...)
        MessageBox()


[68311 refs]
注意到文件的作用了吧?還注意到dir的強大。。。。。。。。。。。。。此時MessageBox已經在Win32API中了,直接呼叫吧。我這裡忽略了視窗的控制代碼,需要注意。

多麼繁忙的World啊。。。。。。。。
此時你會想,太強大了,我要將整個的Win32 API到處,於是Python就能像C/C++語言一樣完全操作整個作業系統了,並且,這還是動態的!!!!
沒錯,不過多大的工作量啊。。。。。。不過,Python這麼流行,總是有人做這樣的事情的,於是PyWindows出世了。去安裝一個,於是你什麼都有了。
>>> import win32api
>>> win32api.MessageBox(0, "Great", "Hello World", 0)
1
這樣,就能達到上面全部的效果。。。。。。。。。。。


Python ctypes

如此這般,原來Python還是離不開C啊(雖然Python本身使用C寫的)。。。,直到。。。。某年某月ctypes橫空出世了,於是,完全不懂C語言的人,也可以直接用Python來完成這樣的工作了。毫無疑問,Python越來越自成體系了,他們的目標是,沒有其他語言!-_-!在Python v3.1.1的文件中如此描述,
ctypes — A foreign function library for Python
然後:It can be used to wrap these libraries in pure Python.
注意,他們要的是Pure Python!(我不是想要挑起語言戰爭。。。。。)
Guido van Rossum開始說,wrap these,in pure Python。。。。不要再用foreign語言,血統不pure的傢伙了。


閒話少說,看看ctypes,因為是pure Python嘛,所以看起來很簡單,事實上文件也比較詳細(當然,還是遺漏了一些細節),下面都以Windows中的Python3.1.1的操作為例:
>>> import ctypes
>>> from ctypes import *
>>> dir(ctypes)
['ARRAY', 'ArgumentError', 'Array', 'BigEndianStructure', 'CDLL', 'CFUNCTYPE', '
DEFAULT_MODE', 'DllCanUnloadNow', 'DllGetClassObject', 'FormatError', 'GetLastEr
ror', 'HRESULT', 'LibraryLoader', 'LittleEndianStructure', 'OleDLL', 'POINTER',
'PYFUNCTYPE', 'PyDLL', 'RTLD_GLOBAL', 'RTLD_LOCAL', 'SetPointerType', 'Structure
', 'Union', 'WINFUNCTYPE', 'WinDLL', 'WinError', '_CFuncPtr', '_FUNCFLAG_CDECL',
 '_FUNCFLAG_PYTHONAPI', '_FUNCFLAG_STDCALL', '_FUNCFLAG_USE_ERRNO', '_FUNCFLAG_U
SE_LASTERROR', '_Pointer', '_SimpleCData', '__builtins__', '__doc__', '__file__'
, '__name__', '__package__', '__path__', '__version__', '_c_functype_cache', '_c
alcsize', '_cast', '_cast_addr', '_check_HRESULT', '_check_size', '_ctypes_versi
on', '_dlopen', '_endian', '_memmove_addr', '_memset_addr', '_os', '_pointer_typ
e_cache', '_string_at', '_string_at_addr', '_sys', '_win_functype_cache', '_wstr
ing_at', '_wstring_at_addr', 'addressof', 'alignment', 'byref', 'c_bool', 'c_buf
fer', 'c_byte', 'c_char', 'c_char_p', 'c_double', 'c_float', 'c_int', 'c_int16',
 'c_int32', 'c_int64', 'c_int8', 'c_long', 'c_longdouble', 'c_longlong', 'c_shor
t', 'c_size_t', 'c_ubyte', 'c_uint', 'c_uint16', 'c_uint32', 'c_uint64', 'c_uint
8', 'c_ulong', 'c_ulonglong', 'c_ushort', 'c_void_p', 'c_voidp', 'c_wchar', 'c_w
char_p', 'cast', 'cdll', 'create_string_buffer', 'create_unicode_buffer', 'get_e
rrno', 'get_last_error', 'memmove', 'memset', 'oledll', 'pointer', 'py_object',
'pydll', 'pythonapi', 'resize', 'set_conversion_mode', 'set_errno', 'set_last_er
ror', 'sizeof', 'string_at', 'windll', 'wstring_at']

一個這樣的小玩意兒包含的東西還真不少啊,可以看到主要包括一些C語言的型別定義。
當你import ctypes的時候,一些動態庫已經載入了:
>>> print(windll.kernel32)

>>> print(windll.user32)

>>> print(windll.msvcrt)


直接來使用試試吧,我們最喜歡的自然是Hello World。這裡直接呼叫MessageBox。查查MSDN,MessageBox在User32中,我們呼叫它。
>>> MessageBox = windll.user32.MessageBoxW
>>> MessageBox(0,"Great","Hello World", 0)
然後,就呼叫了MessageBox了。。。。。。。。

怎麼?暈了?比較一下ctypes庫及Python C API吧。。。。於是,K&R哭了。。。。。。。。。。。。。
故事以下圖開始
以下圖結束:






 

原創文章作者保留版權 轉載請註明原作者 並給出連結

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述 我們對Markdown編輯器進行了一些功能拓展與語法支援,除了標準的Markdown編輯器功能,我們增加了如下幾點新功能,幫助你用它寫部落格:
  1. 全新的介面設計 ,將會帶來全新的寫作體驗;
  2. 在創作中心設定你喜愛的程式碼高亮樣式,Markdown 將程式碼片顯示選擇的高亮樣式 進行展示;
  3. 增加了 圖片拖拽 功能,你可以將本地的圖片直接拖拽到編輯區域直接展示;
  4. 全新的 KaTeX數學公式 語法;
  5. 增加了支援甘特圖的mermaid語法1 功能;
  6. 增加了 多螢幕編輯 Markdown文章功能;
  7. 增加了 焦點寫作模式、預覽模式、簡潔寫作模式、左右區域同步滾輪設定 等功能,功能按鈕位於編輯區域與預覽區域中間;
  8. 增加了 檢查列表 功能。

功能快捷鍵

撤銷:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜體:Ctrl/Command + I
標題:Ctrl/Command + Shift + H
無序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
檢查列表:Ctrl/Command + Shift + C
插入程式碼:Ctrl/Command + Shift + K
插入連結:Ctrl/Command + Shift + L
插入圖片:Ctrl/Command + Shift + G

合理的建立標題,有助於目錄的生成

直接輸入1次#,並按下space後,將生成1級標題。
輸入2次#,並按下space後,將生成2級標題。
以此類推,我們支援6級標題。有助於使用TOC語法後生成一個完美的目錄。

如何改變文字的樣式

強調文字 強調文字

加粗文字 加粗文字

標記文字

刪除文字

引用文字

H2O is是液體。

210 運算結果是 1024.

插入連結與圖片

連結: link.

圖片: Alt

帶尺寸的圖片: Alt

當然,我們為了讓使用者更加便捷,我們增加了圖片拖拽功能。

如何插入一段漂亮的程式碼片

部落格設定頁面,選擇一款你喜歡的程式碼片高亮樣式,下面展示同樣高亮的 程式碼片.

// An highlighted block var foo = 'bar'; 

生成一個適合你的列表

  • 專案
    • 專案
      • 專案
  1. 專案1
  2. 專案2
  3. 專案3
  • 計劃任務
  • 完成任務

建立一個表格

一個簡單的表格是這麼建立的:

專案 Value
電腦 $1600
手機 $12
導管 $1

設定內容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文字居中 第二列文字居右 第三列文字居左

SmartyPants

SmartyPants將ASCII標點字元轉換為“智慧”印刷標點HTML實體。例如:

TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

建立一個自定義列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何建立一個註腳

一個具有註腳的文字。2

註釋也是必不可少的

Markdown將文字轉換為 HTML

KaTeX數學公式

您可以使用渲染LaTeX數學表示式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n 1 ) ! n N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N 是通過尤拉積分

Γ ( z ) = 0 t z 1 e t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.

你可以找到更多關於的資訊 LaTeX 數學表示式here.

新的甘特圖功能,豐富你的文章

gantt
        dateFormat  YYYY-MM-DD
        title Adding GANTT diagram functionality to mermaid
        section 現有任務
        已完成               :done,    des1, 2014-01-06,2014-01-08
        進行中               :active,  des2, 2014-01-09, 3d
        計劃一               :         des3, after des2, 5d
        計劃二               :         des4, after des3, 5d
  • 關於 甘特圖 語法,參考 這兒,

UML 圖表

可以使用UML圖表進行渲染。 Mermaid. 例如下面產生的一個序列圖::

這將產生一個流程圖。:

  • 關於 Mermaid 語法,參考 這兒,

FLowchart流程圖

我們依舊會支援flowchart的流程圖:

  • 關於 Flowchart流程圖 語法,參考 這兒.

匯出與匯入

匯出

如果你想嘗試使用此編輯器, 你可以在此篇文章任意編輯。當你完成了一篇文章的寫作, 在上方工具欄找到 文章匯出 ,生成一個.md檔案或者.html檔案進行本地儲存。

匯入

如果你想載入一篇你寫過的.md檔案或者.html檔案,在上方工具欄可以選擇匯入功能進行對應副檔名的檔案匯入,
繼續你的創作。


  1. mermaid語法說明 ↩︎

  2. 註腳的解釋 ↩︎