簡體   English   中英

如何從 C 代碼調用 Python 代碼?

[英]How do you call Python code from C code?

我想用一些新功能擴展一個大型 C 項目,但我真的想用 Python 編寫它。 基本上,我想從 C 代碼調用 Python 代碼。 但是,像 SWIG 這樣的 Python->C 包裝器允許 OPPOSITE,即編寫 C 模塊並從 Python 調用 C。

我正在考慮一種涉及 IPC 或 RPC 的方法(我不介意有多個進程); 也就是說,讓我的純 Python 組件在單獨的進程中運行(在同一台機器上),並讓我的 C 項目通過從套接字(或 unix 管道)寫入/讀取來與其通信。 我的 python 組件可以讀/寫套接字進行通信。 這是一個合理的方法嗎? 有什么更好的嗎? 喜歡一些特殊的RPC機制?

感謝到目前為止的回答 -但是,我想專注於基於 IPC 的方法,因為我希望將我的 Python 程序作為我的 C 程序放在一個單獨的進程中。 我不想嵌入 Python 解釋器。 謝謝!

我推薦這里詳述方法 它首先解釋如何執行 Python 代碼字符串,然后詳細說明如何設置 Python 環境以與 C 程序交互、從 C 代碼調用 Python 函數、從 C 代碼操作 Python 對象等。

編輯:如果你真的想走 IPC 的路線,那么你會想要使用struct 模塊或更好的protlib Python 和 C 進程之間的大多數通信都圍繞着來回傳遞結構,通過套接字或通過共享內存

我建議創建一個帶有字段和代碼的Command結構來表示命令及其參數。 在不了解您想要完成的任務的情況下,我無法給出更具體的建議,但總的來說,我推薦protlib庫,因為我用它來在 C 和 Python 程序之間進行通信(免責聲明:我是 protlib 的作者) .

您是否考慮過將您的 Python 應用程序包裝在一個 shell 腳本中並從您的 C 應用程序中調用它?

不是最優雅的解決方案,但它非常簡單。

請參閱手冊中的相關章節: http : //docs.python.org/extending/

本質上,您必須將 python 解釋器嵌入到您的程序中。

我沒有使用 IPC 方法進行 Python<->C 通信,但它應該工作得很好。 我會讓 C 程序做一個標准的 fork-exec 並在子進程中使用重定向的stdinstdout進行通信。 良好的基於​​文本的通信將使開發和測試 Python 程序變得非常容易。

如果我決定使用 IPC,我可能會使用XML-RPC —— 跨平台,如果你願意,以后可以輕松地將 Python 服務器項目放在不同的節點上,有許多出色的實現(請參閱此處了解許多,包括 C 和 Python 的,這里是簡單的 XML-RPC 服務器,它是 Python 標准庫的一部分——不像其他方法那樣具有高度可擴展性,但對於您的用例來說可能很好且方便)。

它可能不是適用於所有情況的完美 IPC 方法(甚至是完美的 RPC 方法,無論如何!),但在我看來,它的便利性、靈活性、健壯性和廣泛的實現超過了許多小缺陷。

這看起來很不錯http://thrift.apache.org/ ,甚至還有一本關於它的書。

細節:

Apache Thrift 軟件框架,用於可擴展的跨語言服務開發,將軟件堆棧與代碼生成引擎相結合,構建在 C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、 Cocoa、JavaScript、Node.js、Smalltalk、OCaml 和 Delphi 等語言。

我使用了在另一個應用程序嵌入 Python的“標准”方法。 但這很復雜/乏味。 Python 中的每個新函數都很難實現。

我看到了一個從 C 調用 PyPy的例子。 它使用 CFFI 來簡化接口,但它需要 PyPy,而不是 Python。 首先閱讀並理解這個例子,至少在一個較高的層次上。

我修改了 C/PyPy 示例以使用 Python。 以下是如何使用 CFFI 從 C 調用 Python。

我的例子比較復雜,因為我在 Python 中實現了三個函數而不是一個。 我想涵蓋來回傳遞數據的其他方面。

復雜的部分現在被隔離到將api的地址傳遞給 Python。 這只需執行一次。 之后很容易在 Python 中添加新函數。

接口.h

// These are the three functions that I implemented in Python.
// Any additional function would be added here.
struct API {
    double (*add_numbers)(double x, double y);
    char* (*dump_buffer)(char *buffer, int buffer_size);
    int (*release_object)(char *obj);
};

test_cffi.c

//
// Calling Python from C.
// Based on Calling PyPy from C:
// http://doc.pypy.org/en/latest/embedding.html#more-complete-example
//

#include <stdio.h>
#include <assert.h>

#include "Python.h"

#include "interface.h"

struct API api;   /* global var */

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

    // Start Python interpreter and initialize "api" in interface.py using 
    // old style "Embedding Python in Another Application":
    // https://docs.python.org/2/extending/embedding.html#embedding-python-in-another-application
    PyObject *pName, *pModule, *py_results;
    PyObject *fill_api;
#define PYVERIFY(exp) if ((exp) == 0) { fprintf(stderr, "%s[%d]: ", __FILE__, __LINE__); PyErr_Print(); exit(1); }

    Py_SetProgramName(argv[0]);  /* optional but recommended */
    Py_Initialize();
    PyRun_SimpleString(
            "import sys;"
            "sys.path.insert(0, '.')" );

    PYVERIFY( pName = PyString_FromString("interface") )
    PYVERIFY( pModule = PyImport_Import(pName) )
    Py_DECREF(pName);
    PYVERIFY( fill_api = PyObject_GetAttrString(pModule, "fill_api") )

    // "k" = [unsigned long],
    // see https://docs.python.org/2/c-api/arg.html#c.Py_BuildValue
    PYVERIFY( py_results = PyObject_CallFunction(fill_api, "k", &api) )
    assert(py_results == Py_None);

    // Call Python function from C using cffi.
    printf("sum: %f\n", api.add_numbers(12.3, 45.6));

    // More complex example.
    char buffer[20];
    char * result = api.dump_buffer(buffer, sizeof buffer);
    assert(result != 0);
    printf("buffer: %s\n", result);

    // Let Python perform garbage collection on result now.
    rc = api.release_object(result);
    assert(rc == 0);

    // Close Python interpreter.
    Py_Finalize();

    return 0;
}

接口.py

import cffi
import sys
import traceback

ffi = cffi.FFI()
ffi.cdef(file('interface.h').read())

# Hold references to objects to prevent garbage collection.
noGCDict = {}

# Add two numbers.
# This function was copied from the PyPy example.
@ffi.callback("double (double, double)")
def add_numbers(x, y):
    return x + y

# Convert input buffer to repr(buffer).
@ffi.callback("char *(char*, int)")
def dump_buffer(buffer, buffer_len):
    try:
        # First attempt to access data in buffer.
        # Using the ffi/lib objects:
        # http://cffi.readthedocs.org/en/latest/using.html#using-the-ffi-lib-objects
        # One char at time, Looks inefficient.
        #data = ''.join([buffer[i] for i in xrange(buffer_len)])

        # Second attempt.
        # FFI Interface:
        # http://cffi.readthedocs.org/en/latest/using.html#ffi-interface
        # Works but doc says "str() gives inconsistent results".
        #data = str( ffi.buffer(buffer, buffer_len) )

        # Convert C buffer to Python str.
        # Doc says [:] is recommended instead of str().
        data = ffi.buffer(buffer, buffer_len)[:]

        # The goal is to return repr(data)
        # but it has to be converted to a C buffer.
        result = ffi.new('char []', repr(data))

        # Save reference to data so it's not freed until released by C program.
        noGCDict[ffi.addressof(result)] = result

        return result
    except:
        print >>sys.stderr, traceback.format_exc()
        return ffi.NULL

# Release object so that Python can reclaim the memory.
@ffi.callback("int (char*)")
def release_object(ptr):
    try:
        del noGCDict[ptr]
        return 0
    except:
        print >>sys.stderr, traceback.format_exc()
        return 1

def fill_api(ptr):
    global api
    api = ffi.cast("struct API*", ptr)

    api.add_numbers = add_numbers
    api.dump_buffer = dump_buffer
    api.release_object = release_object

編譯:

gcc -o test_cffi test_cffi.c -I/home/jmudd/pgsql-native/Python-2.7.10.install/include/python2.7 -L/home/jmudd/pgsql-native/Python-2.7.10.install/lib -lpython2.7

執行:

$ test_cffi
sum: 57.900000
buffer: 'T\x9e\x04\x08\xa8\x93\xff\xbf]\x86\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00'
$ 

顯然Python需要能夠編譯為win32 dll,它會解決問題

以這種方式將 c# 代碼轉換為 win32 dll 將使其可用於任何開發工具

將它與 Python 3 綁定的幾個技巧

  1. 不支持 file(),使用 open()

ffi.cdef(open('interface.h').read())

  1. PyObject* PyStr_FromString(const char *u)
    Create a PyStr from a UTF-8 encoded null-terminated character buffer.
    Python 2: PyString_FromString
    Python 3: PyUnicode_FromString

更改為: PYVERIFY( pName = PyUnicode_FromString("interface") )

  1. 節目名稱
    wchar_t *name = Py_DecodeLocale(argv[0], NULL);
    Py_SetProgramName(name); 
  1. 編譯用
   gcc cc.c -o cc -I/usr/include/python3.6m -I/usr/include/x86_64-linux-gnu/python3.6m -lpython3.6m
  1. 我屠宰了 dump def .. 也許它會給出一些想法
def get_prediction(buffer, buffer_len):
    try:
        data = ffi.buffer(buffer, buffer_len)[:]
        result = ffi.new('char []', data)
      
        print('\n I am doing something here here........',data )

        resultA = ffi.new('char []', b"Failed")  ### New message 

        ##noGCDict[ffi.addressof(resultA)] = resultA 
        return resultA
    except:
        print >>sys.stderr, traceback.format_exc()
        return ffi.NULL
} 

希望它會有所幫助並為您節省一些時間

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM