首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C 語言嵌入 Python:動態載入腳本並呼叫函式完整實作

     編輯:關於C

架構概覽

本範例示範 C 程式從記憶體字串動態編譯並載入 Python 模組,再呼叫其中函式。流程:

  1. 初始化 Python 解譯器。
  2. 載入「載入器」模組 api.py,取得 load_test() 函式。
  3. 呼叫 load_test() 讀取 call.py 內容並回傳原始碼字串。
  4. 用 Py_CompileString 編譯字串為程式碼物件,PyImport_ExecCodeModule 建立模組 a.a。
  5. 從新模組取得 add 函式並呼叫,取回結果。

Python 端程式碼

call.py —— 被動態載入的目標模組

def test():
    print('hello world')

def add(a, b):
    return a + b

api.py —— 載入器模組

import io

def load_test():
    with open('call.py', 'r') as fp:
        return fp.read()

C 端完整程式碼

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

int main(int argc, char *argv[])
{
    Py_Initialize();
    if (!Py_IsInitialized()) {
        fprintf(stderr, "Python 初始化失敗\n");
        return -1;
    }

    // 將當前目錄加入 sys.path
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");

    // 1. 匯入 api 模組並取得 load_test 函式
    PyObject *pName = PyUnicode_FromString("api");
    PyObject *pModule = PyImport_Import(pName);
    Py_DECREF(pName);
    if (!pModule) {
        PyErr_Print();
        fprintf(stderr, "無法匯入 api 模組\n");
        goto fail;
    }

    PyObject *pDict = PyModule_GetDict(pModule);
    PyObject *pFunc = PyDict_GetItemString(pDict, "load_test");
    if (!pFunc || !PyCallable_Check(pFunc)) {
        fprintf(stderr, "找不到 load_test 函式\n");
        goto fail;
    }

    // 2. 呼叫 load_test() 取得 call.py 原始碼字串
    PyObject *pResult = PyObject_CallObject(pFunc, NULL);
    if (!pResult) { PyErr_Print(); goto fail; }

    const char *source = PyUnicode_AsUTF8(pResult);
    if (!source) { Py_DECREF(pResult); goto fail; }
    printf("載入的原始碼:\n%s\n", source);

    // 3. 編譯並動態建立模組 a.a
    PyObject *pCode = Py_CompileString(source, "call.py", Py_file_input);
    Py_DECREF(pResult);
    if (!pCode) { PyErr_Print(); goto fail; }

    PyObject *pNewModule = PyImport_ExecCodeModule("a.a", pCode);
    Py_DECREF(pCode);
    if (!pNewModule) { PyErr_Print(); goto fail; }

    // 4. 從新模組取得 add 函式並呼叫
    PyObject *pNewDict = PyModule_GetDict(pNewModule);
    PyObject *pAddFunc = PyDict_GetItemString(pNewDict, "add");
    if (!pAddFunc || !PyCallable_Check(pAddFunc)) {
        fprintf(stderr, "找不到 add 函式\n");
        goto fail;
    }

    PyObject *pArgs = PyTuple_Pack(2, PyLong_FromLong(3), PyLong_FromLong(4));
    PyObject *pRet = PyObject_CallObject(pAddFunc, pArgs);
    Py_DECREF(pArgs);
    if (!pRet) { PyErr_Print(); goto fail; }

    long ret = PyLong_AsLong(pRet);
    Py_DECREF(pRet);
    printf("add(3, 4) = %ld\n", ret);

    Py_DECREF(pModule);
    Py_DECREF(pNewModule);
    Py_Finalize();
    return 0;

fail:
    Py_XDECREF(pModule);
    Py_XDECREF(pNewModule);
    Py_Finalize();
    return -1;
}

關鍵 API 說明

API用途
Py_CompileString將原始碼字串編譯為程式碼物件(PyCodeObject)
PyImport_ExecCodeModule執行程式碼物件並註冊為指定名稱的模組
PyModule_GetDict取得模組的 __dict__ 字典,用於查找函式/變數
PyObject_CallObject呼叫可呼叫物件(函式、方法等)
PyTuple_Pack快速建立參數元組
PyLong_FromLong / PyLong_AsLongC long ↔ Python int 轉換

編譯與執行

# Linux/macOS
gcc -I/usr/include/python3.11 -o embed embed.c -lpython3.11
./embed

# Windows (MSVC)
cl /I C:\Python311\include embed.c /link C:\Python311\libs\python311.lib
embed.exe

需確保 call.py、api.py 與執行檔同目錄。

進階議題

  • 錯誤處理:所有 Python C API 呼叫後應檢查回傳值,失敗時用 PyErr_Print() 印出追蹤資訊。
  • 參考計數:嚴格遵循 Py_INCREF/Py_DECREF,避免記憶體洩漏或過早釋放。
  • 多執行緒:嵌入多執行緒環境需呼叫 PyEval_InitThreads() 並管理 GIL。
  • 沙箱化:動態執行非信任程式碼需限制內建函式、模組存取等,安全性考量極大。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved