StrPairs 라는 클래스다.
#pragma once
#ifndef _STRPAIRS_H_
#define _STRPAIRS_H_
#include "util.h"
class StrPairs
{
public:
StrPairs();
virtual ~StrPairs();
public:
int get_str_index( const char *str );
str_pair &at( const int i );
str_pair &operator[]( const int i );
void add_pair( const char *name, const char *value );
void print();
void read_from_file( const char *fname );
void clear();
public:
str_pair table[MAX_ENTRIES];
int sz; // number of entries in the table
};
#endif // _STRPAIRS_H_
사실 이 class자체로도 wrapper다. C로된 라이브러리의 structure를 C++ wapper class화 한 것이다. 순수 C에서는 StrPairs 안에 들어 있는 str_pair table[MAX_ENTRIES]; 이것을 여러군데서 읽고 쓰고 하고 있다. Python으로 치면 dict 역할을 해서 configuration을 저장하기 위해 만들어졌다.
그냥 C로 된 것을 함수를 적절히 써서는 내 C++ 프로그램에서는 다루기 불편해서 위와 같이 클래스로 만들어야 했다. 그리고 그 프로그램이 더 확장되어서 Python Console도 지원해야 한다 (!). Python에서 쉽게 configuration을 해서 쓰기 위한 용도니 반드시 짚고 넘어가야 할 산이다. (처음 해보면 저게 산이라는)
typedef struct str_pair_st
{
char name[STR_SIZE];
char value[STR_SIZE];
}str_pair;
str_pair는 역시 순수 C용이라서 이렇게 생겼다. std::string보다 좀 까다로운 녀석이다. 정신만 차리면 어렵지 않지만.
Python에 노출시킬 부분에 대한 지정은 cpp파일에 두어야한다. 헤더파일에 두면 잘 안 될 것이다. cpp파일은 다 첨부하지 않는다. 알 사람은 알 만큼 간단하게 되어있다. strcmp로 linear search 해서 item을 찾는 단순 무식한 방식이다. 자료를 저장할땐 strcpy씀. 이제 중요한 부분.
파일의 맨 끝에다 다음을 넣었다:
#include <boost/python.hpp>
using namespace boost::python;
string str_pair_get_name( const str_pair &p )
{
string result( p.name );
return result;
}
void str_pair_set_name( str_pair &p, const string &s )
{
strcpy( p.name, s.c_str() );
}
string str_pair_get_value( const str_pair &p )
{
string result( p.value );
return result;
}
void str_pair_set_value( str_pair &p, const string &s )
{
strcpy( p.value, s.c_str() );
}
BOOST_PYTHON_MODULE( StrPairs )
{
class_<str_pair_st>("str_pair_st")
.add_property( "name", &str_pair_get_name, &str_pair_set_name )
.add_property( "value", &str_pair_get_value, &str_pair_set_value )
;
class_<StrPairs>("StrPairs")
.def( "get_str_index", &StrPairs::get_str_index )
.def( "at", &StrPairs::at, return_value_policy<reference_existing_object>() )
.def( "clear", &StrPairs::clear )
.def( "print", &StrPairs::print )
.def( "add_pair", &StrPairs::add_pair )
;
}
어려웠던 부분은 struct인 녀석인 str_pair에는 member 변수 함수가 없다는 점이다. 그래서 get/set 위주로 설명되어 있는 매뉴얼을 읽어서는 add_property에 getter/setter function을 저렇게 짜야 한다는 것을 유추하기 어렵다. Class로 치면 friend 함수인 녀석들을 저렇게 getter, setter로서 적으면 된다. Setter 함수에 실수로 return type을 void로 두지 않으면 Python에서 실행시켰을 때 segmentation fault가 나니 주의해야 한다.
사용할 때에는
from StrPairs import str_pair_st, StrPairs
이렇게 import하게 된다는 것을 염두에 두라.
BOOST_PYTHON_MODULE( StrPairs ) 가 import 할 모듈을 표현하는 것이고 그 안에 든 class_<어쩌구> 가 import 대상이 된다.
.def 에는 python에서 보일 함수 이름과, 어떤 C++함수와 대응할지를 적어주는 것으로 보인다. 그리고 말 그대로 이름만 적으면 되니 parameter형을 어떻게 적어야 할까를 고민할 필요가 없다.
at()함수가 제일 어려운 부분이다. 나머지는 그저 그런 난이도이다. 왜 어려운가.
1) str_pair 에 대한 reference를 리턴하는 함수다!!
2) 리턴값은 파이선이 어떻게 해야 하나? copy하나? 그런 정책도 정해줘야 한다.
1)은 str_pair wrapper를 잘 짜두면 걱정하지 않아도 좋다, 의외로.
2)는 고민을 해봐야 한다.
http://www.boost.org/doc/libs/1_39_0/libs/python/doc/v2/reference_existing_object.html
볼드처리한 부분인데. reference_existing_object가 이번 예제에서의 정답이다. .at으로 가져온 녀석을 변경도 할 것이니까.
테스트.
path=../obj/Debug
FLAGS=`python3-config --includes` `python3-config --libs` -lboost_python3
StrPairs.so: $(path)/src/StrPairs.o $(path)/src/strutils.o
$(CXX) -shared -fPIC $(FLAGS) $< $(path)/src/strutils.o -o $@
clean:
rm -f StrPairs.so
이렇게 빌드 한다. StrPairs.so가 생긴다. StrPairs.so가 있는 곳에서 다음 파이선 코드를 실행해본다.
#!/usr/bin/python3
from StrPairs import StrPairs, str_pair_st
p = StrPairs()
print( p )
print( "add pair test" )
p.add_pair( "jungmoon", "choi" )
p.add_pair( "soongyu", "sosi" )
p.print() # 목록의 것이 다 보여야 함. C++로 짠 print()에 의해.
print()
print( "clear test" )
p.clear()
p.add_pair( "fei", "missA" )
p.print() # 다 날아가고 하나만 보여야 함
print()
print("pair.name test")
pair = p.at( 0 )
print( pair.name ) # fei가 보여야 함
print()
print("name set test" )
print( pair.name )
pair.name = "min"
p.print() # min으로 벼해있어야 함.
계획대로 잘 되었다면 다 잘 될 것이다. C++ 코드는 이상 없는데 Segementation fault가 난다면 setter 함수에 리턴형이 void가 아니라거나 하는 이상이 있을 것이다.