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용 확장을 만들려면 애플리케이션 코드 만들기, 상용구로 코드 래핑, 컴파일 및 테스트의 세 가지 주요 단계가 필요합니다.
(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;
}


       위의 코드에는 두 가지 함수가 있는데, 하나는 재귀 계승 함수 fac()이고 다른 하나는 간단한 문자열 반전 알고리즘을 구현하는 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 헤더 파일은 대부분의 유닉스 계열 시스템에서 /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 정수로 변환합니다. 전체 함수 호출의 결과로 반환됩니다. 다른 두 래퍼 함수 Extest_doppel() 및 Extest_test()는 비슷합니다.
         Python에서 C로의 변환은 PyArg_Parse* 시리즈의 함수인 int PyArg_ParseTuple()을 사용합니다: Python에서 C로 전달된 매개변수를 변환합니다. 사용법은 C와 동일 sscanf 함수는 문자열 스트림을 받아 지정된 형식 문자열에 따라 구문 분석하고 결과를 해당 포인터가 가리키는 변수에 넣습니다.반환 값은 1입니다. , 구문 분석이 성공했음을 나타내며 값 0은 실패를 나타냅니다. C에서 Python으로의 변환 함수는 PyObject*입니다. Py_BuildValue(): C 데이터를 Python의 개체 또는 개체 그룹으로 변환한 다음 반환 지정된 형식을 Python 개체로 변환합니다.
        C와 Python 간의 데이터 변환을 위한 변환 코드:

        

 

        Python 인터프리터가 가져오고 호출할 수 있도록 각 모듈에 대해 PyMethodDef ModuleMethods[] 유형의 배열을 추가합니다. 각 배열에는 Python의 함수 이름, 해당 래퍼 함수 이름 및 METH_VARARGS 상수가 포함됩니다. METH_VARARGS는 다음을 나타냅니다. 매개변수는 튜플로 전달됩니다. 명명된 매개변수를 분석하기 위해 PyArg_ParseTupleAndKeywords() 함수를 사용해야 하는 경우, 이 플래그 상수와 METH_KEYWORDS 상수가 논리 AND 연산 상수를 수행하도록 해야 합니다. 배열의 끝에서 함수 정보 목록의 끝을 나타내기 위해 두 개의 NULL이 사용됩니다.
         모든 작업의 ​​마지막 부분은 모듈의 초기화 함수로, Py_InitModule() 함수를 호출하고 모듈 이름과 ModuleMethods[] 배열의 이름을 전달하여 인터프리터가 모듈의 함수를 올바르게 호출할 수 있도록 합니다.
(3) 컴파일
        새로운 파이썬 확장을 생성하기 위해서는 파이썬 라이브러리와 함께 컴파일해야 하며, 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"로 작성하고 소스 매개변수는 모든 소스 코드의 파일 목록이며 Extest2.c 파일은 하나뿐입니다. 설정에는 두 가지 매개변수가 필요합니다: 이름 매개변수는 컴파일할 콘텐츠를 나타내고, 또 다른 목록 매개변수는 컴파일할 개체를 나열하며, 위에서 언급한 컴파일 대상은 확장자이므로 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) 참조 카운팅 및 스레드로부터 안전한
       파이썬 객체 참조 카운팅을 위한 매크로: Py_INCREF(obj)는 객체 obj의 참조 카운트를 증가시키고 Py_DECREF(obj)는 객체 obj의 참조 카운트를 감소시킵니다. 두 함수 Py_INCREF() 및 Py_DECREF()에는 객체가 먼저 비어 있는지 확인하는 버전인 Py_XINCREF() 및 Py_XDECREF()도 있습니다.
      확장 프로그램을 컴파일하는 프로그래머는 코드가 다중 스레드 Python 환경에서 실행될 수 있음을 알고 있어야 합니다. 이러한 스레드는 Py_BEGIN_ALLOW_THREADS 및 Py_END_ALLOW_THREADS라는 두 개의 C 매크로를 사용합니다. 스레드에서 코드를 분리하면 실행 및 비실행의 안전성이 보장됩니다. 이러한 매크로로 래핑된 코드는 다른 스레드가 실행되도록 허용합니다.

 


3. C/C++는 Python을 호출합니다.


       C++은 Python 스크립트를 호출할 수 있으므로 C++에서 호출할 Python 스크립트 인터페이스를 작성할 수 있습니다. 인터페이스. 단점은 C++ 프로그램이 한번 컴파일되면 변경하기가 그리 편리하지 않다는 것입니다.
(1) 파이썬 스크립트: 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) 실행 결과:

 

Supongo que te gusta

Origin blog.csdn.net/qq_38295645/article/details/124416630
Recomendado
Clasificación