Post

python에서 c/cpp 코드 사용하기

목차

개요

방법

동적 라이브러리 빌드

C

libadd.c 코드

1
2
3
4
5
6
#include "libadd.h"

int add(int a, int b)
{
	return a + b;
}

libadd.h 코드

1
2
3
4
5
6
#ifndef LIBADD_H
#define LIBADD_H

int add(int a, int b);

#endif /* LIBADD_H */

빌드 진행

오브젝트 파일을 만들고

1
gcc -c -fPIC -I. -o libadd.o *.c

동적 라이브러리로 만든다.
리눅스는 so, 윈도우는 dll, 맥은 dylib 확장자를 사용한다.

1
gcc -shared libadd.o -o libadd.so

gcc 옵션 링크 : http://jangpd007.tistory.com/220
동적 라이브러리에 대한 설명 : https://nomad-programmer.tistory.com/105

test.py 라는 이름으로 파이썬 파일 생성 / 코드 작성 후
python3 test.py 명령어를 통해 실행

1
2
3
4
5
6
7
from os.path import dirname, abspath
import ctypes

CUR_PATH = dirname(abspath(__file__))

libc = ctypes.CDLL(CUR_PATH + "/libadd.so")
print(libc.add(1, 2))

C++

libprint.h 코드

libprint.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef LIBPRINT_H
#define LIBPRINT_H

#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <cstring>

class Foo
{
	char *var;
    public:
		Foo(char *var, int len);
		~Foo();
		void bar();
};

extern "C" {
	Foo* Foo_new(char *str, int len);
	void Foo_bar(Foo* foo);
	void Foo_del(Foo* foo);

	void print_str(char *str);
}

#endif /* LIBPRINT_H */

libprint.cpp 코드

libprint.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include "libprint.h"


Foo::Foo(char *var, int len) {
	this->var = (char *)std::calloc(len + 1, sizeof(char));
	std::memcpy(this->var, var, len);
}

Foo::~Foo() {
	if (this->var != NULL) {
		free(this->var);
	}
}

void Foo::bar() {
	std::cout << "Hello + var : " << this->var << std::endl;
}


extern "C" Foo *Foo_new(char *var, int len)
{
	// return nullptr;
    return new Foo(var, len);
}

extern "C" void Foo_bar(Foo* foo) {
	foo->bar();
}

extern "C" void Foo_del(Foo* foo) {
	foo->~Foo();
}

extern "C" void print_str(char *str)
{
	if (NULL == str) {
		std::cout << "string is NULL" << std::endl;
		return ;
	}
	std::cout << "string is " << str << std::endl;
	printf("string is %s\n", str);
}


빌드 진행

1
2
g++ -c -fPIC -I. -o libprint.o *.cpp
g++ -shared libprint.o -o libprint.so

test.py 파일 생성 및 코드 작성 및 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from os.path import dirname, abspath
import ctypes

CUR_PATH = dirname(abspath(__file__))

libcpp = ctypes.CDLL(CUR_PATH + "/libprint.so")
string = "Hello World"
libcpp.print_str(ctypes.c_char_p(bytes(string, "utf-8")))

var = libcpp.Foo_new(ctypes.c_char_p(string.encode()), len(string))

# nullptr 리턴 시 var의 값은 0이 된다.
if var is 0:
    print("Error alloc C++ class")
    exit(1)

libcpp.Foo_bar(var)

libcpp.Foo_del(var)
del var

자료형

C/C++의 함수 Parameter로 넘기기 위해서는 해당 자료형을 명시해줘야 한다.

https://docs.python.org/3/library/ctypes.html#fundamental-data-types 해당 페이지에서 자료형을 확인할 수 있다.

char * 자료형을 넘기기 위해서는 ctypes.c_char_p를 사용한다.

Array 등의 파이썬 리스트를 넘기는 것이 상당히 불편하다는 것을 알 수 있다.
참조 : https://devocean.sk.com/blog/techBoardDetail.do?ID=163835

또한 잘못된 Argument를 넘기거나 C/C++ 특성상 공유 라이브러리 내 로직 등의 문제가 생길 경우 아래 메시지와 같은 Segmentation Fault 등으로 파이썬 스크립트가 사망할 수 있으니 사용에 있어 각별한 주의가 필요하다.

[1] 5100 segmentation fault ./libadd.py

This post is licensed under CC BY 4.0 by the author.