파이썬 마을 게시판 인덱스 파이썬 마을
우리나라 파이썬 사용자들의 이야기 마을
 
 FAQFAQ   검색검색   멤버리스트멤버리스트   사용자 그룹사용자 그룹   사용자 등록하기사용자 등록하기 
 개인 정보개인 정보   비공개 메시지를 확인하려면 로그인하십시오비공개 메시지를 확인하려면 로그인하십시오   로그인로그인 
Google
python.or.kr Web

[SWIG강좌] 3.4 Typemap (4)

 
글 쓰기   답변 달기    파이썬 마을 게시판 인덱스 -> 파이썬 팁/강좌/모듈소개 모음
이전 주제 보기 :: 다음 주제 보기  
글쓴이 메시지
jrcho



가입:
올린 글: 56

올리기올려짐: 2004 5월 12 7:38 pm    주제: [SWIG강좌] 3.4 Typemap (4) 인용과 함께 답변

3.4.5. Typemap 예

이제 typemap을 한 예를 들기로 한다. 이 예들을 이해하고, typemaps.i의 코드를 분석하면 자신이 원하는 typemap을 직접 작성할 수 있다.

(1) "in" typemap 기초
첫 번째 예로 C/C++ int 형의 포인터를 함수의 인자로 받는 경우에 대한 “in" typemap을 작성해 볼 것이다. 이 경우 표준 typemap 라이브러리(typemaps.i)를 사용해도 된다. 하지만, 이 예제를 통해 temporary 변수의 필요성과 typemap의 원리에 대해 자세히 알수 있고, 다른 typemap 작성할 때 확장하여 사용할 수 있다. 예를 들어

int add(int* a, int* b)

와 같은 C/C++ 함수가 있다고 가정해 보자. 이 함수의 인자는 int 형의 포인터로 선언되어 있다. 이를 해결하기 위해 “in" typemap을 사용해야 한다. 인터페이스파일(Example/input)은 다음과 같다.

코드:

%module example
%{
  int add(int* a,int* b){ return (*a)+(*b); }
%}

%typemap(in) int* a(int temp){

  if(!PyInt_Check($input)){
    PyErr_SetString(PyExc_TypeError,"Input argument must be integer");
    return NULL;
   }

  temp = PyInt_AsLong($input);
  $1 = &temp;
}

%apply int* a {int *b};      // copy int* a to int* b

int add(int* a,int* b);


위에서, typemap의 선언은 %typemap(in) int* a(int temp){..}와 같다. 이 선언은 int* 형의 a라는 인자명를 입력할 때 int temp의 임시변수로 {...}안의 변환 코드를 사용한다는 뜻이다. $input은 입력된 Python객체 PyObject*의 객체를 의미하며, 먼저 PyInt_Check() 함수로 입력된 Python 객체가 정수형인지 검사한다. 그리고 나서, temp = PyInt_AsLong($input)에서 C/C++ long형으로 변환한 후 temp 변수에 입력한다. 다시 temp 변수의 어드레스를 C/C++ 함수의 첫 번째 인자를 나타내는 특수 변수 $1 에 대입한다. 실제로 생성되는 wrapping code는 다음과 같다.

코드:

static PyObject *_wrap_add(PyObject *self, PyObject *args) {
    PyObject *resultobj;
    int *arg1 = (int *) 0 ;
    int *arg2 = (int *) 0 ;
    int result;
    int temp1 ;
    int temp2 ;
    PyObject * obj0 = 0 ;
    PyObject * obj1 = 0 ;
   
    if(!PyArg_ParseTuple(args,(char *)"OO:add",&obj0,&obj1)) goto fail;
    {
        if(!PyInt_Check(obj0)){
            PyErr_SetString(PyExc_TypeError,"Input argument must be integer");
            return NULL;
        }
       
        temp1 = PyInt_AsLong(obj0);
        arg1 = &temp1;
    }
    {
        if(!PyInt_Check(obj1)){
            PyErr_SetString(PyExc_TypeError,"Input argument must be integer");
            return NULL;
        }
       
        temp2 = PyInt_AsLong(obj1);
        arg2 = &temp2;
    }
    result = (int)add(arg1,arg2);
   
    resultobj = PyInt_FromLong((long)result);
    return resultobj;
    fail:
    return NULL;
}


위의 코드를 잘 살펴보면, temp1, temp2라는 임시변수가 typemap이 치환되는 {...} 바깥에 정의되는 local 변수임을 알 수 있다. 만약 이와 같은 임시변수 없이 *arg1 = PyInt_AsLong(obj0);와 같은 코드가 생성된다면, arg는 단수한 포인터이므로 문제가 발생할 것이다. 이것이 임시변수가 필요한 이유이다.

(2) "argout" typemap 기초
두 번째 예로 표준 typemap 라이브러리(typemaps.i)의 OUTPUT이란 인자명으로 사용가능한 typemap을 작성해 보기로 한다. 이 예는 함수의 인자로 포인터이고, 이 포인터에 리턴값을 받아오는 경우이다. 예를 들어

void add(int a,int b,int* result);

와 같은 C/C++ 함수가 있다고 하자. result는 int 형의 포인터로 선언되어 있고 결과값을 받아온다. 인터페이스 파일(example/output)은 다음과 같다.

코드:

%module example
%{
   void add(int a,int b,int* result){ *result = a + b; }
%}

%typemap(in,numinputs=0) int* result(int temp) "$1 = &temp;";
%typemap(argout) int* result{
    PyObject *o, *o2, *o3;
    o = PyInt_FromLong(*$1);
    if ((!$result) || ($result == Py_None)) {
        $result = o;
    } else {
        if (!PyTuple_Check($result)) {
            PyObject *o2 = $result;
            $result = PyTuple_New(1);
            PyTuple_SetItem($result,0,o2);
        }
        o3 = PyTuple_New(1);
        PyTuple_SetItem(o3,0,o);
        o2 = $result;
        $result = PySequence_Concat(o2,o3);
        Py_DECREF(o2);
        Py_DECREF(o3);
    }
}


"argout" typemap은 보통 “in" typemap과 함께 사용된다. ”in" typemap에서 numinputs=0는 입력값을 무시하라는 것이고, C/C++ 함수를 호출하기 전에 인자를 local 변수 temp를 가르키도록 설정한다. "argout" typemap은 먼저 이전 결과가 존재하는 지 검사한다. 만약 존재한다면, 이전 결과를 튜플로 변환하고 새로운 출력값이 concatenate한다. 존재하지 않는 다면 정상적으로 리턴할 것이다. 이와 같이 하는 이유는 int spam(int a,int b,int* result1,int* result2);와 같이 하나이상의 결과가 있을 때 그 결과를 Python에서 받아오기 때문이다(Python에서의 사용은 (n,result1,result2) = spam(a,b)와 같이 3-tuple로 받아온다.). 이제 add() 함수의 Python 에서의 사용은

>>> a = add(1,2)

와 같다. 표준 typemap인 typemaps.i의 헬퍼함수 t_output_helper를 사용하면 위의 typemap을 다음과 같이 간단히 쓸 수 있다.

코드:

%include "typemaps.i"
%typemap(in,numinputs=0) int* result(int temp) "$1 = &temp;";
%typemap(argout,fragment="t_output_helper") int* result{
   PyObject *o = PyInt_FromLong(*$1);
   $result = t_output_helper($result,o);
}


위에서 t_output_helper() 함수는 이전 결과와 concatenation을 수행하고 tuple로 묶는 역할을 한다.

(3) Python Sequence I : Python 문자열 리스트를 C/C++의 char **로 변환하기
표준 typemap인 typemaps.i를 이용해 사용할 수 있는 built-in 자료형을 제외하고 직접 typemap을 작성하는 대부분의 경우는 Python의 리스트나 튜플 같은 시퀀스 자료형으로 변환하는 경우이다. 여기에서는 Python에서 문자열의 리스트를 C/C++에서 char**로 선언된 문자열에 대한 배열로 변환하는 입력 typemap의 예를 보일 것이다. 보통 C/C++에서 문자열의 배열은 다음과 같이 함수의 인자로 사용된다.

void print_arg(char **argv, int argc);

argv[0], ... , argv[argv-1]는 문자열이고, argc는 argv 배열의 크기이다. 이제 Python에서

>>> print_arg(["Dave","Dan","Mary"])

와 같이 사용하려고 한다고 가정하기로 한다. 인터페이스파일(example/string)은 다음과 같다.

코드:

%module example
%{
  void print_arg(char **argv, int argc){
    for(int i=0;i<argc;i++) printf("%s\n",argv[i]);
  }
%}


%typemap(in) (char **argv, int argc){
  /* Check if it is a list */
  if(PyList_Check($input))
  {
    int i;
    $2 = PyList_Size($input);
    $1 = (char **) malloc(($2+1)*sizeof(char *));
    for (i = 0; i < $2; i++)
    {
      PyObject *o = PyList_GetItem($input,i);
      if (PyString_Check(o))
        $1[i] = PyString_AsString(PyList_GetItem($input,i));
      else {
        PyErr_SetString(PyExc_TypeError,"list must contain strings");
       free($1);
       return NULL;
      }
    }
    $1[i] = 0;
  }
  else
  {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

%typemap(freearg)(char **argv, int argc){
  free((char *) $1);
}

void print_arg(char **argv, int argc);


위에서 multi-argument typemap을 사용하였다. “in" typemap은 입력 인자를 받는데 사용되어 이를 C 배열로 변환한다. 동적 메모리 할당이 이 배열에 사용되었기 때문에 ”freearg" typemap이 C 함수의 실행이 끝날 때 메모리 할당을 해제하는 데 사용된다.

(4) Python Sequence Ⅱ : Python 튜플을 C/C++ small array로 변환하기

경우에 따라 작은 배열로 인자를 넘길 경우가 있다. 예를 들어

void set_direction(double a[4]); // Set direction vector

이 경우 다음과 같이 typemap을 사용할 수 있다.

코드:

// Grab a 4 element array as a Python 4-tuple
%typemap(in) double[4](double temp[4]){  // temp[4] becomes a local variable
  int i;
  if (PyTuple_Check($input)) {
    if (!PyArg_ParseTuple($input,"dddd",temp,temp+1,temp+2,temp+3)) {
      PyErr_SetString(PyExc_TypeError,"tuple must have 4 elements");
      return NULL;
    }
    $1 = &temp[0];
  } else {
    PyErr_SetString(PyExc_TypeError,"expected a tuple.");
    return NULL;
  }
}

void set_direction(double a[4]);       // Set direction vector


이제 Python에서 다음과 같이 사용할 수 있다.

>>> set_direction((0.5,0.0,1.0,-0.25))

위 예제는 Python tuple의 내용을 C 배열로 복사한다. 이 예는 큰 배열을 다룰 때는 좋지 못하지만, 작은 배열을 다룰 때 유용하다.

(5) Python Sequence Ⅲ : Python 시퀀스를 C/C++ array로 변환하기
다른 크기의 C 배열을 다루는 예를 보이겠다. 이 예의 앞 예의 일반화된 경우이다.

코드:

// Map a Python sequence into any sized C double array
%typemap(in) double[ANY](double temp[$1_dim0])
  int i;
  if (!PySequence_Check($input)) {
      PyErr_SetString(PyExc_TypeError,"Expecting a sequence");
      return NULL;
  }
  if (PyObject_Length($input) != $1_dim0) {
      PyErr_SetString(PyExc_ValueError,"Expecting a sequence with $1_dim0 elements");
      return NULL;
  }
  for (i =0; i < $1_dim0; i++) {
      PyObject *o = PySequence_GetItem($input,i);
      if (!PyFloat_Check(o)) {
         PyErr_SetString(PyExc_ValueError,"Expecting a sequence of floats");
         return NULL;
      }
      temp[i] = PyFloat_AsDouble(o);
  }
  $1 = &temp[0];
}


$1_dim0 변수는 C 코드에서 실제 사용된 배열의 디멘젼과 일치하도록 확장된다. 이 typemap을 사용하면 다음과 같은 배열에 적용가능하다. as:

void foo(double x[10]);
void bar(double a[4], double b[8]);

위 typemap은 모든 wrapper function에 삽입된다면, helper function 으로 만드는 것이 좋다. 이것을 통해 wrapper code의 양을 상당히 줄일 수 있다. 예를 들어,

코드:

%{
static int convert_darray(PyObject *input, double *ptr, int size){
  int i;
  if (!PySequence_Check(input)) {
      PyErr_SetString(PyExc_TypeError,"Expecting a sequence");
      return 0;
  }
  if (PyObject_Length(input) != size) {
      PyErr_SetString(PyExc_ValueError,"Sequence size mismatch");
      return 0;
  }
  for (i =0; i < size; i++) {
      PyObject *o = PySequence_GetItem(input,i);
      if (!PyFloat_Check(o)) {
         PyErr_SetString(PyExc_ValueError,"Expecting a sequence of floats");
         return 0;
      }
      ptr[i] = PyFloat_AsDouble(o);
  }
  return 1;
}
%}

%typemap(in) double [ANY](double temp[$1_dim0]){
   if(!convert_darray($input,temp,$1_dim0))){
     return NULL;
   }
   $1 = &temp[0];
}


(6) Memory handling
SWIG는 Proxy 클래스의 thisown flag을 이용해 underlying C++ 객체의 생성과 파괴를 관리한다. “3.3.2 Object Ownership & Memory Management”에서 설명한 디폴트 행동을 typemap을 이용하여 이를 변화시킬 수 있다. 이를 위해서 SWIG는 포인터의 변환과 관련된 두 개의 함수를 제공한다.

int SWIG_ConvertPtr(PyObject *obj, void **ptr, swig_type_info *ty, int flags)
Python 객체 obj 를 C 포인터로 변환하여 ptr에 저장한다. ty 는 SWIG type descriptor structure이고, flag은 SWIG_POINTER_EXCEPTION와 SWIG_POINTER_DISOWN flag의 bitwise-or이다. SWIG_POINTER_EXCEPTION이 설정되어 있으면 함수가 type error에 대한 exception을 raise하도록 하고, SWIG_POINTER_DISOWN이 설정되어 있으면 Python 객체 obj의 ownership을 steal한다(즉, thisown을 0으로 설정). 변환이 성공하면 0, 실패하면 -1을 리턴한다.

PyObject *Swig_NewPointerObj(void *ptr, swig_type_info *ty, int own)
새로운 Python pointer object를 생성한다. ptr은 변환될 포인터이고, ty는 SWIG type descriptor structure이다. own은 생성되는 Python 객체가 ptr 포인터가 가르키는 underlying C++ 객체의owneship을 가질 것인지 말 것인지를 결정한다.

이제 실제적인 예(example/Car)를 이용해 어떻게 이용하는 지를 살펴보기로 하자.

코드:

%module example
%{
class Engine
{
public:
  Engine(){printf("Engine class constructor\n");}
  ~Engine() { printf("Engine class destructor\n");}
};

class Engineer
{
public:
  Engine* createEngine() { Engine *e = new Engine; return e; }
};

class Car
{
public:
  Car() : engine(0) { }
  void addEngine(Engine* e){engine = e;}
  const Engine* getEngine(){return engine; }
  ~Car(){ if(engine) delete engine; }
private:
  Engine* engine;
};


%}

%typemap(out) Engine* {
  $result = SWIG_NewPointerObj((void *) $1, $1_descriptor, 1);
}

%typemap(in) Engine* {
  if(SWIG_ConvertPtr($input,(void **) &$1, $1_descriptor,
                 SWIG_POINTER_EXCEPTION|SWIG_POINTER_DISOWN) == -1)
    return NULL;
}


class Engine
{
public:
  Engine();
  ~Engine();
};

class Engineer
{
public:
  Engine* createEngine();
};

%typemap(out) Engine* ;    //getEngine에 적용되지 않도록 typemap 삭제

class Car
{
public:
  Car();
  void addEngine(Engine* e);
  const Engine* getEngine();
  ~Car();
};


typemap의 작성시 $1_descriptor라는 특수변수를 사용하였는데, SWIG_NewPointerObj(), SWIG_ConvertPtr() 함수의 swig_type_info 구조체에 들어가는 데이터 형의 정보를 가지고 있다. 위 예에서 Engine, Engineer, Car라는 3개의 클래스가 있다. Engineer가 createEngine() 메쏘드로 Engine 객체를 생성할 수 있고, 이를 Car 객체에 addEngine() 메쏘드로 추가한다. 이때 추가된 Engine 객체를 소거할 책임은 Car에 있다. Car 객체는 getEngine()과 같이 내부 엔진 객체를 억세스하는 함수를 가지고 있다.
createEngine()으로 생성된 Python 객체는 thisown = 1로 설정되어야 하고, getEngine()으로 반환되는 Python 객체는 thisown = 0이어야 한다. SWIG 디폴트로 생성되는 코드는 반환되는 Python 객체의 thisown을 0으로 설정하기 때문에 createEngine()을 사용할 때는 thisown을 외부에서 1로 설정해 주어야 한다. 이를 해결하기 위해 위의 예에서 "out" typemap을 사용한 것이다.
“in" typemap은 addEngine() 함수를 위한 것이다. addEngine()으로 추가된 Engine 객체는 Car 클래스가 소거할 책임이 있으므로, 추가되는 Engine 객체는 ownership을 잃어야 한다. 만약 ownership을 유지한다면, Engine에 대한 Python Proxy 객체가 소거될 때 underlying C++ 객체 역시 소거될 것이고, Car 역시 소거될때 Engine의 C++ 객체를 소거하여, Engine 객체에 대해 두 번 소거하는 일이 생기기 때문이다.

>>> john = Engineer()
>>> car = Car()
>>> turboEngine = john.createEngine()
Engine class constructor
>>> car.addEngine(turboEngine)
>>> turboEngine.thisown
0
>>> engine = car.getEngine()
>>> engine
<C Engine instance at _80bf8000_p_Engine>
>>> engine.thisown
0
>>> del car
Engine class destructor
위로
사용자 정보 보기 비밀 메시지 보내기    
이전 글 표시:   
글 쓰기   답변 달기    파이썬 마을 게시판 인덱스 -> 파이썬 팁/강좌/모듈소개 모음 시간대: GMT + 9 시간(한국)
페이지 11

 
건너뛰기:  
새로운 주제를 올릴 수 없습니다
답글을 올릴 수 없습니다
주제를 수정할 수 없습니다
올린 글을 삭제할 수 없습니다
투표를 할 수 없습니다



Powered by phpBB © 2001, 2005 phpBB Group
회선/장비: Daum DNA , 관리: 장혜식,서상현