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

C 語言嵌入 Python 進階:從記憶體動態載入並編譯執行 Python 程式碼

     編輯:關於C語言

場景動機

上一篇示範從檔案載入模組。本文解決更靈活的需求:

  • Python 程式碼存儲在資料庫、網路下發、加密存儲
  • 運行時動態生成程式碼字串
  • 避免寫入臨時 .py 檔案

核心 API:Py_CompileString + PyImport_ExecCodeModule

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()) return -1;

    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(); Py_Finalize(); return -1; }

    PyObject *pDict = PyModule_GetDict(pModule);
    PyObject *pFunc = PyDict_GetItemString(pDict, "load_test");
    if (!pFunc || !PyCallable_Check(pFunc)) { Py_DECREF(pModule); Py_Finalize(); return -1; }

    PyObject *pCodeStr = PyObject_CallObject(pFunc, NULL);  // 回傳 Python str 物件
    if (!pCodeStr) { Py_DECREF(pModule); Py_Finalize(); return -1; }

    const char *c_code = PyUnicode_AsUTF8(pCodeStr);  // 轉為 C 字串
    if (!c_code) { Py_DECREF(pCodeStr); Py_DECREF(pModule); Py_Finalize(); return -1; }

    /* 2. 核心步驟:編譯字串為程式碼物件 */
    PyObject *pCodeObj = Py_CompileString(c_code, "", Py_file_input);
    Py_DECREF(pCodeStr);
    if (!pCodeObj) { PyErr_Print(); Py_DECREF(pModule); Py_Finalize(); return -1; }

    /* 3. 執行程式碼物件建立模組 */
    PyObject *pNewModule = PyImport_ExecCodeModule("dynamic_call", pCodeObj);
    Py_DECREF(pCodeObj);
    if (!pNewModule) { PyErr_Print(); Py_DECREF(pModule); Py_Finalize(); return -1; }

    /* 4. 從動態模組取得 add 函數並呼叫 */
    PyObject *pNewDict = PyModule_GetDict(pNewModule);
    pFunc = PyDict_GetItemString(pNewDict, "add");
    if (pFunc && PyCallable_Check(pFunc)) {
        PyObject *pArgs = Py_BuildValue("(ll)", 3, 4);
        PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
        Py_DECREF(pArgs);
        if (pResult) {
            long ret = PyLong_AsLong(pResult);
            printf("動態載入 add(3,4) = %ld\n", ret);
            Py_DECREF(pResult);
        }
    }

    /* 5. 清理 */
    Py_DECREF(pNewModule);
    Py_DECREF(pModule);
    Py_Finalize();
    return 0;
}

關鍵 API 深度解析

API用途關鍵參數
Py_CompileString將字串編譯為程式碼物件str: 原始碼
filename: 錯誤訊息顯示用虛擬檔名
start: Py_file_input (模組級)、Py_eval_input (單一運算式)、Py_single_input (互動式單句)
PyImport_ExecCodeModule執行程式碼物件並註冊為模組name: 模組名 (如 "dynamic_call")
co: Py_CompileString 回傳的程式碼物件

進階變體:PyImport_ExecCodeModuleEx 與 PyImport_ExecCodeModuleWithPathnames

  • PyImport_ExecCodeModuleEx(name, co, pathname):指定 __file__ 屬性
  • PyImport_ExecCodeModuleWithPathnames(name, co, pathname, cpathname):同時指定原始碼與編譯後路徑(Python 3.2+)

應用場景擴展

  1. 插件系統:主程式掃描目錄,讀取 .py 文字,動態載入為插件模組
  2. 腳本沙箱:結合 PyDict_SetItemString 限制可用內建函數/模組
  3. 熱重載:檢測檔案修改時間,重新 CompileString + ExecCodeModule 替換 sys.modules 中舊模組
  4. 加密腳本:運行時解密到記憶體字串,編譯執行,不落地明文

記憶體管理關鍵點

  • Py_CompileString 回傳新引用,用完必須 Py_DECREF
  • PyImport_ExecCodeModule 回傳新引用(模組物件)
  • PyUnicode_AsUTF8 回傳借用引用指標,不可 Py_DECREF,且生命週期綁定原 PyObject*
  • 錯誤路徑上務必釋放所有已獲取的引用

除錯技巧

/* 編譯失敗時獲取詳細錯誤 */
if (!pCodeObj) {
    PyErr_Print();  // 印出語法錯誤、縮排錯誤等
    // 或程式化獲取
    PyObject *ptype, *pvalue, *ptraceback;
    PyErr_Fetch(&ptype, &pvalue, &ptraceback);
    // 解析 pvalue 獲取錯誤訊息字串
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved