Unix

boost python, 좀 더 복잡한 자료구조를 다뤄보자?

ForceCore 2014. 3. 10. 22:52

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가 아니라거나 하는 이상이 있을 것이다.