C言語とPythonの相互運用性:組み込み環境でのインターフェース実装

C言語アプリケーションからPythonコードを実行する

まず、C言語のプログラム内でPythonインタプリタを初期化し、特定のモジュール内の関数を呼び出す手順について解説します。この手法は、C言語で記述された既存の高パフォーマンスな処理の流れの中に、Pythonの柔軟なロジックを組み込みたい場合に有効です。

1. Pythonモジュールの作成
以下の内容で math_helper.py という名前のファイルを作成します。このモジュールには、2つの数値を受け取り、累乗計算を行う関数を定義します。

# math_helper.py
def calculate_power(base, exponent):
    print("Python function executed successfully.")
    print(f"Base value: {base}")
    print(f"Exponent value: {exponent}")
    
    result = base ** exponent
    print(f"Calculation result: {result}")
    
    return result

2. C言語からの呼び出しコード
次に、Python APIを使用して上記のモジュールをロードし、関数を実行するCプログラムを作成します。ファイル名は invoke_python.c とします。

#include <stdio.h>
#include <stdlib.h>
#include <Python.h>

int main(void) {
    // Pythonインタプリタの初期化
    Py_Initialize();

    if (!Py_IsInitialized()) {
        fprintf(stderr, "Failed to initialize Python interpreter.\n");
        return -1;
    }

    // モジュール検索パスにカレントディレクトリを追加
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");

    // オブジェクト参照用ポインタの宣言
    PyObject *pModuleName = NULL;
    PyObject *pModule = NULL;
    PyObject *pDict = NULL;
    PyObject *pFunc = NULL;
    PyObject *pArgs = NULL;
    PyObject *pRetVal = NULL;

    // モジュール名の指定とインポート
    pModuleName = PyUnicode_FromString("math_helper");
    pModule = PyImport_Import(pModuleName);

    if (!pModule) {
        fprintf(stderr, "Failed to import 'math_helper' module.\n");
        goto cleanup;
    }

    // モジュール辞書の取得
    pDict = PyModule_GetDict(pModule);
    if (!pDict) {
        fprintf(stderr, "Failed to retrieve module dictionary.\n");
        goto cleanup;
    }

    // 辞書から関数オブジェクトの取得
    pFunc = PyDict_GetItemString(pDict, "calculate_power");
    if (!pFunc || !PyCallable_Check(pFunc)) {
        fprintf(stderr, "Function 'calculate_power' not found or not callable.\n");
        goto cleanup;
    }

    // 引数タプルの作成 (base=5, exponent=3)
    pArgs = PyTuple_New(2);
    PyTuple_SetItem(pArgs, 0, PyLong_FromLong(5));
    PyTuple_SetItem(pArgs, 1, PyLong_FromLong(3));

    // 関数の実行
    pRetVal = PyObject_CallObject(pFunc, pArgs);
    
    if (pRetVal != NULL) {
        long result = PyLong_AsLong(pRetVal);
        printf("C received return value: %ld\n", result);
        Py_DECREF(pRetVal);
    }

cleanup:
    // 参照カウントのデクリメントと終了処理
    if (pArgs) Py_DECREF(pArgs);
    if (pModule) Py_DECREF(pModule);
    if (pModuleName) Py_DECREF(pModuleName);

    Py_Finalize();
    return 0;
}

3. コンパイルと実行
Pythonの開発ヘッダーファイルとライブラリを指定してコンパイルを行います。環境に合わせてパスを調整してください(ここではPython 3の構文を使用しているため、Python 3.xのライブラリを想定しています)。

gcc -I/usr/include/python3.8 invoke_python.c -o invoke_python -lpython3.8
./invoke_python

実行すると、Python側のログが出力され、その後C言語側で戻り値を受け取ったログが表示されます。


PythonからのC言語共有ライブラリの呼び出し

次に、逆にPythonスクリプトからC言語で実装された関数を呼び出す方法です。Python標準ライブラリの ctypes モジュールを使用すると、動的ロードライブラリ(.soファイル)内の関数を簡単に利用できます。これは計算量の多い処理をC言語にオフロードする際に広く利用される手法です。

1. C言語ソースコードの作成
長方形の面積を計算するシンプルな関数を実装します。ファイル名は geometry.c とします。

#include <stdio.h>

int calculate_area(int width, int height) {
    int area = width * height;
    printf("C Library: Calculating area of %dx%d -> %d\n", width, height, area);
    return area;
}

2. 共有ライブラリのビルド
Cソースコードをコンパイルし、共有ライブラリ libgeometry.so を生成します。

gcc -o libgeometry.so -shared -fPIC geometry.c

3. Pythonからの呼び出しスクリプト
生成された共有ライブラリをロードし、関数を実行するPythonスクリプトを作成します。ファイル名は run_geometry.py とします。

import ctypes

# 共有ライブラリのロード
# カレントディレクトリのライブラリを指定
try:
    geo_lib = ctypes.CDLL("./libgeometry.so")
except Exception as e:
    print(f"Error loading library: {e}")
    exit(1)

# 関数の引数と戻り値の型を設定(オプションだが推奨)
geo_lib.calculate_area.argtypes = [ctypes.c_int, ctypes.c_int]
geo_lib.calculate_area.restype = ctypes.c_int

# C関数の呼び出し (幅: 10, 高さ: 20)
w = 10
h = 20
result = geo_lib.calculate_area(w, h)

print(f"Python received calculated area: {result}")

4. 実行結果
Pythonスクリプトを実行すると、C言語の printf による出力と、Python側での出力結果が確認できます。

python3 run_geometry.py

タグ: Python C c-api ctypes embedded

8月3日 01:15 投稿