 |
파이썬 마을 우리나라 파이썬 사용자들의 이야기 마을
|
|
| 이전 주제 보기 :: 다음 주제 보기 |
| 글쓴이 |
메시지 |
jrcho
가입: 올린 글: 56
|
올려짐: 2004 5월 12 7:03 pm 주제: [SWIG강좌] 3.3 C++에서의 사용 (1) |
|
|
3.3. C++에서의 사용
3.3.1. Class Basics
SWIG 실행결과로 생성되는 파일은 C/C++ wrapper code를 담은 low level wrapper와 Python 코드를 담은 high level wrapper 두가지이다. 이 두 파일을 쓰는 이유는 structure, union, class 등을 처리할 때 편리하게 하기 위해서이다. 여기에서는 구조체와 클래스 두가지 예를 들어 어떻게 SWIG가 파일을 생성하고 사용하는 지를 설명하겠다. 먼저 다음과 같은 간단한 구조체의 예를 들어보자.
struct Vector{
double x,y,z;
};
SWIG 인터페이스파일은
| 코드: |
// example.i
%module example
%{
struct Vector{
double x,y,z;
};
%}
struct Vector{
double x,y,z;
};
|
이다. SWIG의 실행은
> swig -python example.i
이고, 실행결과 example.py, example_wrap.c이다. 다음과 같은 setup.py를 이용해
| 코드: |
# setup.py
from distutils.core import setup, Extension
setup(name="example",
py_modules = ["example"],
ext_modules=[Extension("_example",["example_wrap.c"])]
)
|
Python 인터프리터에서 사용할 수 있도록 다음과 같이 인스톨한다.
> python setup.py install
이러한 인스톨을 통해 Python에 인스톨되는 파일은 example.py와 _example.pyd이다. 이중에서 example.py는 SWIG실행 결과로 나오는 high level wrapper이고, _example.pyd는 low level wrapper를 DLL로 만든 것이다. 둘다 모듈로 Python에서 임포트가 가능하다. 먼저 example.py를 임포트해서 사용하는 예는 다음과 같다.
>>> from example import *
>>> a = Vector()
>>> print a
<C Vector instance at _a8228700_p_Vector>
>>> a.x = 1.
>>> print a.x
1.0
>>> del a
>>>
Low level wrapper를 직접 임포트해서 사용하는 예는 다음과 같다.
>>> from _example import *
>>> a = new_Vector()
>>> print a
'_d01d7f00_p_Vector'
>>> Vector_x_set(a,2)
>>> print Vector_x_get(a)
2.0
>>> delete_Vector(a)
>>>
위 예들에서 example.py를 임포트한 경우 마치 Python 클래스를 사용하듯이 사용할 수 있음을 알 수 있을 것이다. 실제로 SWIG는 Wrapper 코드를 생성할 때 low level wrapper를 사용한 예와 같이 구조체의 각 멤버를 억세스하는 함수(accessor 함수, 즉, get/set 함수)와 메모리 할당/해제와 관련된 함수들을 생성한다. 그리고 SWIG에서 생성된 high level wrapper를 이들 함수를 이용해 Python 클래스와 같이 사용할 수 있도록 해준다. 이렇게 생성된 클래스를 Proxy class라 한다. 다음은 lower level wrapper의 일부와 high leve wrapper의 일부 코드를 나타낸 것이다. 자세히 살펴보면, SWIG에서 구조체를 처리하는 원리에 대해 이해할 수 있을 것이다.
| 코드: |
/* example_wrap.c : low level wrapper */
#include <Python.h>
...
struct Vector{
double x,y,z;
};
static PyObject *_wrap_Vector_x_set(PyObject *self, PyObject *args) { ... }
static PyObject *_wrap_Vector_x_get(PyObject *self, PyObject *args) { ... }
static PyObject *_wrap_Vector_y_set(PyObject *self, PyObject *args) { ... }
static PyObject *_wrap_Vector_y_get(PyObject *self, PyObject *args) {...}
static PyObject *_wrap_Vector_z_set(PyObject *self, PyObject *args) {...}
static PyObject *_wrap_Vector_z_get(PyObject *self, PyObject *args) {...}
static PyObject *_wrap_new_Vector(PyObject *self, PyObject *args) {...}
static PyObject *_wrap_delete_Vector(PyObject *self, PyObject *args) {...}
# example.py : high level wrapper
import _example
...
class Vector(_object):
def __repr__(self):
return "<C Vector instance at %s>" % (self.this,)
__swig_setmethods__["x"] = _example.Vector_x_set
__swig_getmethods__["x"] = _example.Vector_x_get
if _newclass:x = property(_example.Vector_x_get, _example.Vector_x_set)
__swig_setmethods__["y"] = _example.Vector_y_set
__swig_getmethods__["y"] = _example.Vector_y_get
if _newclass:y = property(_example.Vector_y_get, _example.Vector_y_set)
__swig_setmethods__["z"] = _example.Vector_z_set
__swig_getmethods__["z"] = _example.Vector_z_get
if _newclass:z = property(_example.Vector_z_get, _example.Vector_z_set)
def __init__(self, *args):
_swig_setattr(self, Vector, 'this', _example.new_Vector(*args))
_swig_setattr(self, Vector, 'thisown', 1)
def __del__(self, destroy=_example.delete_Vector):
try:
if self.thisown: destroy(self)
except: pass
|
기본적으로 C++ 클래스는 C 구조체에 멤버함수가 추가하여 확장된 것이다. 따라서 acccesor 함수, 생성/소멸과 관련된 함수를 구조체와 동일하게 low level wrapper에 생성하고, 또한 멤버함수와 연관된 함수를 추가로 생성한다. 이러한 low level wrapper를 이용해 Python클래스와 같이 사용하게 할 수 있는 Proxy 클래스를 high level wrapper에 생성해 사용할 수 있도록 한다. 다음예를 보기 바란다. 앞서의 Vector구조체를 클래스로 선언하고, norm() 멤버함수를 추가한 경우이다.
| 코드: |
// example.i
%module example
%{
#include <math.h>
class Vector
{
public:
double norm(){ return sqrt(x*x+y*y+z*z);}
double x,y,z;
};
%}
class Vector
{
public:
double norm();
double x,y,z;
};
|
SWIG의 실행 및 인스톨은
> swig -python -c++ example.i
> python setup.py install
이고, setup.py를 다음과 같다.
| 코드: |
# setup.py
from distutils.core import setup, Extension
setup(name="example",
py_modules = ["example"],
ext_modules=[Extension("_example",["example_wrap.cxx"])]
)
|
low level wrapper인 example.wrap.cxx 파일에서 norm() 에 대한 wrapper 코드가 추가되었음을 확인할 수 있을 것이다:
static PyObject *_wrap_Vector_norm(PyObject *self, PyObject *args) {...}
그리고 다음은 Python에서 멤버함수를 이용한 예이다.
>>> a = Vector()
>>> a.x = 1.
>>> a.norm()
1.0
3.3.2. Constructors
SWIG에서 생성자를 처리하는 방법에 대해 설명하고자 한다. C++ 클래스는 보통 public으로 선언된 생성자를 하나이상 갖고 있다. 예를 들어, 다음과 같은 간단한 클래스의 경우
생성자의 Wrapping
| 코드: |
// example.i
%module example
%{
class A
{
public:
A(int a) { n = a;}
void write() { printf("class A: %d",n); }
~A() {}
private:
int n;
};
%}
class A
{
public:
A(int a);
void write();
~A();
};
|
// Low level wrapper에서
| 코드: |
class A
{
public:
A(int a) { n = a;}
void write() { printf("class A: %d",n); }
~A() {}
private:
int n;
};
static PyObject *_wrap_new_A(PyObject *self, PyObject *args) {
...
if(!PyArg_ParseTuple(args,(char *)"i:new_A",&arg1)) goto fail;
result = (A *)new A(arg1);
...
}
static PyMethodDef SwigMethods[] = {
{ (char *)"new_A", _wrap_new_A, METH_VARARGS },
...
|
// High level wrapper에서
| 코드: |
class A(_object):
...생략
def __init__(self, *args):
_swig_setattr(self, A, 'this', _example.new_A(*args))
_swig_setattr(self, A, 'thisown', 1)
... 생략
|
위 예에서 클래스 A의 생성자에 대한 wrapping은 low level wrapper의 handler 함수와 high level wrapper에서 __init__()로 이루어지게 된다. 만약, 클래스에 public 영역에 constructor가 하나도 없는 경우는 어떨까? 이 경우 역시 SWIG는 default constructor에 wrapper code를 작성한다. 이렇게 생성자가 없을 때 자동으로 default 생성자에 대한 wrapper code를 생성하는 것은 일반적으로 당연하지만, 경우에 따라서 문제를 유발할 수 있다. 여기에 대해서는 다음에 언급하기로 한다.
C++ 클래스 중에 하나 이상의 pure virtual function을 가지는 가상클래스(virtual class)는 객체를 직접 생성할 수 없고, 그 하위 클래스에서 객체를 생성해야만 한다. 이 경우 SWIG는 생성자에 대한 wrapper 코드를 만들지 않는다. 예를 들어
Virtual Class인 경우
| 코드: |
// interface file
%module example
%{
class A
{
public:
A();
virtual void write() = 0;
~A() {}
};
%}
class A
{
public:
A();
virtual void write() = 0;
~A();
};
|
// Low level wrapper에서
| 코드: |
static PyObject *_wrap_new_A(PyObject *self, PyObject *args) 함수 없음
|
// High level wrapper에서
| 코드: |
class A(_object):
...생략
def __init__(self): raise RuntimeError, "No constructor defined"
...생략
|
위와 같이 A 클래스는 가상 함수이다. 따라서 low level wrapper에 생성자에 대한 handler 함수가 없고, high level wrapper의 __init__() 함수에서는 에러를 발생시키게 된다. 이와 비슷한 이유로 다음과 같이 Protected나 Private 영역에 존재하는 생성자도 마찬가지다.
| 코드: |
// interface file
class A
{
protected:
A();
public:
void write(){ printf("class A\n");
~A() {}
};
%}
class A
{
protected:
A();
public:
void write();
~A() {}
};
|
하지만 이 경우는 조금 더 주의를 기울여야 한다. C++에서 protected로 선언되었다는 것을 SWIG가 모른다면 에러를 발생한다. 즉, 다음과 같이 동일한 클래스에 대해 protected 선언을 숨겼다면,
| 코드: |
// interface file
class A
{
protected:
A();
public:
void write(){ printf("class A\n");
~A() {}
};
%}
class A
{
public:
void write();
~A();
};
|
이 경우는 default 생성자에 대한 wrapper code를 생성하기 때문에 compile에러를 발생시킨다. 즉, protected/private 영역에 선언된 생성자는 SWIG에 알려 주어야 이에 대한 wrapper code를 적절히 생성하는 것이다. 일반적으로 SWIG는 private/protected 영역에 선언된 내용을 무시하지만 이경우는 예외이다. 또 다른 해결책으로는 %nodefault 명령을 사용하는 것이다.
| 코드: |
// interface file
class A
{
protected:
A();
public:
void write(){ printf("class A\n");
~A() {}
};
%}
%nodefault A; // Disable default constructor wrapping for class A
class A
{
public:
void write();
~A() ;
};
|
생성자와 관련된 또 다른 이슈는 복사생성자(copy constructor)에 대한 내용이다. 이전 버전(SWIG-1.3.12.까지)의 SWIG에서 복사생성자를 지원하지 않아 %name 명령 등을 이용해 이름을 바꾸어 사용하였으나, 이제는 자연스럽게 지원하고 있다.
| 코드: |
class A
{
public:
A(int i);
A(const A& a);
...
};
|
Python에서의 사용
a = A();
b = A(a); |
|
| 위로 |
|
 |
|
|
새로운 주제를 올릴 수 없습니다 답글을 올릴 수 없습니다 주제를 수정할 수 없습니다 올린 글을 삭제할 수 없습니다 투표를 할 수 없습니다
|
|