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

[SWIG강좌] 3.2. Basics

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



가입:
올린 글: 56

올리기올려짐: 2004 5월 12 6:54 pm    주제: [SWIG강좌] 3.2. Basics 인용과 함께 답변

3.2. Basics

3.2.1. 개요

SWIG는 인터페이스 파일(*.i 파일)로부터 스크립트 언어의 wrapper 코드를 작성해 주는 유틸리티이다. 먼저 C/C++ 소스파일이 있다면, 이에 대한 인터페이스 파일을 생성하고, SWIG를 실행하면 wrapper 코드가 생성된다. 이후 컴파일과정을 통해 DLL로 만들어 Python에서 사용할 수 있게 된다. 그림 xx는 SWIG를 사용하여 C/C++ extension을 작성하는 일련의 과정을 나타낸 것이다. 인터페이스 파일을 작성하고 난후 SWIG의 사용법은 명령행에서

> swig [option] file.i


그림 3.2 SWIG 사용 개요

와 같이 사용한다. 주로 많이 사용되는 option은

> swig -python file.i → Python을 위한 C wrapper code를 작성
> swig -python -c++ file.i → Python을 위한 C++ wrapper code를 작성

등이다. SWIG에 의해 생성되는 파일은 두가지로 C/C++ wrapper 코드를 담고 있는 low level wrapper인 example_wrap.c(-c++옵션에서는 example_wrap.cxx)와 Python에서 import할 때 사용되는 high level wrapper인 example.py이다. SWIG를 실행하고 난후 file_wrap.c와 C/C++ 코드를 컴파일하여 DLL을 만들어 example.py와 함께 Python에서 사용하면 된다.
간단한 예를 들어보기로 하겠다. 다음과 같은 C 함수들과 전역 변수가 포함된 C 코드를 변환한다고 가정하기로 한다.

코드:

// example.c

double pi = 3.1415926535;

double area(double r)
{
   return pi*r*r;
}

double perimeter(double r)
{
   rturn 2*pi*r;
}

이제 인터페이스 파일을 작성한다.


그림 3.3 example.i

인터페이스 파일은 C 문법과 유사한 형태이다. 주석은 // 또는 /* ... */ 이고, C에서 사용되는 선언을 유사하게 사용할 수 있다. %기호와 함께 키워드가 주어지며, 블록은 %{, %}으로 지정된다. 위에서 %module 키워드는 생성하는 모듈명을 지정하게 된다. 작성된 인터페이스 파일로 SWIG의 실행은 다음과 같다.

> swig -python example.i

이제 example_wrap.c와 example.py 파일이 생성됨을 확인할 수 있을 것이다. example_wrap.c는 wrapper 코드이고, example.py는 Python에서 모듈을 import 할때 사용되는 스크립트이다. 만약 SWIG의 실행 결과 생성되는 파일 이름과 폴더 명을 변경하고 싶다면, -o, -outdir 등의 옵션을 사용한다.

> swig -python -c++ -o example_wrap.cpp example.i
> swig -python -c++ -o cppfile/example_wrap.cpp -outdir pyfiles example.i


이제, example.c와 example_wrap.c 파일을 컴파일하여, _example.dll로 dll을 만든 후, _example.dll과 example.py를 인스톨하여 Python에서 사용할 수 있다. 컴파일은 3.2절에서와 같이 직접 웍크스페이스를 작성하거나(_example.pyd 또는 _example.dll로 만들 것), DistUtil을 이용하면 된다. DistUtil의 경우 setup.py는 다음과 같다.

코드:

# setup.py
from distutils.core import setup, Extension

setup(name="example",
      py_modules = ["example"],
      ext_modules=[Extension("_example",["example.c","example_wrap.c"])]
     )


인스톨 후 프로그램의 수행은

>>> import example
>>> a = example.area(10.)
314.1592
>>> example.cvar.pi
3.141592

등이다. cvar는 Python에서 C/C++ 전역변수를 지정하기위해 SWIG에서 생성되는 객체이다.

이제 SWIG를 이용해 C/C++ extension을 만들어 사용할 때 일어나는 기본적인 사항에 대해 설명한다. SWIG를 사용할 경우 C 선언은 다음과 같이 변환되어 Python에서 사용되게 된다.


▪ C 함수는 Python 함수(또는 명령)가 된다.
▪ C 전역 변수는 SWIG가 생성하는 'cvar'라는 Python 객체의 속성(attribute)가 된다.
▪ C 상수는 Python 변수가 된다.
▪ C built-in type은 Python의 가장 근접한 형으로 변환된다. 예를 들어 다음과 같다.
int, long, short ↔ Python integers.
float, double ↔ Python floats
char, char * ↔ Python strings.
void ↔ None
long long, long double → Currently unsupported
▪ built-in type을 제외한 형의 객체(complex object)는 포인터로 처리한다.



전역변수가 'cvar'라는 객체의 속성으로 변환되는 것은 앞서의 예에서 확인하였다. 만약 명칭을 바꾸고 싶다면, SWIG를 수행할 때 -global 옵션으로 바꿀 수 있다. 예를 들어, 'cvar'을 'myvar' 로 바꾸고 싶다면 다음과 같이 SWIG를 실행하면 된다.

% swig -python -globals myvar example.i

#define, const, enum 등으로 시작하는 C 상수는 Python 변수로 변환된다. 예를 들어,

코드:

// example.i
%module example

#define ICONST 5
#define FCONST 3.14159
#define SCONST "hello world"
enum boolean {NO=0, YES=1};
enum months {JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
const double PI = 3.141592654;
#define MODE 0x04 | 0x08 | 0x40


이다. Python에서 사용하는 방법은 Python 변수이므로

>import example
> print example.ICONST
5

등이다. 상수값은 반드시 정의되어야 한다. 예를 들어

#define USE_PROTOTYPES // No value given
#define _ANSI_ARGS_(a) a // A macro
#define FOO BAR // BAR is undefined

는 Python 변수를 생성하지 않는다. #define은 SWIG preprocessor에서 매크로와 심볼을 지정하는데 사용될 수 있다. 이때 역시 #define은 실제 값을 지정해 주어야 한다. 예를 들어

#define READ_MODE 1
#define HAVE_ALLOCA 1
#define FOOBAR 8.29993
#define VALUE 4*FOOBAR

3.2.2. Complex object의 Type 변환

C/C++는 배열, 구조체, 객체등을 지원하고, 이에 대한 포인터, 레퍼런스 등을 함수의 인자로 넘기거나 리턴값으로 처리할 수 있도록 한다. 반면에 Python은 오직 레퍼런스(즉, 포인터)로만 데이터를 처리한다. 이러한 기본적인 언어간의 차이점을 극복하기 위해 SWIG는 built-in type을 제외한 모든 형을 처리할 때 포인터로 처리하게 된다. SWIG에서는 이러한 형의 객체를 complex object라고 부른다. 심지어 built-in type의 포인터 역시 새로운 형을 만든다. 예를 들어,

int ↔ Python integer
int * ↔ _10081012_p_int
double *** ↔ _1008e124_ppp_double
char ** ↔ _f8ac_pp_char
Vector* ↔ _a82288700_p_Vector
Vector ↔ instance at _a82288700_p_Vector

와 같다. C/C++의 int 형과 같은 built-in type은 이에 대응하는 Python 형으로 변환되지만, built-in type의 포인터형은 SWIG가 새로운 형을 만듦을 알 수 있다. SWIG에서 사용하는 포인터 모델을 SWIG type-checked pointer model이라고 하는데, 위에서 보는 것과 같이 type-information와 함께 엔코딩하게 되고 이 정보를 이용하여 run time에서 형 검사를 수행한다. Vector와 같은 클래스나 구조체 역시 이러한 방식으로 처리됨을 알 수 있다.
다음 예는 C의 표준라이브러리의 일부를 사용한 경우이다.

코드:

// example.i
%module example

FILE *fopen(char *filename, char *mode);
int fclose(FILE *f);
unsigned fread(void *ptr, unsigned size, unsigned nobj, FILE *);
unsigned fwrite(void *ptr, unsigned size, unsigned nobj, FILE *);

// A memory allocation functions
void *malloc(unsigned nbytes);
void free(void *);

// 사용예 1
import example
def filecopy(source,target):
  f1 = example.fopen(source,"r")
  f2 = example.fopen(target,"w")
  buffer = example.malloc(8192)
  nbytes = example.fread(buffer,1,8192,f1)
  while nbytes > 0:
    example.fwrite(buffer,1,nbytes,f2)
    nbytes = example.fread(buffer,1,8192,f1)
  example.fclose(f1)
  example.fclose(f2)
  example.free(buffer)


// 사용예 2
>>> f = example.fopen("test","r")
>>> print f
_f8e40a8_FILE_p
>>> buffer = example.malloc(8192)
>>> print buffer
_1000afe0_void_p
>>> example.fclose(buffer)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: Type error in argument 1 of fclose. Expected _FILE_p.

위에서 FILE 구조체를 wrapper 코드에 선언하지 않아도 되는데, 즉, 사용자가 몰라도 되는데(이를 내부 구조를 몰라도 된다는 뜻에서 Opaque object라 한다), 이것은 type 정보를 같이 엔코딩하여 새로운 형을 만들기 때문이다.

C/C++는 함수의 인자로 넘기는 다양한 방식이 있다. 다음과 같이 complex object를 by value로 인자를 넘기는 함수를 고려해 보기로 하자.

double dot_product(Vector a, Vector b);

Vector는 built-in type이 아니므로 포인터로 처리된다. 따라서 위 함수는

double dot_product(Vector *a, Vector *b)

를 wrapping하는 것과 동일한 효과를 가져오게 되고, Python에서 사용할 때는

>>> a = Vector(1,2,3);
>>> b = Vector(1,2,3);
>>> print dot_product(a,b)

과 같다. 인자를 reference로 넘기는 것도 동일한 역할을 하게 된다. 즉,

void spam1(Foo *x); // Pass by pointer
void spam2(Foo &x); // Pass by reference
void spam3(Foo x); // Pass by value
void spam4(Foo x[]); // Array of objects

이 4개의 함수를 Python에서 사용할 때는 동일하게 사용된다는 것이다.

>>> f = Foo() # Create a Foo
>>> spam1(f) # Ok. Pointer
>>> spam2(f) # Ok. Reference
>>> spam3(f) # Ok. Value.
>>> spam4(f) # Ok. Array (1 element)

이제, C/C++ 함수에서 값을 리턴하는 경우에 대해 알아보기로 하겠다.

Foo *spam5();
Foo &spam6();
Foo spam7();

위 세 함수는 모두 Python에서의 사용법을 동일하다. 다만 세 번째 함수(spam7)은 값을 리턴하기 때문에, 내부에서 새로운 메모리를 할당하는 연산을 추가적으로 수행한다. 이 경우 memory leak을 가져올 수 있는 데, return by value된 변수의 메모리 해제는 사용자 책임이다. -c++ 옵션을 사용한 C++의 경우는 다음과 같이 디폴트 복사 생성자(default copy constructor)를 사용한다.
이외에도 인자를 넘길 때 여러 가지 문제가 있을 수 있다. 다음 예를 보기로 하자.

double sum(double *a, int n); // 배열을 넘기고 합을 return 값으로 리턴
void sum(double *a, int n, double* result);

Python 인터프리터에서 사용은 두 함수 모두

>>> a = (1.,2.,3.)
>>> s = sum(a)

와 같이 사용하기를 바랄 것이다. 하지만 배열은 단순히 SWIG 형 변환을 거쳐 포인터로 변환되고 되므로 Python 튜플형의 인자로 호출하게 되면 오류를 발생시킨다. 또한, 두 번째 함수에서 함수의 인자로 값을 리턴한 경우(result) 역시 Python에서 사용하도록 하는 데 문제가 발생한다. 이것을 해결하기 위해서는 typemap이라는 것을 사용해야 하는데, 나중에 자세히 설명하도록 하겠다. 단지 여기에서는 C/C++ 배열이 Python 리스트, 튜플 등으로 자동으로 변환되지 않는다는 점을 기억하길 바란다.
이제까지 C/C++, SWIG, Python 간의 형변환에 대해 알아보았다. 특히 C/C++의 structure, union, class를 포인터로 처리한다고 설명하였다. 이외에도 이러한 structure, union, class에 대한 자세한 내용은 C++을 설명한 부분에 기술하였다.

3.2.3. Low level wrapper의 구조

SWIG 실행으로 생성된 C/C++ wrapper function들을 정의하는 low level wrapper 파일은 header와 wrapper 함수, 그리고 초기화 코드 부분으로 나누어진다. 그림 3.4에 간단한 예가 나타나 있다.


그림 3.4 Low level wrapper의 구조

그림 3.4에서 low level wrapper에서 #include <Python.h>는 포함되는 헤더이고, 이후 #include <math.h>는 %{...%}에서 지정한 내용이다. %{ ... %}는 low level wrapper 헤더부분에 코드를 삽입하는 기능을 하는데, 여기에서 wrapper 함수가 사용하는 헤더파일을 삽입하거나 구조체, 클래스의 선언 등을 하면 된다. 인터페이스 화일의 C/C++ declarations 부분에 있는 코드를 _wrap으로 시작하는 wrapper 함수로 변환된다. 그리고 마지막의 초기화 함수는 자동으로 SWIG가 생성하여 Python 인터프리터에서 사용할 수 있도록 해준다.
헤더 부분에 코드를 삽입하는 %{...%} 이외에 코드를 삽입하는 데 사용되는 몇 가지 명령이 더있다.

%wrapper %{ ... %} 는 wrapper 부분에 코드를 삽입한다.
%init %{ ... %} 초기화 함수 부분에 코드를 삽입한다.
%inline %{ ... %} 는 헤더 부분에 코드를 삽입하고, 삽입된 코드를 wrapping하는 함수를 생성한다.

위에서 %inline %{...%}은 %{...%}와 인터페이스의 C/C++ declaration에 같은 내용을 사용한 것과 동일하다. 주로 %inline은 객체를 생성하고 파괴하는 함수나, 배열에 접근하거나, 데이터구조체에 내부에 접근하는 헬퍼 함수를 구현할 때 주로 사용된다.

코드:

%module darray
%inline %{
double *new_darray(int size)
{
  return (double *) malloc(size*sizeof(double));
}
double darray_get(double *a, int index)
{
  return a[index];
}
void darray_set(double *a, int index, double value)
{
  a[index] = value;
}
%}
%name(delete_darray) free(void *);


이 헬퍼 함수들의 사용예는 다음과 같다.

코드:

from darray import *
# Turn a Python list into a C double array
def createfromlist(l):
  d = new_darray(len(l))
  for i in range(0,len(l)):
    darray_set(d,i,l[i])
  return d

# Print out some elements of an array
def printelements(a, first, last):
  for i in range(first,last):
  print darray_get(a,i)


3.2.4. Others

Renaming
%name 명령으로 Python 명령의 이름을 바꿀 수 있다. %name 명령은 C와 Python사이의 namespace가 충돌하는 문제를 해결하는데 주로 사용된다.

%name(output) void print();

Restricting(read-only variables)
%readonly, %readwrite명령의 변수에 대한 접근허용을 바꿀 수 있다.

double foo; // A global variable (read/write)
%readonly
double bar; // A global variable (read only)
double spam; // (read only)
%readwrite

Read-only 모드는 명시적으로 disable할 때까지 유효하다.

Default/optional argument
SWIG는 C/C++의 default 인자를 지원한다. 예를 들어 다음과 같다.

int plot(double x,double y,int color=WHITE);

Pointers to function and callback
경우에 따라서 C 함수에서 함수의 포인터를 인자로 받는 경우가 있다. 예를 들어

int binary_op(int a, int b, int (*op)(int,int));

이고, Python에서

>> def add(x,y):
... return x+y
>> binary_op(3,4,add)

와 같이 처리하고 할 것이다. 이 경우 오류가 발생하는 데 이를 해결하기 위해서는 %constant 명령을 이용해야 한다. 여기에서는 자세한 설명을 생략하기로 하고 user manual을 참조하길 바란다.

Conditional Compilation
C preprocessor directives를 SWIG compilation을 제어하는 데 사용할 수 있다. 다음은 SWIG/C 헤더파일을 혼용한 예이다.

코드:

/* header.h
A mixed SWIG/C header file */

#ifdef SWIG
%module example
%{
#include "header.h"
%}
#endif

/* C declarations */
...
#ifndef SWIG
/* Don't wrap these declarations. */
#endif
...


File Inclusion
%include 명령을 이용해 인터페이스 파일에 다른 인터페이스 파일에 포함시킬 수 있다. 이 명령으로 큰 라이브러리를 SWIG를 사용할 때 유용한다.

코드:

%module opengl.i
%include gl.i
%include glu.i
%include aux.i
%include "vis.h"
%include helper.i


C 컴파일러와 비슷하게 -I 옵션으로 주로 사용하는 인터페이스 파일이 저장된 폴더를 지정할 수 있다. 예를 들어,

% swig -python -I/home/beazley/SWIG/lib example.i

이외에 %extern , %import 명령이 있다.


...이 강좌는 컴파일과 빌드를 DistUtil을 이용합니다. 사용법은 이 사이트 어딘가에서 찾으실 수 있을 겁니다.
위로
사용자 정보 보기 비밀 메시지 보내기    
이전 글 표시:   
글 쓰기   답변 달기    파이썬 마을 게시판 인덱스 -> 파이썬 팁/강좌/모듈소개 모음 시간대: GMT + 9 시간(한국)
페이지 11

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



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