Python과 C 혹은 C++ 연동 사용하기

2024. 8. 27. 11:19프로그래밍

Python을 사용하다보면 어떠한 목적으로 C혹은 C++ 쪽에서 작성한 코드와 연동하여 사용할 필요가 있다.

 

이는 성능이슈로 C, C++를 사용하든, 아니면 python 코드의 경우 코드를 완벽하게 숨길 수 없어 숨겨야하는 핵심 코드가 있든...

 

아무튼 C, C++ 와 Python 간의 연동을 위한 ctypes 라는 모듈이 있는데, 기본적으로 C 로 작성된 코드들을

Python script에서 사용할 수 있도록 해준다.

 

자세히는 아래 url을 참고한다.

https://docs.python.org/ko/3/library/ctypes.html

 

 

아래는 C/C++ 모듈에서 JPG 파일을 읽은 후 디코딩한 RGB 이미지를 Python으로 전달해서 

화면에 표시하기 위해서 PIL 모듈의 Image 객체를 만드는 예제이다.


이를 통해 Structure 자료구조를 python과 C 모듈에서 공유하고, C 모듈 함수 호출하는 것과 

C/C++에서 잡은 byte array를 python에서 사용하는 것과 반대로 Python에서 잡은 byte array를 C/C++에 전달하는 것에 대해서  구체적인 예제를 작성하였다.

 

Python 코드

from ctypes import *
import Image, ImageTk  # PIL module로 Python설치시 기본 설치되는 모듈은 아니고, 
                       # jpg, png 등 각종 이미지 포맷과 이미지 관련 함수 제공

lib = cdll.LoadLibrary('C:/Devel/MyDLL/my.dll')

class ImageInfo(Structure) :
    _fields_ = (
        ('_width', c_uint),
        ('_height', c_uint),
        ('_depth', c_uint),
        ('_name', c_char_p),
        ('_data', POINTER(c_ubyte)),
    )    # C 와 공유될 자료구조 선언

pImageInfo = POINTER(ImageInfo)
lib.getJpgImage.argtypes = [pImageInfo]    # C모듈에서 제공되는 getJpgImage 함수 인자들의 type과 
lib.getJpgImage.restype = c_int            #    return type
lib.freeJpgImage.argtypes = [pImageInfo]   #
lib.freeJpgImage.restype = c_int           #

imgInfo = ImageInfo()
fileName = "C:/Devel/Fisheye/Fisheye_ceil_down.jpg"
imgInfo._name jj= c_char_p(fileName)
lib.getJpgImage(imgInfo)                         # C모듈 getJpgImage 함수 호출
w,h,d = imgInfo._width, imgInfo._height, imgInfo._depth
print "original image - %d x %d (%d)" % (w,h,d)

# C모듈에서 만든 byte array인 imagInfo._data를  python array 변수형으로 변경하는 부분, 
# Image 모듈에서는 (h, w, rgb) 3차원 array 형태여야함.
# (c_ubyte*3*w*h) 는 3차원 array 로 정의되는데 3*w*h 로 반대순서 유의
tmp = cast(imgInfo._data, POINTER(c_ubyte*3*w*h)).contents  
oriImage = Image.frombuffer('RGB', (w, h), tmp, 'raw', 'RGB', 0, 1)
print "Image is created"

# Python에서 만든 byte array를 C모듈로 전달하고자할 경우
#    역시 cast를 이용하여 c_ubyte pointer를 전달하면 된다.
imgInfo2 = ImageInfo()
imgData = (w*h*d*ctypes.c_ubyte)()
imgInfo2._data = cast(imgData, POINTER(c_ubyte)) 
...

 

C++ 코드 - PythonWrap.h

#ifndef _PYTHON_WRAP_H_
#define _PYTHON_WRAP_H_

typedef struct _ImageInfo {
    unsigned int _width;
    unsigned int _height;
    unsigned int _depth;
    char* _name;
    unsigned char* _data;
} ImageInfo, *pImageInfo;

#endif // _PYTHON_WRAP_H_

 

C++ 코드 - PythonWrap.cpp

extern "C" {   // C++ 모듈이라도 Python 과는 C 함수로 공유되어야 한다.

__declspec(dllexport) int __cdecl getJpgImage(pImageInfo pInfo)
{
    if (pInfo->_name == NULL) {
        return -1;
    }
    unsigned char* data = jpgLoad(pInfo->_name, &pInfo->_width, &pInfo->_height);
    if (data == NULL) {
        return -1;
    }
    pInfo->_depth = 3; // always rgb 24bit
    pInfo->_data = (unsigned char*) malloc(pInfo->_width*pInfo->_height*pInfo->_depth);
    if (pInfo->_data == NULL) {
         jpgFree();
         return -1;
    }
    memcpy(pInfo->_data, data, pInfo->_width*pInfo->_height*pInfo->_depth);
    return 0;
}

__declspec(dllexport) void __cdecl freeJpgImage(pImageInfo pInfo) 
{
    if (pInfo->_data) {
        free (pInfo->_data);
        pInfo->_data = NULL;
    }
    pInfo->_width = 0;
    pInfo->_height = 0;
    pInfo->_depth = 0;
    }
}

 

Python쪽에서 ctypes.Structure에 pointer가 아닌 실제 array를 사용 예저는 아래와 같다. c_float*10 면 array가 되는데, 이때 혹시 c_float*(5*2) 처럼 * 연산을 통해 array 크기를 지정할 때, 꼭 괄호를 하든지 아니면 5*2*c_float 처럼 숫자를 앞 적어야 1차원 array가 된다. c_float*5*2 처럼 괄호 없이 * 를  c_float같은 형 뒷쪽에 사용하면 2차원 array가 되니 조심해야한다. 이때 row와 column은 순서가 반대가 되는데, 즉 c_float*5*2 로 하면 row가 2 이고 column 이 5인 2차원 행렬이 된다.

# Python code
class TestStruct(Structure) :
    _fields_ = [
        ('_a',c_int),
        ('_b',c_int),
        ('_c',c_float*(5*2)) #주의! c_float*5*2 하면 2차원 array
                             # 5*2*c_float 도 1차원, c_float*(5*2)도 1차원 array, 
    ]

위에 대응되는 C나 C++ 코드는

// C, C++ 코드
struct TestStruct {
    int _a;
    int _b;
    float _c[2*5];
};

Windows에서 DLL 방식으로 작성된 C++ 모듈의 예인데, Linux에서는 SO 형태로 작성된 모듈도 동일한 방식으로 연동될 수 있다.

Windows에서 주의점은 아래와 같은 모듈 정의 파일 my.def를 만들고 프로젝트 속성에서 링커 > 입력 >  모듈 정의 파일 에 지정해야 한다.

; my.def : Declares the module parameters for the DLL.
; LIBRARY "my"

EXPORTS
    getJpgImage
    freeJpgImage