本範例示範 C 程式從記憶體字串動態編譯並載入 Python 模組,再呼叫其中函式。流程:
api.py,取得 load_test() 函式。load_test() 讀取 call.py 內容並回傳原始碼字串。Py_CompileString 編譯字串為程式碼物件,PyImport_ExecCodeModule 建立模組 a.a。add 函式並呼叫,取回結果。def test():
print('hello world')
def add(a, b):
return a + b
import io
def load_test():
with open('call.py', 'r') as fp:
return fp.read()
#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 | 用途 |
|---|---|
Py_CompileString | 將原始碼字串編譯為程式碼物件(PyCodeObject) |
PyImport_ExecCodeModule | 執行程式碼物件並註冊為指定名稱的模組 |
PyModule_GetDict | 取得模組的 __dict__ 字典,用於查找函式/變數 |
PyObject_CallObject | 呼叫可呼叫物件(函式、方法等) |
PyTuple_Pack | 快速建立參數元組 |
PyLong_FromLong / PyLong_AsLong | C 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 與執行檔同目錄。
PyErr_Print() 印出追蹤資訊。Py_INCREF/Py_DECREF,避免記憶體洩漏或過早釋放。PyEval_InitThreads() 並管理 GIL。