Python の例について語る -- Python と C/C++ は相互に呼び出します

再版リンク: Python について話す例 3 Python と C/C++ が相互に呼び出します

目次

1. 問題点

2. Python が C/C++ を呼び出す

1. Python は C ダイナミック リンク ライブラリを呼び出します

2. Python は C++ (クラス) ダイナミック リンク ライブラリを呼び出します

3. Python は C/C++ 実行可能プログラムを呼び出します

3. C/C++ が Python を呼び出す


 

1. 問題点


      Python モジュールと C/C++ ダイナミック ライブラリ間の相互呼び出しは実際のアプリケーションに関係しますので、ここでまとめます。

2. Python が C/C++ を呼び出す


1. Python は C ダイナミック リンク ライブラリを呼び出します


        Python が C ライブラリを呼び出すのは比較的簡単で、パッケージ化せずに C ライブラリにパッケージ化され、Python の ctypes によって呼び出されます。
(1) C言語ファイル:pycall.c

/***gcc -o libpycall.so -shared -fPIC pycall.c*/
#include <stdio.h>
#include <stdlib.h>
int foo(int a, int b)
{
  printf("you input %d and %d\n", a, b);
  return a+b;
}


(2) gcc を使用してダイナミック ライブラリ libpycall.so をコンパイルして生成します: gcc -o libpycall.so -shared -fPIC pycall.c。g++ を使用して C ダイナミック ライブラリを生成するコード内の関数またはメソッドをコンパイルする場合は、extern "C" を使用してコンパイルする必要があります。


(3) Python はダイナミック ライブラリのファイル pycall.py を呼び出します。

import ctypes
ll = ctypes.cdll.LoadLibrary 
lib = ll("./libpycall.so")  
lib.foo(1, 3)


(4) 走行結果:

 

2. Python は C++ (クラス) ダイナミック リンク ライブラリを呼び出します


       これを支援するには extern "C" が必要です。つまり、C 関数のみを呼び出すことができ、メソッドを直接呼び出すことはできませんが、C++ メソッドは解析できます。extern "C" を使用する代わりに、構築されたダイナミック リンク ライブラリにはこれらの関数のシンボル テーブルがありません。
(1) C++クラスファイル:pycallclass.cpp

#include <iostream>
using namespace std;
 
class TestLib
{
    public:
        void display();
        void display(int a);
};
void TestLib::display() {
    cout<<"First display"<<endl;
}
 
void TestLib::display(int a) {
    cout<<"Second display:"<<a<<endl;
}
extern "C" {
    TestLib obj;
    void display() {
        obj.display(); 
      }
    void display_int() {
        obj.display(2); 
      }
}


(2) g++ でコンパイルしてダイナミック ライブラリ libpycall.so を生成します: g++ -o libpycallclass.so -shared -fPIC pycallclass.cpp。
(3) Python はダイナミック ライブラリのファイル pycallclass.py を呼び出します。

import ctypes
so = ctypes.cdll.LoadLibrary 
lib = so("./libpycallclass.so") 

lib.display()

lib.display_int(100)


(4) 走行結果:

3. Python は C/C++ 実行可能プログラムを呼び出します


(1) C/C++プログラム:main.cpp

#include <iostream>
using namespace std;
int test()
{
    int a = 10, b = 5;
    return a+b;
}
int main()
{
    cout<<"---begin---"<<endl;
    int num = test();
    cout<<"num="<<num<<endl;
    cout<<"---end---"<<endl;
}


(2) バイナリ実行可能ファイルにコンパイルします: g++ -o testmain main.cpp。
(3) Python呼び出しプログラム:main.py

import commands
import os
main = "./testmain"
if os.path.exists(main):
    rc, out = commands.getstatusoutput(main)
    print 'rc = %d, \nout = %s' % (rc, out)

f = os.popen(main)  
data = f.readlines()  
f.close()  
print data

os.system(main)


(4) 走行結果:


4. Python の拡張 (C++ は Python の拡張モジュールを作成します)


       他の Python スクリプトに統合またはインポートできるコードはすべて、拡張機能と呼ばれます。拡張機能は、Python または C や C++ などのコンパイル言語で作成できます。Python は設計の初めに、モジュールのインポート メカニズムを十分に抽象化することを検討していました。これは非常に抽象的であるため、モジュールを使用するコードはモジュールの具体的な実装の詳細を理解できません。Python の拡張性には、言語に新しい関数を追加するのに便利で、カスタマイズ可能で、コードを再利用できるという利点があります。
       Python の拡張機能を作成するには、アプリケーション コードの作成、定型コードによるコードのラップ、コンパイルとテストの 3 つの主な手順が必要です。
(1) アプリケーションコードの作成 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int fac(int n)
{
    if (n < 2) return(1); /* 0! == 1! == 1 */
    return (n)*fac(n-1); /* n! == n*(n-1)! */
}
 
char *reverse(char *s)
{
    register char t,                    /* tmp */
            *p = s,                     /* fwd */
            *q = (s + (strlen(s) - 1)); /* bwd */
 
    while (p < q)               /* if p < q */
    {
        t = *p;         /* swap & move ptrs */
        *p++ = *q;
        *q-- = t;
    }
    return(s);
}
 
int main()
{
    char s[BUFSIZ];
    printf("4! == %d\n", fac(4));
    printf("8! == %d\n", fac(8));
    printf("12! == %d\n", fac(12));
    strcpy(s, "abcdef");
    printf("reversing 'abcdef', we get '%s'\n", \
        reverse(s));
    strcpy(s, "madam");
    printf("reversing 'madam', we get '%s'\n", \
        reverse(s));
    return 0;
}


       上記のコードには 2 つの関数があり、1 つは再帰階乗関数 fac() で、もう 1 つの reverse() 関数は単純な文字列反転アルゴリズムを実装しており、その主な目的は、内容が完全に反転されるように受信文字列を変更することです。ただし、メモリを適用してから逆方向にコピーする必要はありません。
(2)コード インターフェイスをボイラープレートで
        ラップするコードは「ボイラープレート」コードと呼ばれ、アプリケーション コードと Python インタープリター間の対話の重要な部分です。テンプレートは主に 4 つのステップに分かれています: a. Python ヘッダー ファイルをインクルードする; b. 各モジュールの各関数に PyObject* Module_func() などのラッパー関数を追加する; c. 各モジュールに PyMethodDef ModuleMethods などの型を追加する[] の配列; d、モジュール初期化関数 void initModule () を増やします。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int fac(int n)
{
    if (n < 2) return(1);
    return (n)*fac(n-1);
}
 
char *reverse(char *s)
{
    register char t,
            *p = s,
            *q = (s + (strlen(s) - 1));
 
    while (s && (p < q))
    {
        t = *p;
        *p++ = *q;
        *q-- = t;
    }
    return(s);
}
 
int test()
{
    char s[BUFSIZ];
    printf("4! == %d\n", fac(4));
    printf("8! == %d\n", fac(8));
    printf("12! == %d\n", fac(12));
    strcpy(s, "abcdef");
    printf("reversing 'abcdef', we get '%s'\n", \
        reverse(s));
    strcpy(s, "madam");
    printf("reversing 'madam', we get '%s'\n", \
        reverse(s));
    return 0;
}
 
#include "Python.h"
 
static PyObject *
Extest_fac(PyObject *self, PyObject *args)
{
    int num;
    if (!PyArg_ParseTuple(args, "i", &num))
        return NULL;
    return (PyObject*)Py_BuildValue("i", fac(num));
}
 
static PyObject *
Extest_doppel(PyObject *self, PyObject *args)
{
    char *orig_str;
    char *dupe_str;
    PyObject* retval;
 
    if (!PyArg_ParseTuple(args, "s", &orig_str))
        return NULL;
    retval = (PyObject*)Py_BuildValue("ss", orig_str,
        dupe_str=reverse(strdup(orig_str)));
    free(dupe_str);             #防止内存泄漏
    return retval;
}
 
static PyObject *
Extest_test(PyObject *self, PyObject *args)
{
    test();
    return (PyObject*)Py_BuildValue("");
}
 
static PyMethodDef
ExtestMethods[] =
{
    { "fac", Extest_fac, METH_VARARGS },
    { "doppel", Extest_doppel, METH_VARARGS },
    { "test", Extest_test, METH_VARARGS },
    { NULL, NULL },
};
 
void initExtest()
{
    Py_InitModule("Extest", ExtestMethods);
}


        Python.h ヘッダー ファイルは、ほとんどの Unix 系システムの /usr/local/include/python2.x または /usr/include/python2.x ディレクトリにあり、システムは通常、ファイルがインストールされているパスを認識しています。 。
        ラッパー関数を追加します。モジュール名は Extest です。次に、Extest_fac() というラッパー関数を作成します。これを Python スクリプトで使用します。最初に Extest をインポートし、次に Extest.fac() を呼び出します。Extest.fac() が呼び出されるときに、ラッパー関数 Extest_fac() が呼び出され、ラッパー関数は Python 整数パラメーターを受け入れ、それを C 整数に変換し、次に C fac() 関数を呼び出し、整数の戻り値を取得し、最後に戻り値を Python の整数に変換します。関数呼び出し全体の結果として返されます。他の 2 つのラッパー関数 Extest_doppel() と Extest_test() は似ています。
         Python から C への変換では、PyArg_Parse* 一連の関数 int PyArg_ParseTuple() を使用します: Python から C に渡されたパラメーターを変換します; int PyArg_ParseTupleAndKeywords() は PyArg_ParseTuple() と同じ機能を持ちますが、同時にキーワード パラメーターを解析します。 sscanf 関数は C の関数と非常によく似ており、文字列ストリームを受け取り、指定された形式文字列に従って解析し、結果を対応するポインタが指す変数に格納します。戻り値は 1 です。 、解析が成功したことを示し、値 0 は失敗を示します。C から Python への変換関数は PyObject* Py_BuildValue() です。C データを Python のオブジェクトまたはオブジェクトのグループに変換して返します。Py_BuildValue の使用法は sprintf と非常によく似ており、すべてのパラメーターは次に従って設定されます。指定された形式を Python オブジェクトに変換します。
        C と Python の間でデータを変換するための変換コード:

        

 

        Python インタープリターがモジュールをインポートして呼び出せるように、モジュールごとに PyMethodDef ModuleMethods[] タイプの配列を追加します。各配列には、Python の関数の名前、対応するラッパー関数の名前、および METH_VARARGS 定数が含まれます。パラメータはタプルとして渡されます。PyArg_ParseTupleAndKeywords() 関数を使用して名前付きパラメーターを分析する必要がある場合は、このフラグを定数にし、METH_KEYWORDS 定数で論理 AND 演算定数を実行する必要もあります。配列の最後には、関数情報リストの終わりを示すために 2 つの NULL が使用されます。
         すべての作業の最後の部分は、モジュールの初期化関数です。Py_InitModule() 関数を呼び出し、インタープリターがモジュール内の関数を正しく呼び出せるように、モジュール名と ModuleMethods[] 配列の名前を渡します。
(3) コンパイル
        新しい Python 拡張機能を作成するには、Python ライブラリと一緒にコンパイルする必要があり、これらのモジュール、拡張機能、パッケージのコンパイル、インストール、配布には distutils パッケージが使用されます。
        setup.py ファイルを作成すると、コンパイルの主な作業は setup() 関数によって行われます。

#!/usr/bin/env python
 
from distutils.core import setup, Extension
 
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])


        Extension() の最初の引数は、拡張機能の (完全な) 名前に、モジュールがパッケージの一部である場合は「.」で区切られた完全なパッケージ名を加えたものです。上記の拡張子はそれぞれ独立しているので、名前を「Extest」と書きます。sources パラメータはすべてのソース コードのファイル リストで、ファイルは Extest2.c の 1 つだけです。セットアップには 2 つのパラメータが必要です: name パラメータはコンパイルするコンテンツを示し、別の list パラメータはコンパイルするオブジェクトをリストし、上記のコンパイル対象のものは拡張機能であるため、ext_modules パラメータの値は次のリストに設定されます。拡張モジュール。
        setup.py ビルド コマンドを実行して拡張機能のコンパイルを開始し、いくつかの情報を求めるプロンプトが表示されます。

creating build/lib.linux-x86_64-2.6
gcc -pthread -shared build/temp.linux-x86_64-2.6/Extest2.o -L/usr/lib64 -lpython2.6 -o build/lib.linux-x86_64-2.6/Extest.so


(4) 拡張機能をインポートしてテストすると、
         setup.py スクリプトが実行されるディレクトリの下の build/lib.* ディレクトリに拡張機能が作成されます。そのディレクトリに切り替えてモジュールをテストするか、コマンドを使用してインストールできます。それを Python Middle: python setup.py install に入力すると、対応する情報が求められます。
         テストモジュール:


(5) 参照カウントとスレッドセーフな
       Python オブジェクト参照カウントのためのマクロ: Py_INCREF(obj) はオブジェクト obj の参照カウントを増加させ、Py_DECREF(obj) はオブジェクト obj の参照カウントを減少させます。2 つの関数 Py_INCREF() と Py_DECREF() には、オブジェクトが空かどうかを最初にチェックするバージョン、それぞれ Py_XINCREF() と Py_XDECREF() もあります。
      拡張機能をコンパイルするプログラマーは、コードがマルチスレッド Python 環境で実行される可能性があることに注意する必要があります。これらのスレッドは、Py_BEGIN_ALLOW_THREADS と Py_END_ALLOW_THREADS という 2 つの C マクロを使用します。コードをスレッドから分離することで、実行中と非実行中の安全性が保証されます。これらのマクロでラップされたコードにより、他のスレッドの実行が許可されます。

 


3. C/C++ が Python を呼び出す


       C++ は Python スクリプトを呼び出すことができるため、C++ が呼び出すための Python スクリプト インターフェイスをいくつか作成できます。少なくとも、Python をテキスト形式のダイナミック リンク ライブラリとして扱うことができ、変更しない限り、必要に応じて変更できます。インターフェース。欠点は、C++ プログラムがコンパイルされると、それを変更するのがそれほど便利ではないことです。
(1) Pythonスクリプト:pytest.py

#test function
def add(a,b):
    print "in python function add"
    print "a = " + str(a)
    print "b = " + str(b)
    print "ret = " + str(a+b)
    return
 
def foo(a):
 
    print "in python function foo"
    print "a = " + str(a)
    print "ret = " + str(a * a)
    return 
 
class guestlist:
    def __init__(self):
        print "aaaa"
    def p():
      print "bbbbb"
    def __getitem__(self, id):
      return "ccccc"
def update():
    guest = guestlist()
    print guest['aa']
 
#update()


(2) C++ コード:

/**g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6**/
#include <Python.h>
int main(int argc, char** argv)
{
    // 初始化Python
    //在使用Python系统前,必须使用Py_Initialize对其
    //进行初始化。它会载入Python的内建模块并添加系统路
    //径到模块搜索路径中。这个函数没有返回值,检查系统
    //是否初始化成功需要使用Py_IsInitialized。
    Py_Initialize();
 
    // 检查初始化是否成功
    if ( !Py_IsInitialized() ) {
        return -1;
    }
    // 添加当前路径
    //把输入的字符串作为Python代码直接运行,返回0
    //表示成功,-1表示有错。大多时候错误都是因为字符串
    //中有语法错误。
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("print '---import sys---'"); 
    PyRun_SimpleString("sys.path.append('./')");
    PyObject *pName,*pModule,*pDict,*pFunc,*pArgs;
 
    // 载入名为pytest的脚本
    pName = PyString_FromString("pytest");
    pModule = PyImport_Import(pName);
    if ( !pModule ) {
        printf("can't find pytest.py");
        getchar();
        return -1;
    }
    pDict = PyModule_GetDict(pModule);
    if ( !pDict ) {
        return -1;
    }
 
    // 找出函数名为add的函数
    printf("----------------------\n");
    pFunc = PyDict_GetItemString(pDict, "add");
    if ( !pFunc || !PyCallable_Check(pFunc) ) {
        printf("can't find function [add]");
        getchar();
        return -1;
     }
 
    // 参数进栈
    PyObject *pArgs;
    pArgs = PyTuple_New(2);
 
    //  PyObject* Py_BuildValue(char *format, ...)
    //  把C++的变量转换成一个Python对象。当需要从
    //  C++传递变量到Python时,就会使用这个函数。此函数
    //  有点类似C的printf,但格式不同。常用的格式有
    //  s 表示字符串,
    //  i 表示整型变量,
    //  f 表示浮点数,
    //  O 表示一个Python对象。
 
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",3));
    PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",4));
 
    // 调用Python函数
    PyObject_CallObject(pFunc, pArgs);
 
    //下面这段是查找函数foo 并执行foo
    printf("----------------------\n");
    pFunc = PyDict_GetItemString(pDict, "foo");
    if ( !pFunc || !PyCallable_Check(pFunc) ) {
        printf("can't find function [foo]");
        getchar();
        return -1;
     }
 
    pArgs = PyTuple_New(1);
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",2)); 
 
    PyObject_CallObject(pFunc, pArgs);
     
    printf("----------------------\n");
    pFunc = PyDict_GetItemString(pDict, "update");
    if ( !pFunc || !PyCallable_Check(pFunc) ) {
        printf("can't find function [update]");
        getchar();
        return -1;
     }
    pArgs = PyTuple_New(0);
    PyTuple_SetItem(pArgs, 0, Py_BuildValue(""));
    PyObject_CallObject(pFunc, pArgs);     
 
    Py_DECREF(pName);
    Py_DECREF(pArgs);
    Py_DECREF(pModule);
 
    // 关闭Python
    Py_Finalize();
    return 0;
} 


(3) C++ をバイナリ実行可能ファイルにコンパイルします。 g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6、コンパイル オプションは次のようにする必要があります。 Python のインクルード パスとリンク パスを手動で指定します (Python のバージョン番号は特定の状況によって異なります)。
(4) 走行結果:

 

おすすめ

転載: blog.csdn.net/qq_38295645/article/details/124416630