GnuSmalltalkUsersGuide:BasicChapter 05

From 흡혈양파의 번역工房
Jump to: navigation, search
제 5 장. C와 CNU Smalltalk 간 상호 운용성

C와 CNU Smalltalk 간 상호 운용성

개인의 라이브러리를 가상 머신으로 연결하기

GNU Smalltalk로 할 수 있는 일들 중 한 가지 멋진 일은 자신만의 goodies를 향상시킨다는 데에 있다. 스몰토크로만 작성되었다면 문제가 없다: 이를 패키지로서 작동하도록 만들고 (31 페이지의 제 3장 [패키지]를 참조), GNU Smalltalk 패키징 시스템에 연결시키는 일은 5분 만에 끝날 작업이다.


자신의 goodie가 대부분 C로 작성되어 스몰토크로 연결시킬만한 특별한 소재가 필요없다면 (예: C 코드에서 스몰토크 코드로 콜백이 없는 경우), 동적 라이브러리 연결 시스템을 사용하면 될 일이다. 이러한 시스템을 사용 시에는 GNU Smalltalk 를 DLD를 이용해 런타임에서 라이브러리와 연결시켜야 한다; 이 때 사용할 메서드는 DLD class>>#addLibrary: 이다.


하지만 Blox의 경우처럼 C와 스몰토크 간 좀 더 밀접한 연결을 제공하고 싶다면, 동적 모듈 연결 시스템을 사용해야 한다. 이번 장에서는 Blox 라이브러리를 참고하여 무엇을 해야 할 것인지 설명하고자 한다.


모듈은 시스템 경로의 gnu-smalltalk 하위디렉터리 또는 SMALLTALK_MODULES 환경 변수가 가리키는 디렉터리에서 검색된다. 모듈은 표준 공유 라이브러리와 구별되는데, 스몰토크가 모듈을 초기화하기 위해 호출하는 함수를 갖고 있기 때문이다; 이러한 함수명은 gst_initModule일 것이다. Blox가 사용하는 초기화 함수를 아래 소개하겠다:

void
gst_initModule(proxy)
    VMProxy *proxy;
{
    vmProxy = proxy;
    vmProxy->defineCFunc("Tcl_Eval", Tcl_Eval);
    vmProxy->defineCFunc("Tcl_GetStringResult", Tcl_GetStringResult);
    vmProxy->defineCFunc("tclInit", tclInit);
    vmProxy->defineCFunc("bloxIdle", bloxIdle);
}


defineCFunc 함수가 gst_initModule 내 함수 포인터를 통해 호출되고, Blox는 코드 내의 다른 곳에서 사용될 매개변수의 값을 저장한다는 사실을 주목하자. 이는 엄밀히 많은 플랫폼, 즉, 모듈이 런타임 시 스몰토크 가상 머신과 효율적으로 연결되는 플랫폼에서는 필요로 하지 않는다; 하지만 일부 플랫폼[1]에서는 최상의 이식성을 목적으로 항상 프록시를 통해 가상 머신을 호출해야 하고, 가상 머신이 내보내는 기호를 절대 참조해선 안 된다. 획일성을 위해 libgst.a 와 연결되는 프로그램조차 이러한 함수들을 직접 호출해선 안 되며, libgst.a 가 내보내고 gst_interpreter_proxy 변수를 통해 접근이 가능한 VMProxy를 통해서만 호출해야 한다.


가장 먼저 자신의 패키지를 공유 라이브러리로서 빌드해야 한다; GNU Automake와 libtool를 이용하면 자신의 Makefile.am 파일을 변경하는 것만큼이나 쉬우므로, 아래가 아니라:

pkglib_LIBRARIES = libblox.a
libblox_a_LDFLAGS = ... more flags ...
libblox_a_SOURCES = ... your source files .


아래와 같이 읽힌다:

pkglib_LTLIBRARIES = libblox.la
libblox_la_LDFLAGS = -module -no-undefined ... more flags ...
libblox_la_SOURCES = ... your source files ...


위에서 볼 수 있듯이 .a 확장자를 .la 로 변경하고, LIBRARIES가 LTLIBRARIES를 대상으로 하며, LDFLAGS[2]에 적절한 옵션을 추가하기만 하면 된다. 또한 libtoolize를 실행하고 그 명령어를 따라야 하지만 이는 실제로 보기보다 간단하다.


BLOX는 스몰토크와 함께 설치되기 때문에 해당 예제는 pkglib 를 사용하고 있다는 점도 주목해야 하지만 일반적으로 꼭 필요한 것은 아니다. 라이브러리는 원하는 곳마다 설치할 수 있다; libtool 은 ldconfig 의 (또는 이에 상응하는 프로그램의) 재실행이 필요할 때마다 인스톨러에게 적절한 경고를 생성하기도 할 것이다.


마지막으로 모듈명을 packages.xml 파일 내에 추가해야 할 것이다. 이러한 경우 해당 파일 내 관련 엔트리는 다음이 될 것이다:

<package>
    <name>BloxTK</name>
    <namespace>BLOX</namespace>
    <filein>BloxBasic.st</filein>
    <filein>BloxWidgets.st</filein>
    <filein>BloxText.st</filein>
    <filein>BloxCanvas.st</filein>
    <filein>BloxExtend.st</filein>
    <filein>Blox.st</filein>
    <module>blox-tk</module>
    <directory>blox-tk</directory>
    <file>Blox.st</file>
    <file>BloxBasic.st</file>
    <file>BloxWidgets.st</file>
    <file>BloxText.st</file>
    <file>BloxCanvas.st</file>
    <file>BloxExtend.st</file>
    <file>colors.txt</file>
</package>


C callout 메커니즘 사용하기

C callout 메커니즘을 사용하기 위해서는 먼저 당신이 호출하고자 하는 C 함수에 관해 스몰토크로 알릴 필요가 있다. 최근에는 두 가지 장소에서 이를 실행해야 한다: 1) 자신의 C 함수 어드레스와 자신이 이것에 붙이고자 하는 이름 간에 매핑을 구축할 필요가 있으며, 2) 스몰토크 해석기에 대해 인자 객체가 어떻게 C 데이터 타입으로 매핑되어야 하는지를 비롯해 함수를 정의해야 한다. 예를 들어, system과 getenv의 (GNU 스몰토크에) 사전 정의된 함수를 사용해보자.


첫째, 이러한 함수와 함수에 대한 문자열 이름 간 매핑이 자신의 모듈에 구축될 필요가 있다. (스몰토크 객체에서 살펴보고 조작할 수 있는) 외부 스몰토크 모듈을 작성하고 있다면, 47 페이지의 5.1절 [개인의 라이브러리를 가상 머신으로 연결하기]를 참고한다; 동적으로 로딩된 라이브러리로부터 함수를 사용하고 있다면 22페이지의 2.6절 [동적 로딩]을 참고한다.


둘째, 이러한 C 함수를 호출하고 그 인자를 스몰토크 런타임 시스템으로 설명해줄 메서드를 정의할 필요가 있다. 그러한 메서드는 primitive-like 구문으로 정의되는데, 아래 예제와 유사하다 (kernel/CFuncs.st에서 발췌).

system: aString

<cCall: 'system' returning: #int args: #(#string)>


getenv: aString

<cCall: 'getenv' returning: #string args: #(#string)>


이 메서드들은 SystemDictionary 클래스 상에서 정의되었으므로 아래와 같이 호출할 것이다:

Smalltalk system: 'lpr README' !


하지만 어떤 클래스가 메서드를 수신하는지와 관련해선 특별한 의미가 없다; Float 일 수도 있지만 아래와 같은 모습이라면 낯설 것이다:

1701.0 system: 'mail help-smalltalk@gnu.org' !


다양한 키워드 인자를 아래에서 설명해보고자 한다.


cCall: 'system'

이것은 우리가 C 함수 system을 정의하고 있음을 말해준다. 그 이름은 defineCFunc 으로 전달된 문자열과 정확히 동일해야 한다.
메서드명이 C 함수의 이름과 일치할 필요가 없다; 단순히 선택자를 'rambo: fooFoo' 로 정의할 수도 있다; 단 유사한 이름의 메서드를 정의하고 인자 이름이 전달되어야 하는 데이터 유형을 반영하도록 정의하는 것은 훌륭한 연습이다.


returning: #int

이것은 리턴될 C 데이터 타입을 정의한다. 이에 상응하는 스몰토크 데이터 타입으로 변환된다. 유효한 리턴 타입의 집합은 다음과 같다:
char 단일 C 문자 값
string C char*, 스몰토크 문자열로 변환됨
stringout C char*, 스몰토크 문자열로 변환된 후 free 됨.
symbol C char*, 스몰토크 기호로 변환됨.
symbolOut C char*, 스몰토크 기호로 변환된 후 free 됨.
int C int 값
uInt C 부호가 없는 int 값
long C 긴 값
uLong C 부호가 없는 긴 값
double FloatD의 인스턴스로 변환된 C double
longDouble FloatQ의 인스턴스로 변환된 C long double
void 리턴된 값 없음 (스몰토크로부터 리턴된 self)
wchar 단일 C 너비 문자 (wchar_t) 값
wstring wide C 문자열 (wchar_t*), UnicodeString으로 변환됨
wstringOut wide C 문자열 (wchar_t*), UnicodeString으로 변환된 후 free됨
cObject 익명 C 포인터; 어떤 C 함수를 후에 다시 전달 시 유용
smalltalk (C에게) 익명의 스몰토크 객체 포인터; 과거 어떤 시점에 C에게 전달되어야 했거나, 다른 public GNU Smalltalk 함수를 호출함으로써 프로그램에 의해 생성되었어야 한다 (57 페이지의 5.4절 [스몰토크 타입] 참조).
ctype CType의 인스턴스 또는 그 서브클래스 중 하나를 전달할 수 있다 (53 페이지의 5.3절 [C 데이터 타입]을 참조). 이러한 경우 리턴되기 전에 객체에게 #narrow가 전송될 것이다: 실험적 Gtk+ 바인딩에서 해당 기능의 예를 찾을 수 있다.


args: #(string)

기호의 배열로, 인자 타입을 순서대로 설명한다. 가령 open(2)로의 호출을 명시하기 위한 인자는 아래와 같은 모습을 할 것이다:
args: #(string int int)
아래의 인자 타입이 지원된다; 상세한 내용은 위를 참고한다.
unknown 스몰토크는 해당 객체에서 추측할 수 있는 최선의 규칙을 만들어낼 것이다; 아래 매핑 테이블을 참고하라.
boolean char 로서 전달되고, int 로 진행된다.
char char 로서 전달되고 int로 진행된다.
wchar wchar_t 로서 전달된다.
string char* 로서 전달된다.
stringOut char* 로서 전달되고, 내용은 새 C 문자열로 오버라이트될 것으로 예상되며, 전달된 객체는 리턴 시 새 문자열이 된다.
wstring wchar_t* 로서 전달된다.
wstringOut wchar_t* 로서 전달되고, 내용은 새 C 너비의 문자열로 오버라이트될 것으로 예상되며, 전달된 객체는 리턴 시 새 문자열이 된다.
symbol char* 로서 전달된다.
byteArray char* 로서 전달되지만 NUL's를 포함할 수도 있다.
int int 로서 전달된다.
uInt unsigned int 로서 전달된다.
long long 으로서 전달된다.
uLong unsigned long 으로서 전달된다.
double double 로서 전달된다.
longDouble long double 로서 전달된다.
cObject void* 로서 전달되는 C 객체 값.
포인터가 아닌(Non-pointer) 색인된 인스턴스 변수가 있는 클래스라면 모두 #cObject 로서 전달이 가능하고, GNU Smalltalk는 처음으로 색인된 인스턴스 변수의 어드레스를 전달할 것이다. 하지만 객체를 할당하는 함수에는 절대로 실행해선 안 되며, 스몰토크 코드로 다시 호출하지 않는 이상 쓰레기 수집을 야기할 것이다: GC 이후에 #cObject 로서 전달된 포인터는 무효화될 수도 있다. 이런 경우 모든 객체를 #smalltalk 로서 전달하거나, C 함수가 이전에 리턴한 CObjects만 전달하는 편이 더 안전하다.
또한 #cObject 는 함수 포인터에도 이용 가능하다. 이러한 포인터는 CCallable의 인스턴스이거나 그 서브클래스들 중 하나이다. 스몰토크 블록을 위한 함수 포인터를 생성하는 방법은 64 페이지에 실린 5.6절 [스몰토크 콜백]을 참고한다.
cObjectPtr void**로서 전달된 C 객체 값에 대한 포인터. CObject 는 전달된 객체로 저장된 값을 반영하도록 출력(output)에서 수정된다.
smalltalk 객체 포인터를 C로 전달한다. C 루틴은 값을 익명 저장공간(anonymous storage)으로의 포인터로 취급해야 한다. 이 포인터는 추후 적절한 시기에 스몰토크로 리턴 가능하다.
variadic
variadicSmalltalk Array가 예상된다. variadic이 사용되거나 variadicSmalltalk에 대한 raw 객체 포인터로서 전달될 경우 배열의 각 요소는 unknown 매개변수처럼 변환될 것이다.
self
selfSmalltalk 수신자를 전달하고, self가 사용되거나 selfSmalltalk에 대한 raw 객체 포인터로서 전달될 경우 이 수신자를 unknown 매개변수처럼 C로 변환한다. 이러한 방식으로 전달되는 매개변수는 메시지 인자로 매핑되지 않으며, 대신 메시지의 수신자로 매핑된다.


선언된 매개변수 타입 객체 타입 사용된 C 매개변수 타입
boolean Boolean (True, False) int
byteArray ByteArray char *
cObject CObject void *
cObject ByteArray, etc. void *
cObjectPtr CObject void **
char Boolean (True, False) int
char Character int (C promotion rule)
char Integer int
double Float double (C promotion)
longDouble Float long double
int Boolean (True, False) int
int Integer int
uInt Boolean (True, False) unsigned int
uInt Integer unsigned int
long Boolean (True, False) long
long Integer long
uLong Boolean (True, False) unsigned long
uLong Integer unsigned long
smalltalk, selfSmalltalk 무엇이든 가능 OOP
string String char *
string Symbol char *
stringOut String char *
symbol Symbol char *
unknown, self Boolean (True, False) int
unknown, self ByteArray char *
unknown, self CObject void *
unknown, self Character int
unknown, self Float double
unknown, self Integer long
unknown, self String char *
unknown, self Symbol char *
unknown, self 다른 어느 것이든 가능 OOP
variadic Array 각 요소는 "unknown"에 따라 전달됨
variadicSmalltalk Array 각 요소는 OOP로서 전달됨
wchar Character wchar_t
wstring UnicodeString wchar_t *
wstringOut UnicodeString wchar_t *
매개변수 변환 테이블:


자신의 call-out이 #void를 반환하면 애플리케이션에 따라 비동기식 call-out의 사용을 고려해볼 수도 있다. 이는 call-out을 시작한 프로세스를 중단하지 않는 call-out이므로, 프로세스를 다시 계획하여 call-out 자체의 실행 도중에 call-out을 따르는 코드를 실행할 수 있다. 이는 특히 이벤트 루프를 (스몰토크로 콜백하는 가장 흔한 장소) 작성 시 유용한데, 이런 경우 외부(outer) 이벤트의 프로세싱이 끝나기 전에 외부 이벤터의 처리 중 도달하는 이벤트를 처리할 수 있기 때문이다. 물론 자신의 애플리케이션에 따라 옳을 수도, 그렇지 않을 수도 있다. 향후에는 비동기식 call-out이 구분된 쓰레드(thread)에서 시작될지도 모른다.


비동기식 call-out은 대안적으로 선택 가능한 primitive-like 구문인 asyncCCall:args: 를 이용해 정의된다. 비동기식 call-out은 항상 nil을 리턴하기 때문에 리턴된 값 매개변수는 누락됨을 주목한다.


C 데이터 타입 조작 시스템

CType은 C 데이터 타입 자체를 (저장공간 없이 타입만) 표현하는 데 사용되는 클래스다. CmumbleType 과 같이 불리는 서브클래스도 있다. 인스턴스는 자신의 크기와 정렬(alignment) 정보를 응답할 수 있다. 그들의 valueType은 데이터의 기반이 되는 타입이다. 이는 해석기에 의해 스칼라 타입으로 해석되는 정수이거나, 다른 CType 서브클래스 인스턴스인 기본 요소 타입이다.


삶을 더 수월하게 만들기 위한 목적으로, CScalarCType의 인스턴스에 의지하는 전역 변수도 있다: 이는 CmumbleType (CIntCType이 아니라 CIntType와 같이) 이라 불리며, C 데이터타입이 사용될 때마다 사용 가능하다. 문자열 배열을 갖고 있었다면 요소들은 CStringType의 (CScalarCType의 구체적 인스턴스) 것이다.


CObject는 C 데이터의 인스턴스에 대한 기반 클래스다. 이는 CScalar 라는 서브클래스를 갖고 있는데, 이 서브클래스는 다시 Cmumble 이라는 서브클래스를 갖고 있다. 이러한 서브클래스들은 크기와 정렬 정보를 응답할 수 있다.


CObject의 인스턴스는 raw C 포인터를 (예: malloac된 힙 안에) 보유하거나, 저장공간(storage)을 ByteArray로 위임할 수 있다. 후자의 경우 CObject가 죽게 되면 저장공간을 자동으로 쓰레기 수집되고, VM은 접근이 범위 내(in bound)에 있는지 확인한다. 반면 저장공간이 이동할 수도 있으며, 이러한 이유 때문에 스몰토크로 콜백할 때나 전달된 포인터를 어딘가에 저장하는 C 루틴의 CObject 유형을 사용할 때는 특별히 주의를 기울여야 한다.


CObject의 인스턴스는 다양한 방식으로 생성이 가능하다:

  • class new 로 인스턴스를 생성 시 NULL에 대한 포인터를 초기화한다;
  • type이 CType 서브클래스 인스턴스인 type new를 실행하면 malloc으로 새 인스턴스를 할당한다.
  • Type 이 CType 서브클래스 인스턴스인 type new를 실행하면 쓰레기로 수집된 저장공간이 있는 새 인스턴스를 할당한다.


CStruct와 CUnion 서브클래스는 특별하다. 첫째, new는 포인터를 NULL로 초기화하는 대신 malloc으로 새 인스턴스를 할당한다. 둘째, 쓰레기로 수집된 저장공간이 있는 새 인스턴스를 생성하는 gcNew를 지원한다.


C callout 메커니즘으로 생성된 CObject에는 절대 쓰레기 수집된 저장공간이 따라오지 않는다.


CObject와 그 서브클래스는 C 객체로의 포인터를 표현하고, C 포인터가 지원하는 전체 연산 범위를 제공한다. 각 항목의 크기보다 anInteger의 배수만큼 메모리에서 높은 CObject를 리턴하는 +anInteger를 예로 들 수 있겠다. 또한 매개변수로서 정수에 주어졌을 때 마치 +처럼 행동하는 –도 있다. CObject가 주어질 경우 이는 두 포인터 간 차이를 리턴한다. incr, decr, incrBy:, decrBy: 는 문자열을 1 또는 n 문자만큼 전방향 또는 후방향으로 조정한다. 문자열에 대한 포인터만 변경될 뿐이다; 문자열 내 실제 문자는 그대로 남는다.


CObject는 C 데이터 타입과 마찬가지로 두 개의 군, 스칼라 타입과 비스칼라(non-scalar) 타입으로 나눌 수 있다. 스칼라 타입은 value 메시지를 전송하면 스몰토크 객체를 페치(fetch)하고, value: 메시지를 전송하면 그 값을 변경한다. 비스칼라 타입은 이 메시지들을 지원하지 않는다. 비스칼라 타입은 CArray의 인스턴스, CStruct와 CUnion의 (CPtr은 제외) 서브클래스를 포함한다.


CPtr과 CArray는 CArray 또는 CPtr 인스턴스와 관련된 CType 서브클래스 인스턴스를 통해 기본이 되는 요소 타입을 얻는다.


CPtr의 value와 value: 메서드는 그것이 가리키는 기반이 되는 값을 얻거나 변경한다. value는 가리키는 값에 상응하는 다른 CObject를 리턴한다. 예를 들자면 CPtr to long은 메모리 내에서 pointer to long이 저장된 장소를 가리키기 때문이다. 이는 주로 long**으로, long을 얻으려면 cPtr value value로 두 번 참조해제해야 한다.


CString은 value를 전송하면 스몰토크 String을 응답하는 CPtr의 서브클래스이며, value: 를 전송하면 스몰토크 String을 복사 및 NULL로 끝나도록(null-terminate) 저장공간을 자동으로 할당한다. replaceWith: 는 인스턴스가 가리키는 문자열을 인수로서 전달된 ByteArray 또는 새 문자열로 대체한다. 사실상 이는 스몰토크 String 인스턴스인 aString으로부터 널 종료자(null terminator)를 이용해 이미 CString를 가리키는 동일한 버퍼로 바이트를 복사한다.


마지막으로 CObject[3]의 추상 클래스인 CStruct와 CUnion이 있다. 아래는 CStruct를 참조할 것이지만 CUnion에도 동일한 사항을 고려하지만, CUnion은 C union의 구문을 구현한다는 점에서만 차이가 있겠다.


이러한 클래스들은 아래를 포함해 C 데이터 구조로 직접 접근성을 제공한다.

  • long (역시 부호가 없음)
  • short (역시 부호가 없음)
  • char (역시 부호가 없음) & 바이트 타입
  • double, long double, float
  • string (NUL terminated char*, 특수 접근자가 있음)
  • 모든 타입의 배열
  • 모든 타입의 포인터
  • 여느 고정된 크기의 타입을 포함하는 기타 structs


C에서 struct decl의 예를 들어보겠다:

struct audio_prinfo {
    unsigned channels;
    unsigned precision;
    unsigned encoding;
    unsigned gain;
    unsigned port;
    unsigned _xxx[4];
    unsigned samples;
    unsigned eof;
    unsigned char pause;
    unsigned char error;
    unsigned char waiting;
    unsigned char _ccc[3];
    unsigned char open;
    unsigned char active;
};
struct audio_info {
    audio_prinfo_t play;
    audio_prinfo_t record;
    unsigned monitor_gain;
    unsigned _yyy[4];
};


스몰토크에서 위에 상응하는 선택은 다음과 같다:

CStruct subclass: AudioPrinfo [
    <declaration: #( (#sampleRate #uLong)
        (#channels #uLong)
        (#precision #uLong)
        (#encoding #uLong)
        (#gain #uLong)
        (#port #uLong)
        (#xxx (#array #uLong 4))
        (#samples #uLong)
        (#eof #uLong)
        (#pause #uChar)
        (#error #uChar)
        (#waiting #uChar)
        (#ccc (#array #uChar 3))
        (#open #uChar)
        (#active #uChar))>
    <category: 'C interface-Audio'>
]
CStruct subclass: AudioInfo [
    <declaration: #( (#play #{AudioPrinfo} )
        (#record #{AudioPrinfo} )
        (#monitorGain #uLong)
        (#yyy (#array #uLong 4)))>
    <category: 'C interface-Audio'>
]


이는 CStruct의 새 서브클래스 두 개, AudioPrinfo와 AudioInfo를 주어진 필드와 함께 생성한다. 추가 메타데이터 declaration: 으로 표준 서브클래스를 생성할 때도 구문은 동일하다. C 함수가 AudioPrinfo type을 매개변수로 하여 returning: 키워드에게 전달함으로써 이러한 클래스의 인스턴스인 CObject를 리턴하도록 만들 수 있다.


AudioPrinfo는 그 위에 아래와 같은 메서드들이 정의되어 있다:

#sampleRate
#channels
#precision
#encoding


이러한 메서드들은 다양한 데이터 멤버(data member)로 접근한다. 배열 요소 접근자(xxx, ccc)는 배열 자체로의 포인터를 리턴한다.


단순한 scalar 타입의 경우, 변수 다음에 타입명을 열거하기만 하면 된다. Scarlar 이름 집합은 다음과 같으며, kernel/CStruct.st에 정의되어 있다:

#long           CLong
#uLong          CULong
#ulong          CULong
#byte           CByte
#char           CChar
#uChar          CUChar
#uchar          CUChar
#short          CShort
#uShort         CUShort
#ushort         CUShort
#int            CInt
#uInt           CUInt
#uint           CUInt
#float          CFloat
#double         CDouble
#longDouble     CLongDouble
#string         CString
#smalltalk      CSmalltalk
#{...} 주어진 CObject  서브클래스


  1. {...} 구문은 Blue Book에 포함되어 있지 않지만 GNU Smalltalk와 다른 Smalltalk에서 찾을 수 있다; 이 구문은 전역 변수에 상응하는 Association 객체를 리턴한다.

타입으로 포인터를 가지려면 아래와 같이 사용하라:

(#example (#ptr #long))


size 크기의 배열 포인트를 가지려면 아래와 같이 사용하라:

(#example (#array #string size))


이는 C에서 char *example[size]로 매핑함을 주목하라.


필드를 사용해 리턴된 객체는 CObjects이다; 현재엔 페치(fetch)하는 함축적(implicit) 값이 없다. 예를 들어, 위에 설명된 바와 같이 AudioPrinfo 클래스의 인스턴스를 어떻게든 연결했다고 가정하자 (인스턴스는 CObject 클래스이고, 어딘가 있는 실제 C 구조를 가리킨다). 이 객체를 audioInfo 변수에 보관했다고 치자. 현재 gain 값을 얻으려면, 아래를 실행하라:

audioInfo  gain  value


구조에서 gain 값을 변경하려면 아래를 실행하라:

audioInfo  gain  value: 255


구조 멤버(structure member) 메시지는 단순히 CObject 인스턴스를 응답하므로, 당신은 구조 멤버를 직접 참조하도록 유지하거나, value 또는 value: 메서드를 이용해 멤버의 값으로 접근 또는 그 값을 변경할 수 있다.


이는 CString이나 CArrays 또는 CPtrs 상에서 addressAt: 메서드를 사용할 때 얻는 것과 동일한 타입의 접근임을 주목하라: 이들은 올바른 타입의 C 객체를 가리키는 CObject를 리턴하고, 실제 C 변수로 접근하고 그 값을 수정하기 위해선 value와 value: 를 사용할 필요가 있다.


C로부터 스몰토크 데이터 조작하기

GNU Smalltalk는 Integers를 제외한 모든 객체를 OOP 라는 (필자가 아는 한 어떤 것의 두문자도 아니다) 데이터 구조로 매핑한다. OOP는 내부 데이터 구조에 대한 포인터다; 이러한 데이터 구조는 기본적으로 객체의 표현에 간접지정(indirection)수준을 추가하는데, 그 이유는 구조가 아래를 포함하기 때문이다:

  • 실제 객체 데이터를 가리키는 포인터
  • 플래그 무리, 대부분은 쓰레기 수집 프로세스와 관련


추가 간접지정 수준은 쓰레기 수집을 매우 효율적으로 만드는데, 수집기가 heap 내에 객체로의 모든참조를 업데이트하지 않고 메모리 안에서 자유롭게 객체를 이동시켜 heap을 완전히 압축된 채로 유지하여 새 객체를 매우 빠르게 할당하도록 해주기 때문이다. 하지만 객체를 처리하길 원하는 C 코드를 본래보다 더 어지럽게 만들기도 한다; 예를 확인하고 싶다면 GNU Smalltalk 에서 프로세스를 처리하는 흥미로운 코드를 살펴보기 바란다.


C와 같은 비 객채 지향 언어에서 객체 지향 프로그래밍을 이행 시 발생하는 복잡성으로부터 가능한 한 보호하기 위해 GNU Smalltalk는 공통된 스몰토크 객체와 C 타입 간 매핑에 적합한 함수를 제공한다. 이를 통해 단순히 OOP 변수를 선언한 후 C 데이터와 같은 내용을 처리하는 그 함수를 사용하면 된다.


이러한 함수들은 47 페이지의 5.1절 [개인의 라이브러리를 가상 머신으로 연결하기]에서 보이는 바와 같이 모듈로 전달되는 포인터, VMProxy 구조를 통해 모듈로 전달된다. 이 함수는 두 가지 그룹으로 나뉘는데, 하나는 스몰토크 객체에서 C 데이터 타입으로 매핑되는 함수이고, 하나는 C 데이터 타입에서 스몰토크 객체로 매핑되는 함수이다.


첫 번째 그룹(스몰토크에서 C로)의 함수는 다음과 같다; 전부 OOPTo로 시작되는 모습을 볼 수 있을 것이다:


long OOPToInt (OOP) [함수]

해당 함수는 전달된 OOP가 Integer이고, 해당 정수에 대해 C signed long을 리턴한다고 가정한다.


long OOPToId (OOP) [함수]

해당 함수는 주어진 OOP에 대해 유일한 식별자를 리턴하는데, 이러한 식별자는 OOP가 쓰레기 수집될 때까지 유효하다.


double OOPToFloat (OOP) [함수]

해당 함수는 전달된 OOP가 Integer 또는 Float이라고 가정하고, 해당 객체에 대해 C double을 리턴한다.


long double OOPToLongDouble (OOP) [함수]

해당 함수는 전달된 OOP가 Integer 또는 Float이라고 가정하고, 해당 객체에 대해 C long double을 리턴한다.


int OOPToBool (OOP) [함수]

해당 함수는 주어진 OOP가 true 객체인 경우 true에 해당하는 (예: !=0) C 정수를 리턴하고, 그렇지 않을 경우 false를 리턴한다.


char OOPToChar (OOP) [함수]

해당 함수는 전달된 OOP가 Character라고 가정하고, 해당 정수에 대해 C char를 리턴한다.


wchar_t OOPToWChar (OOP) [함수]

해당 함수는 전달된 OOP가 Character 또는 UnicodeCharacter라고 가정하고, 해당 정수에 대해 C wchar_t를 리턴한다.


char *OOPToString (OOP) [함수]

해당 함수는 전달된 OOP가 String 또는 ByteArray라고 가정하고, 동일한 내용의 C null-terminated char*를 리턴한다. 포인터를 해제(free)시키고 스몰토크 객체 내부에서 가능한 'NUL' 문자를 처리하는 것은 호출자의 책임이다.


wchar_t *OOPToWString (OOP) [함수]

해당 함수는 전달된 OOP가 UnicodeString이라고 가정하고, 동일한 내용의 C null-terminated wchar_t*를 리턴한다. 포인터를 해제시키고 스몰토크 객체 내부에서 가능한 'NUL' 문자를 처리하는 것은 호출자의 책임이다.


char *OOPToByteArray (OOP) [함수]

해당 함수는 전달된 OOP가 String 또는 ByteArray라고 가정하고, 동일한 내용의 C char*를 널 종료(null-terminating)하지 않고 리턴한다. 포인터를 해제시키는 것은 호출자의 책임이다.


PTR OOPToCObject (OOP) [함수]

해당 함수는 전달된 OOP가 CObject의 유형이라고 가정하고, 객체가 가리키는 C 데이터로 C PTR을 리턴한다. 호출자는 포인터가 무엇을 하는지 정확히 알기 전까진 포인터를 해제시켜서도 안 되고, 그 크기나 내용에 대해 어떠한 추측도 해선 안 된다. PTR은 지원 시 void* 이고, 그렇지 않을 경우 char* 이다.


long OOPToC (OOP) [함수]

해당 함수는 전달된 OOP가 String, ByteArra, CObject 또는 내장된 객체(nil, true, false, 문자, 정수)라고 가정한다. OOP가 nil인 경우 0을 응답한다; 그 외에 각 객체에 대한 매핑은 위의 함수와 정확히 같다. 함수가 long을 리턴하는 것으로 선언된다 하더라도 char* 또는 PTR로 캐스팅해야 할 수도 있다는 사실을 명심하라.


위의 함수를 사용 시에는 특별한 주의가 요구되는 반면 (최소한 자신이 변환하는 스몰토크 객체 타입에 대해서는 알아야 할 것이다), 아래와 같이 C 데이터를 스몰토크 객체로 변환하는 함수는 사용이 쉽고, 객체를 인큐베이터(incubator)에 넣으므로 쓰레기 수집으로 정리되지 않을 것이다 (74 페이지 5.10절 [인큐베이터] 참조). cObjectToTypedOOP를 제외한 모든 함수는 ToOOP로 끝난다:


OOP intToOOP (long) [함수]

해당 객체는 전달된 C long과 동일한 값을 포함하는 Smalltalk Integer를 리턴한다. 스몰토크 정수는 항상 부호가 있으며(signed), C long에 대해 정밀도(precision)가 덜하다. 32 비트 머신에서 정밀도는 (부호가 없을 시) 30 비트 또는 (부호가 있을 시) 31 비트이다; 64 비트 머신에서 정밀도는 (부호가 없을 시) 62 비트 또는 (부호가 있을 시) 63 비트이다.


OOP idToOOP (OOP) [함수]

해당 함수는 OOPToId가 리턴한 유일한 식별자로부터 OOP를 리턴한다. OOPToId로의 호출 이후에 원본 OOP가 쓰레기 수집되지 않은 경우에만 OOP는 OOPToId로 전달된 것과 동일해질 것이다.


OOP floatToOOP (double) [함수]

해당 객체는 전달된 double과 동일한 값을 포함하는 Smalltalk FloatD를 리턴한다. Integers와 달리 FloatD는 C double과 정확히 같은 정밀도를 갖는다.


OOP longDoubleToOOP (long double) [함수]

해당 객체는 전달된 long double과 동일한 값을 포함하는 Smalltalk FloatQ를 리턴한다. Integers와 달리 FloatQ는 C long double과 정확히 같은 정밀도를 갖는다.


OOP boolToOOP (int) [함수]

해당 객체는 전달된 C int와 동일한 부울 값을 포함하는 Smalltalk Boolean을 리턴한다. 즉, 리턴된 OOP는 매개변수가 0인지 아닌지에 따라 False 아니면 True의 유일한 인스턴스인 것이다.


OOP charToOOP (char) [함수]

해당 객체는 전달된 C char과 동일한 char를 표현하는 Smalltalk Character를 리턴한다.


OOp charToOOP (wchar_t) [함수]

해당 객체는 전달된 C wchar_t와 동일한 char를 표현하는 Smalltalk Character 또는 UnicodeCharacter를 리턴한다.


OOP classNameToOOP (char *) [함수]

해당 메서드는 이름이 주어진 매개변수를 이름으로 가진 스몰토크 클래스(예: Class의 서브클래스의 인스턴스)를 리턴한다. 해덩 메서드는 속도가 느리다; 그 결과를 안전히 캐시 저장할 수 있다.


OOP stringToOOP (char *) [함수]

해당 메서드는 주어진 null-terminated C 문자열로 매핑하는 String을 리턴하고, 매개변수가 어드레스 0 (영)을 가리킬 경우에는 내장된 객체 nil을 리턴한다.


OOP wstringToOOP (wchar_t *) [함수]

해당 메서드는 주어진 null-terminated C wide string을 매핑하는 UnicodeString을 리턴하고, 매개변수가 어드레스 0(영)을 가리키는 경우에는 내장된 객체 nil을 리턴한다.


OOP byteArrayToOOP (char *, int) [함수]

해당 메서드는 첫 번째 매개변수가 가리키는 바이트로 매핑하는 ByteArray를 리턴한다; 두 번째 매개변수는 ByteArray의 크기를 제공한다. 첫 번째 매개변수가 어드레스 0(영)을 가리키는 경우에는 내장된 객체 nil을 리턴한다.


OOP symbolToOOP (char *) [함수]

해당 메서드는 주어진 null-terminated C 문자열로 매핑하는 String을 매핑하고, 매개변수가 어드레스 0(영)을 가리키는 경우에는 내장된 객체 nil을 리턴한다.


OOP cObjectToOOP (PTR) [함수]

해당 메서드는 주어진 C 포인터로 매핑하는 CObject를 리턴하고, 매개변수가 어드레스 0(영)을 가리키는 경우에는 내장된 객체 nil을 리턴한다. 리턴된 객체에는 정밀한 CType가 할당되어 있지 않다. 하나를 할당하기 위해서는 cObjectToTypedOOP를 사용하라.


OOP cObjectToTypedOOP (PTR, OOP) [함수]

해당 메서드는 주어진 C 포인터로 매핑하는 CObject를 리턴하고, 매개변수가 어드레스 0(영)을 가리키는 경우에는 내장된 객체 nil을 리턴한다. 리턴된 값은 두 번째 매개변수를 그 타입으로 갖는다; 가능한 타입을 모두 얻으려면 typeNameToOOP를 사용하라.


OOP typeNameToOOP (char *) [함수]

해당 메서드가 사실상 하는 일은 그 매개변수들을 스몰토크 코드로서 평가하는 일 뿐이다; 따라서 아래와 같은 방식으로 사용할 수 있겠다:
cIntType = typeNameToOOP("CIntType");
myOwnCStructType = typeNameToOOP("MyOwnCStruct type");


위에서 언급한 바와 같이 C to Smalltalk layer는 자동으로 그것이 생성하는 객체를 인큐베이터에 넣어서 쓰레기로 수집되지 않도록 객체를 보호한다. 하지만 플러그인(plugin)은 인큐베이터에 대한 통제성이 제한되어 있으므로, 객체를 상대적으로 장시간 등록된 채 유지해야 하는 경우와 레지스트리에서 수명(lives)이 일반적으로 중복되는 객체의 경우 인큐베이터 자체는 별로 도움이 되지 못한다.


그러한 객체의 쓰레기 수집을 피하기 위해서는 구분된 레지스트리로 접근하는 아래의 함수들을 사용할 수 있겠다:


void registerOOP (OOP) [함수]

주어진 OOP를 레지스트리에 넣는다. 객체를 여러 번 등록하면 동일한 수만큼 등록 해제해야 할 것이다. 스몰토크 call-in에 의해 리턴된 객체를 등록하길 원할 수도 있다.


void unregisterOOP (OOP) [함수]

주어진 OOP의 발생을 레지스트리에서 제거한다.


void registerOOPArray (OOP **, OOP **) [함수]

객체의 배열을 루트 집합(root set)의 일부로 만들어야 함을 쓰레기 수집기에게 알려준다. 두 매개변수는 배열의 base와 top을 간접적으로 가리킨다; 즉, 배열의 base와 top을 보유하는 변수로의 포인터인 셈이다: 간접 포인터를 갖고 있을 경우 배열의 크기를 동적으로 변경하도록 해주고, 심지어 배열을 수정할 때마다 등록 해재나 재등록할 필요 없이 위치를 변경하도록 해준다. 배열을 여러 번 등록할 경우 동일한 수만큼 등록해제 해야 할 것이다.


void unregisterOOPArray (OOP **) [함수]

주어진 base의 배열을 레지스트리에서 제거한다.


C 에서 스몰토크로 호출

GNU Smalltalk는 스몰토크 메서드를 현재 실행 컨텍스트가 아닌 다른 실행 컨텍스트에서 호출하도록 해주는 함수 호출 7개를 제공한다. 메서드가 실행되는 우선순위는 현재 실행 중인 스몰토크 프로세스 중 하나와 같을 것이다.


이러한 함수들 중에서 4 개는 좀 더 저수준의 것으로, 스몰토크 프로그램 자체가 수신자, 선택자, 그리고 어쩌면 매개변수도 몇 가지 제공한 경우에 적절하다; 나머지 3개는 대신 좀 더 다용도로 사용된다. 그 중 하나는 (msgSendf) C 데이터 타입과 스몰토크 객체 간 대부분 변환을 자동으로 처리하는 반면 나머지는 스몰토크 코드의 전체 조각을 책임진다.


이러한 함수들은 모두 '3-인자 선택자에 5개 인자'와 같이 명시하는 경우를 적절히 처리한다-자세한 정보는 단일 함수의 설명을 참고하라). msgSendf를 제외하고 나머지 함수의 경우 NULL을 선택자로서 전달하면 수신자가 블록일 것으로 예상하고 평가할 것이다.


OOP msgSend (OOP 수신자, OOP 선택자, ...) [함수]

해당 함수는 주어진 선택자를 (Symbol 또는 nilOOP이 리턴된다) 주어진 수신자에게 전송한다. 메시지 인자 또한 OOP이어야 하고 (그렇지 않을 경우 접근 위반 예외가 발생할 가능성이 높다), 널 종료되는 리스트에서 선택자 다음으로 전달된다. 메서드로부터 리턴된 값은 msgSend의 결과로 C 프로그램에 다시 OOP로서 전달되고, 인자의 수가 올바르지 않을 경우에는 nilOOP가 리턴된다. 예를 들자면 (1+2와 같다) 다음과 같다:
OOP shouldBeThreeOOP = vmProxy->msgSend(
    intToOOP(1),
    symbolToOOP("+"),
    intToOOP(2),
    NULL);


OOP strMsgSend (OOP 수신자, char * 선택자, . . . ) [함수]

해당 함수는 위와 동일하나 선택자가 C 문자열로서 전달되고 자동으로 스몰토크 기호로 변환된다.
이론상 프로그램에 선택자를 캐시 저장하는 방법이 존재하고 call-in마다 symbolToOOP의 호출을 피한다면 해당 함수는 msgSend보다 조금 느리다. 하지만 "실제" 코드에서는 이러한 차이를 눈치채기는 쉽지 않은데, 스몰토크 해석기에 소요되는 시간이 보통 선택자를 Symbol 객체로 변환하는 데 소요되는 시간보다 훨씬 크기 때문이다. 예를 들자면 다음과 같다:
OOP shouldBeThreeOOP = vmProxy->strMsgSend(
    intToOOP(1),
    "+",
    intToOOP(2),
    NULL);


OOP vmsgSend (OOP 수신자, OOP 선택자, OOP *args) [함수]

해당 함수는 msgSend와 동일하지만 변수-인자 함수 대신 인자의 널 종료되는 리스트를 향하는 포인터를 수락한다. 예를 들자면 다음과 같다:
OOP arguments[2], shouldBeThreeOOP;
arguments[0] = intToOOP(2);
arguments[1] = NULL;
/* ... some more code here ... */

shouldBeThreeOOP = vmProxy->vmsgSend(
    intToOOP(1),
    symbolToOOP("+"),
    arguments);


OOP nvmsgSend (OOP 수신자, OOP 선택자, OOP *args, int nargs) [함수]

해당 함수는 msgSend와 동일하지만 인자의 널 종료에 의존하지 않고 스몰토크 메서드로 전달될 인자의 수를 포함하는 추가 매개변수를 수락한다. 아래는 그 예제이다:
OOP argument, shouldBeThreeOOP;
argument = intToOOP(2);
/* ... some more code here ... */

shouldBeThreeOOP = vmProxy->nvmsgSend(
    intToOOP(1),
    symbolToOOP("+"),
    &argument,
    1);


OOP perform (OOP, OOP) [함수]

단항 선택자(unary selector)를 선택하기 위한 shortcut 함수. 첫 번째 매개변수는 수신자이고, 두 번째는 선택자이다.


OOP performWith (OOP, OOP, OOP) [함수]

1-인자 선택자를 호출하기 위한 shortcut 함수. 첫 번째 매개변수는 수신자, 두 번째는 선택자, 세 번째는 유일한(sole) 인자이다.


OOP invokeHook (int) [함수]

매개변수에 의해 주어진 ObjectMemory hook를 처리하기 위해 스몰토크로 부른 호출. 사실상 매개변수로부터 도출된 기호로 changed: 가 ObjectMemory 로 전송된다. 매개변수는 다음 중 하나가 될 수 있다:
  • GST_BEFORE_EVAL
  • GST_AFTER_EVAL
  • GST_ABOUT_TO_QUIT
  • GST_RETURN_FROM_SNAPSHOT
  • GST_ABOUT_TO_SNAPSHOT
  • GST_FINISHED_SNAPSHOT
뒤에서 3개를 사용해야 하는 경우는 모두 GNU Smalltalk의 소스 코드에서 다루어야 한다. 하지만 앞에서 3개는 사실상 사용자 코드에서 유용할 것이다.


스몰토크 코드를 직접 수락하는 두 함수가 있는데, evalCode와 evalExpr로, 기본적으로는 둘이 동일하다. 둘 다 단일 매개변수, 그리고 파서로 전송될 코드로의 포인터를 수락한다. 차이점을 들자면 evalCode는 결과를 폐기하는 반면 evalExpr는 결과를 OOP로서 호출자에게 리턴한다는 데 있다.


대신 msgSendf는 근본적으로 다른 구문을 갖고 있다. 예를 몇 가지 살펴보자.

/* 1 + 2 */
int shouldBeThree;
vmProxy->msgSendf(&shouldBeThree, "%i %i + %i", 1, 2)

/* aCollection includes: 'abc' */
OOP aCollection;
int aBoolean;
vmProxy->msgSendf(&aBoolean, "%b %o includes: %s", aCollection, "abc")

/* 'This is a test' printNl -- in two different ways */
vmProxy->msgSendf(NULL, "%v %s printNl", "This is a test");
vmProxy->msgSendf(NULL, "%s %s printNl", "This is a test");

/* 'This is a test', ' ok?' */
char *str;
vmProxy->msgSendf(&str, "%s %s , %s", "This is a test", " ok?");


위에서 볼 수 있듯이 msgSendf로의 매개변수를 순서대로 소개하자면:

  • 레코드(record)를 포함하게 될 변수에 대한 포인터. 해당 포인터가 NULL인 경우 폐기된다.
  • 이 포맷으로 된 메서드의 인터페이스에 대한 설명 (% 부호 다음에 붙은 객체 타입은 이번 절 뒷부분에서 설명할 것이다)
    %result_type %receiver_type selector %param1_type %param2_type
    
  • 수신자에 대한 C 변수 또는 스몰토크 객체 (타입 명시자에 따라)
  • 필요 시 인자에 대한 C 변수와(혹은) 스몰토크 객체 (타입 명시자에 따라)


수신자와 매개변수는 객체 레지스트리에 등록되어 있지 않음을 주목하라 (57 페이지의 5.4절 [스몰토크 타입]을 참조). receiver_type 과 paramX_type 은 아래의 의미를 지닌 문자라면 어떤 것이든 가능하다:

명시자 C 데이터 타입 상응하는 스몰토크 클래스
i long Integer (intToOOP 참고)
f double Float (floatToOOP 참고)
F long double Float (longDoubleToOOP 참고)
b int True or False (boolToOOP 참고)
B OOP BlockClosure
c char Character (charToOOP 참고)
C PTR CObject (cObjToOOP 참고)
s char * String (stringToOOP 참고)
S char * Symbol (symbolToOOP 참고)
o OOP any
t char *, PTR CObject (아래 참고)
T OOP, PTR CObject (아래 참고)
w wchar_t Character (wcharToOOP 참고)
W wchar_t * UnicodeString (wstringToOOP 참고)


'%t' 와 '%T'는 msgSendf 에 추가적으로 하나가 아니라 두 개의 인자를 전달해야 한다는 점에서 특별하다. 첫 번째는 생성될 CObject의 타입 설명이 될 것이고, 두 번째는 CObject의 어드레스가 될 것이다. '%t'라고 명시했다면 두 개 중 첫 번째 인자는 typeNameToOOP 를 통해 CType 으로 변환될 것이다 (57 페이지의 5.4 절 [스몰토크 타입] 참조); 그리고 '%T'라고 명시했다면 새 CObject의 타입에 대한 OOP를 당신이 직접 전달해야 할 것이다.


'%B'의 경우 선택자를 전달해선 안 되며, 블록이 평가될 것이다.


result_type 용으로 전달할 수 있는 타입 명시자는 위와 약간 다르다:

명시자 nil의 경우 결과 C 데이터 타입 예상 결과
i 0L long nil 또는 an Integer
f 0.0 double nil 또는 a Float
F 0.0 long double nil 또는 a Float
b 0 int nil 또는 a Boolean
c '\0' char nil 또는 a Character
C NULL PTR nil 또는 a CObject
s NULL char * nil, a String, 또는 a Symbol
? 0 char *, PTR oopToC 참고
o nilOOP OOP 어느 값이나 가능 (결과가 변환되지 않음)
w '\0' wchar_t nil 또는 a Character
W NULL wchar_t * nil 또는 a UnicodeString
v / 어느 값이나 가능 (결과가 폐기됨)


resultPtr 이 NULL인 경우 result_type 은 항시 '%v'로 취급됨을 주목하라. 오류가 발생 시 'result if nil' 열의 결과가 리턴된다.


C 함수 포인터로서의 스몰토크 블록

스몰토크 콜인(callin) 메커니즘은 콜백을 필요로 하는 C 라이브러리로의 바인딩을 스몰토크에 효과적으로 구성하는 데에 사용할 수 있다. 하지만 라이브러리로 전달되는 콜백 함수는 C에서 작성되었고 그 타입 서명이 고정되어 있으므로 "정적"인 메커니즘이라 할 수 있겠다.


콜백의 서명이 미리 알려지지 않은 경우 C 함수 포인터를 통해서만이 (GTK+에서와 같은 반영적 메커니즘과 반대) 콜백을 정의할 수 있는데, 그러면 스몰토크 콜인을 위한 VMProxy 함수는 충분하지 않다.


GNU Smalltalk는 CCallbackDescriptor 클래스를 통해 좀 더 동적인 방식으로 스몰토크 블록을 C 함수로 변환한다. 이 클래스는 callout에 사용되는 cCall: annotation과 유사한 구성자(constructor) 메서드이다. 메서드에 for:returning:withArgs:가 호출되고, 그 매개변수는 다음과 같다:

  • 블록, 그 인자의 수는 변수임
  • 리턴 타입을 표시하는 기호
  • 인자 타입을 표시하는 배열


세 번째 매개변수로서 전달된 배열은 C에서 스몰토크로 전달된 값을 나타내는데, C callout의 리턴 타입에 의해 사용되는 것과 동일한 규칙으로 채워져야 한다. 특히 C callback이 int *를 수락하는 경우 인자 타입을 #{CInt}로 명시하여 블록이 CInt 객체를 수신하게 될 가능성이 있다(사실상 유용하기도 하다).


glutReshapeFunc[4] 로 전달되는 콜백의 생성 예를 들어보겠다. C에서 바람직한 서명은 void (*) (int, int)이다.

| glut |
...
glut glutReshapeFunc: (CCallbackDescriptor
    for: [ :x :y | self reshape: x@y ]
    returning: #void
    withArgs: #(#int #int))


이러한 유형의 콜백은 이미지 로딩에 걸쳐 살아남지 못한다는 사실을 명심하는 것이 중요하다 (이러한 제약은 향후 버전에선 제거될 것이다). 이미지가 로딩되면 어떤 C 함수로든 전달되기 이전에 link 메시지를 콜백으로 전송함으로써 리셋해야 한다. 이미 유효한 콜백으로 link 메시지를 전송하더라도 무해하며 비용도 저렴하다.


모듈에 이용 가능한 다른 함수

앞서 설명한 함수에 더해 모듈에 이용 가능한 VMProxy는 C에서 GNU 스몰토크 확장판(extension)을 개발하는 데 도움이 되는 많은 함수들을 위한 진입점을 포함한다. 해당 노드는 이러한 함수들, 그리고 libgst/gstpub.h 에서 정의한 매크로를 문서화한다.


void asyncCall (void ( * ) (OOP), OOP) [함수]

해당 함수는 함수 포인터와 OOP를 수락하고 (또는 NULL을 수락하기도 하나 임시 포인터는 수락하지 않음), 다음 메시지 전송이 실행되는 즉시 함수를 호출하도록 해석기를 준비시킨다.
주의: 이 함수와 다음에 소개할 두 개의 함수는 쓰레드 안전(thread-safe)한 intepreterProxy 내 유일한 함수들이다.


void asyncSignal (OOP) [함수]

해당 함수는 Semaphore 객체에 대한 OOP를 수락하고 해당 객체를 시그널링하여 해당 세마포어(semaphore)를 기다리는 프로세스 중 하나를 깨운다. 스몰토크 call-in은 원자 조작이 아니기 때문에 세마포어를 올바로 시그널링하는 방법은 객체에 signal 메서드를 전송하는 것이 아니라 아래를 사용한다:
asyncSignal(semaphoreOOP)


void asyncSignalAndUnregister (OOP) [함수]

해당 함수는 Semaphore 객체에 대한 OOP를 수락하고 해당 객체를 시그널링하여 해당 세마포어를 기다리는 프로세스가 깨어난다; 신호 요청은 다음 메시지 전송이 실행되는 즉시 처리될 것이다. 이후 객체가 레지스트리에서 제거된다.


void syncSignal (OOP, mst_Boolean) [함수]

해당 함수는 Semaphore 객체에 대한 OOP를 수락하고 해당 객체를 시그널링하여 해당 세마포어를 기다리는 프로세스 중 하나를 깨운다. 세마포어가 큐(queue)에 대기 중인 프로세스가 없고 두 번째 인자가 true인 경우, 세마포어에 초과 신호(excess signal)가 추가된다. 스몰토크 call-in은 원자 조작이 아니기 때문에 세마포어를 올바로 시그널링하는 방법은 객체에 signal 이나 notify 메서드를 전송하는 것이 아니라 아래를 사용한다:
asyncSignal(semaphoreOOP, true)
함수명 안에 sync는 asyncSignal로부터 구별을 가능하게 하는데, asyncCall로 이미 예정된 프로시저로부터 또는 call-in으로부터만 호출할 수 있다. 스몰토크 쓰레드 외의 쓰레드에서는 호출할 수 없다.


void syncWait (OOP) [함수]

해당 함수는 Semaphore 객체에 대한 OOP를 수락하고 현재 프로세스를 잠재우는데(put sleep)하는데, 세마포어가 해당 프로세서 위에 초과 신호를 갖지 않는 한에서만 실행한다. 스몰토크 call-in은 원자 조작이 아니기 때문에 세마포어를 올바로 시그널링하는 방법은 객체에 wait 메서드를 전송하는 것이 아니라 아래를 사용한다:
syncWait(semaphoreOOP)
syncSignal의 경우 해당 함수는 asyncCall로 이미 예정된 프로시저로부터 또는 call-in으로부터만 호출할 수 있다. 스몰토크 쓰레드 외의 쓰레드에서는 호출할 수 없다.


void showBacktrace (FILE* ) [함수]

해당 함수는 주어진 파일의 역추적을 보여준다.


OOP objectAlloc (OOP, int) [함수]

objectAllocn 함수는 새로 생성된 클래스의 인스턴스 중에서 OOP가 첫 매개변수로서 전달된 인스턴스에 대한 OOP를 할당한다; 그 매개변수가 클래스가 아닌 경우 결과는 정의되지 않는다 (현재로선 "프로그램이 코어 덤프(core dump)할 가능성이 높다"고 말하지만 향후 버전에는 변경될 수 있다).
두 번째 매개변수는 클래스가 색인 가능한 경우에만 사용되며, 그렇지 않은 경우 폐기된다: 이는 생성될 객체 내에 색인된 인스턴스 변수의 수를 포함한다. objectAlloc의 간단한 사용 예제는 다음과 같다:
OOP myClassOOP;
OOP myNewObject;
myNewObjectData obj;
. . .
myNewObject = objectAlloc(myClassOOP, 0);
obj = (myNewObjectData) OOP_TO_OBJ (myNewObject);
obj->arguments = objectAlloc(classNameToOOP("Array"), 10);
. . .


size_t OOPSize (OOP) [함수]

주어진 객체 내에서 색인된 인스턴스 변수의 수를 리턴한다.


OOP OOPAt (OOP, size_t) [함수]

주어진 객체의 색인된 인스턴스 변수를 리턴한다. 색인은 두 번째 매개변수에 있고 0을 시작점(zero-based)으로 한다. 색인이 범위를 벗어나면 함수는 취소된다.


OOP OOPAtPut (OOP, size_t, OOP) [함수]

세 번째 매개변수로서 주어진 객체를 첫 번째 매개변수로서 주어진 객체의 색인된 인스턴스 변수로 넣는다. 색인은 두 번째 매개변수에 있고 0을 시작점으로 한다. 색인이 범위를 벗어나면 함수는 취소된다.


enum gst_indexed_kind OOPIndexedKind (OOP) [함수]

주어진 객체가 가진 유형의 색인된 인스턴스 변수를 리턴한다.


void * OOPIndexedBase (OOP) [함수]

주어진 객체의 첫 색인된 인스턴스 변수의 포인터를 리턴한다. 프로그램은 먼저 OOPIndexedKind를 이용해 데이터 유형을 검색해야 한다.


OOP getObjectClass (OOP) [함수]

매개변수로서 전달된 스몰토크 객체의 클래스를 리턴한다.


OOP getSuperclass (OOP) [함수]

스몰토크 객체에 의해 주어진 클래스의 슈퍼클래스, 즉 매개변수로서 전달된 대상을 리턴한다.


mst_Boolean clasIsKindOf (OOP, OOP) [함수]

첫 번째 매개변수로서 주어진 클래스가 두 번째 매개변수로서 주어진 클래스와 동일하거나 그 슈퍼클래스인 경우 true를 리턴한다.


mst_Boolean objectIsKindOf (OOP, OOP) [함수]

첫 번째 매개변수로서 주어진 클래스가 두 번째 매개변수로서 주어진 클래스의 인스턴스이거나 그 서브클래스들 중 하나일 경우 true를 리턴한다.


mst_Boolean classImplementsSelector (OOP, OOP) [함수]

첫 번째 매개변수로서 주어진 클래스가 만일 선택자가 두 번째 매개변수로 주어진 메서드를 구현하거나 오버라이드할 경우 true를 리턴한다.


mst_Boolean classCanUnderstand (OOP, OOP) [함수]

첫 번째 매개변수로서 주어진 클래스의 인스턴스가 만일 선택자가 두 번째 매개변수로 주어진 메시지에 응답할 경우 true를 리턴한다.


mst_Boolean respondsTo (OOP, OOP) [함수]

첫 번째 매개변수로서 주어진 객체가 만일 선택자가 두 번째 매개변수로 주어진 메시지에 응답할 경우 true를 리턴한다.


마지막으로 시스템 객체와 가장 중요한 클래스로 접근성을 제공하는 해석기 프록시 몇 가지가 남았다:

  • nilOOP, trueOOP, falseOOP, processorOOP
  • objectClass, arrayClass, stringClass, characterClass, smallIntegerClass, floatDClass, floatEClass, byteArrayClass, objectMemoryClass, classClass, behaviorClass, blockClosureClass, contextPartClass, blockContextClass, methodContextClass, compiledMethodClass, compiledBlockClass, fileDescriptorClass, fileStreamClass, processClass, semaphoreClass, cObjectClass


향후에는 내용이 더 추가될지도 모른다.


매크로는 다음과 같다[5]:


gst_object OOP_TO_OBJ (OOP) [매크로]

OOP에 대한 포인터를 실제 객체에 대한 포인터로 역참조(dereference)한다 (69 페이지의 5.8절 [객체 표현] 참조). 쓰레기 수집이 발생 시 OOP_TO_OBJ의 결과는 더 이상 유효하지 않다; 이러한 이유로 당신은 call-in를 실행하고, objectAlloc를 호출하며, "C to Smalltalk" 함수 중 하나라도 실행하면 객체 데이터에 대한 포인터는 유효하지 않은 것으로 가정해야 한다 (5.4절 [스몰토크 타입] 참조).


OOP OOP_CLASS (OOP) [매크로]

주어진 객체의 클래스에 대한 OOP를 리턴한다. 예를 들어, OOP_CLASS(proxy->stringToOOP("Wonderful gnu Smalltalk")) 는 classNameToOOP("String")에 의해 리턴된 String 클래스이다.


mst_Boolean IS_INT (OOP) [매크로]

OOP가 Integer 객체인지 아닌지를 표시하는 Boolean을 리턴한다; SmallInteger 객체의 값은 gst_object 구조에 따로 인코딩되는 것이 아니라 OOP에 직접 인코딩된다. isInt가 false를 리턴할 경우 OOP_TO_OBJ 와 OOP_CLASS 를 사용하는 것은 안전하지 않다


mst_Boolean IS_OOP (OOP) [매크로]

OOP가 '실제' 객체인지 아닌지를 (SmallInteger 여부가 아니라) 표시하는 Boolean을 리턴한다. IS_OOP가 true를 리턴하는 경우에만 OOP_TO_OBJ 와 OOP_CLASS 의 사용이 안전하다.


mst_Boolean ARRAY_OOP_AT (gst object, int) [매크로]

주어진 Array 객체의 두 번째 매개변수에 주어진 문자로 접근한다. gst_object가 정의된 방식은 indexedOOP의 작동을 막기 때문에 꼭 필요하다.


mst_Boolean STRING_OOP_AT (gst object, int) [매크로]

주어진 String 또는 ByteArray 객체의 두 번째 매개변수에 주어진 문자로 접근한다. gst_object가 정의된 방식은 indexedByte의 작동을 막기 때문에 꼭 필요하다.


mst_Boolean INDEXED_WORD (some-object-type, int) [매크로]

variableSubclass 내에 주어진 색인된 인스턴스 변수로 접근한다. 첫 번째 매개변수는 69 페이지의 5.8절 [객체 표현]에 설명된 바와 같이 선언된 구조여야 한다.


mst_Boolean INDEXED_BYTE (some-object-type, int) [매크로]

variableByteSubclass 내에 주어진 색인된 인스턴스 변수로 접근한다. 첫 번째 매개변수는 69 페이지의 5.8절 [객체 표현]에 설명된 바와 같이 선언된 구조여야 한다.


mst_Boolean INDEXED_OOP (some-object-type, int) [매크로]

variableSubclass 내에 주어진 색인된 인스턴스 변수로 접근한다. 첫 번째 매개변수는 69 페이지의 5.8절 [객체 표현]에 설명된 바와 같이 선언된 구조여야 한다.


자신만의 스몰토크 클래스의 인스턴스를 C로부터 조작하기

GNU Smalltalk의 라이브러리는 가장 공통된 기반 클래스의 인스턴스를 처리하기 위한 함수를 제공하긴 하지만 머지않아 자신의 프로그램에서 정의된 클래스의 인스턴스를 C가 직접 처리할 수 있길 원할 것이다. 이를 위해서는 3가지 단계를 따라야 한다.

  • 스몰토크 클래스 정의하기
  • 클래스의 표현을 매핑하는 C struct 정의하기
  • 실제로 C struct 사용하기


이번 장에서는 스몰토크 인터페이스를 SQL 서버로 정의하는 가상업무를 고려하여 위의 단계를 실행해볼 것이다.


첫 번째는 가장 간단한 단계인데, 스몰토크 클래스를 정의하는 작업은 매우 쉽고 실용적인 한 가지 방식으로 완료할 수 있기 때문이다; 아래를 실행하는 표준 스몰토크 코드를 평가하면 될 일이다:

Object subclass: SQLAction [
    | database request |
    <category: 'SQL-C interface'>
]

SQLAction subclass: SQLRequest [
    | returnedRows |
    <category: 'SQL-C interface'>
]


Object 로부터 파생된 클래스에 대한 C struct를 정의하기 위해 GNU Smalltalk의 gstpub.h 은 OBJ_HEADER 매크로를 정의하는 파일을 포함하는데, 이 매크로는 모든 객체의 헤더를 구성하는 필드(field)를 정의한다. SQLAction 결과에 대한 struct 의 정의는 다음 코드와 같다:

struct st_SQLAction {
    OBJ_HEADER;
    OOP database;
    OOP request;
}


메모리에서 SQLRequest의 표현은 다음과 같다:

.------------------------------.
|     common object header     |      2 longs
|------------------------------|
| SQLAction instance variables |
|           database           |      2 longs
|           request            |
|------------------------------|
| SQLRequest instance variable |
|         returnedRows         |      1 long
'------------------------------'


struct 정의하는 첫 번째 방식은 다음이 될 것이다:

typedef struct st_SQLAction {
    OBJ_HEADER;
    OOP database;
    OOP request;
    OOP returnedRows;
} *SQLAction;


하지만 중복 코드가 많이 발생한다. SQLAction의 다른 서브클래스, 가령 SQLObjectCreation이나 SQLUpdateQuery 등의 서브클래스가 있다면 어떨까 생각해보라. GNU Smalltalk의 소스 코드에서 사용되기도 하는 해결방법은 다음과 같이 각 슈퍼클래스에 매크로를 정의하는 방법이다:

/* SQLAction
|-- SQLRequest
| '-- SQLUpdateQuery
'-- SQLObjectCreation 		*/

#define ST_SQLACTION_HEADER 	\
    OBJ_HEADER; 			\
    OOP database; 		\
    OOP request 			/* no semicolon */

#define ST_SQLREQUEST_HEADER 	\
    ST_SQLACTION_HEADER; 	\
    OOP returnedRows 		/* no semicolon */

typedef struct st_SQLAction {
    ST_SQLACTION_HEADER;
} *SQLAction;

typedef struct st_SQLRequest {
    ST_SQLREQUEST_HEADER;
} *SQLRequest;

typedef struct st_SQLObjectCreation {
    ST_SQLACTION_HEADER;
    OOP newDBObject;
} *SQLObjectCreation;

typedef struct st_SQLUpdateQuery {
    ST_SQLREQUEST_HEADER;
    OOP numUpdatedRows;
} *SQLUpdateQuery;


당신이 선언하는 매크로가 슈퍼클래스와 서브클래스의 선언에서 OBJ_HEADER 대신 사용된다는 점을 주목하라.


이 예제에서는 물론 그렇지 않지만 클래스가 색인된 인스턴스 변수를 가진 경우 당신은 어떤 것도 선언해선 안 된다는 사실을 명심하기 바란다.


사실상 자신의 struct를 사용하는 데 있어 첫 번째 단계는 자신의 클래스의 인스턴스에 해당하는 OOP로의 포인터를 획득하는 일이다. 이를 실행하는 방법으로는 call-in 실행하기, call-out에서 객체 수신하기가 포함된다 (#smalltalk, #self 또는 #selfSmalltalk을 타입 명시자로서 사용).


oop 변수는 그러한 객체를 포함한다고 가정해보자. 그렇다면 OOP(57 페이지의 5.4절 [스몰토크 타입]에서 살펴보았듯 실제 객체를 간접적으로만 가리키는)를 역참조하고 실제 데이터에 대한 포인터를 얻어야 한다. 이는 OOP_TO_OBJ 매크로를 이용해 실행한다 (타입 캐스팅 주목):

SQLAction action = (SQLAction) OOP_TO_OBJ(oop);


이제 이러한 pseudo-code에서와 같이 객체에서 필드를 사용할 수 있다:

/* These are retrieved via classNameToOOP and then cached in global variables */
OOP sqlUpdateQueryClass, sqlActionClass, sqlObjectCreationClass;
...
invoke_sql_query(
    vmProxy->oopToCObject(action->database),
    vmProxy->oopToString(action->request);
    query_completed_callback, 		/* Callback function */
    oop); 					/* Passed to the callback */
...

/* Imagine that invoke_sql_query runs asynchronously and calls this when the job is done. */
void
query_completed_callback(result, database, request, clientData)
    struct query_result *result;
    struct db *database;
    char *request;
    OOP clientData;
{
    SQLUpdateQuery query;
    OOP rows;
    OOP cObject;

    /* Free the memory allocated by oopToString */
    free(request);

    if (OOP_CLASS (oop) == sqlActionClass)
        return;
        
    if (OOP_CLASS (oop) == sqlObjectCreationClass)
        {
            SQLObjectCreation oc;
            oc = (SQLObjectCreation) OOP_TO_OBJ (clientData);
            cObject = vmProxy->cObjectToOOP (result->dbObject)
            oc->newDBObject = cObject;
        }
    else
        {
            /* SQLRequest or SQLUpdateQuery */
            cObject = vmProxy->cObjectToOOP (result->rows);
            query = (SQLUpdateQuery) OOP_TO_OBJ (clientData);
            query->returnedRows = cObject;
            if (OOP_CLASS (oop) == sqlUpdateQueryClass)
            query->numReturnedRows = vmProxy->intToOOP (result->count);
        }
}


쓰레기 수집이 발생하지 경우 OOP_TO_OBJ 의 결과는 더 이상 유효하지 않음을 주목하라; 이러한 이유로 당신은 call-in를 실행하고, objectAlloc를 호출하며, "C to Smalltalk" 함수 중 하나라도 실행하면 객체 데이터에 대한 포인터는 유효하지 않은 것으로 가정해야 한다 (57 페이지의 5.4절 [스몰토크 타입] 참조). 이것이 바로 필자가 OOP를 객체 포인터 자체가 아니라 콜백으로 전달하는 이유다.


자신의 클래스가 색인된 인스턴스 변수를 갖는다면 gstpub.h 에 선언된 INDEX_WORD, INDEXED_OOP, INDEXED_BYTE를 사용할 수 있는데, 이는 주어진 색인된 인스턴스 변수에 대한 값을 리턴한다-상세한 정보는 65 페이지의 5.7절 [기타 C 함수]를 참고하라.


스몰토크 환경을 확장 라이브러리로 사용하기

GNU Smalltalk에 대한 확장을 작성하기 위해 이번 장을 읽고 있다면 5.9절엔 흥미가 없을 것이다. 하지만 GNU Smalltalk를 향후 멋들어진 소프트웨어 프로그램을 위한 스크립팅 언어나 확장 언어로 사용하려 한다면 충분히 흥미가 생길 것이다.


GNU Smalltalk를 초기화하는 방식은 GNU Smalltalk 고유의 소스 코드에서 가장 간략하고 쉽게 설명되어 있다. 이러한 이유로 gst-too.c 에서 간소화한 소스 코드를 발췌하겠다:

int main(argc, argv)
int argc;
char **argv;
{
    gst_set_var (GST_VERBOSITY, 1);
    gst_smalltalk_args (argc - 1, argv + 1);
    gst_set_executable_path (argv[0]);
    result = gst_initialize ("kernel-dir", "image-file", GST_NO_TTY);
    
    if (result != 0)
        exit (result < 0 ? 1 : result);

    if (!gst_process_file ("source-file", GST_DIR_KERNEL_SYSTEM))
        perror ("gst: couldn't load 'source-file'");
        
    gst_invoke_hook (GST_ABOUT_TO_QUIT);
    exit (0);
}


당신의 초기화 코드는 gst_process_file 로의 호출을 제외하면 GNU Smalltalk의 main ( )과 거의 동일할 것이다. 그저 gst_smalltalk_args 를 통해 GNU Smalltalk 라이브러리로 몇 개의 인자만 전달하고, gst_get_var와 gst_set_var를 이용해 기본값 몇 가지를 수정한 후 gst_initialize를 호출하기만 하면 된다.


gst_get_var와 gst_set_var로 전달 가능한 변수 색인은 다음을 포함한다:

GST_DECLARE_TRACING
GST_EXECUTION_TRACING
GST_EXECUTION_TRACING_VERBOSE
GST_GC_MESSAGE
GST_VERBOSITY
GST_MAKE_CORE_FILE
GST_REGRESSION_TESTING


마지막 매개변수로서 gst_initialize로 전달 가능한 플래그는 아래의 어떤 조합이든 괜찮다:

GST_REBUILD_IMAGE
GST_MAYBE_REBUILD_IMAGE
GST_IGNORE_USER_FILES
GST_IGNORE_BAD_IMAGE_NAME
GST_IGNORE_BAD_IMAGE_PATH
GST_IGNORE_BAD_KERNEL_PATH
GST_NO_TTY


gst_initialize 는 이미지 파일의 재빌드 필요성 여부를 확인하고, 필요할 경우 기본 이미지를 형성하는 스몰토크 코드 50,000 행 이상을 재로딩 및 재컴파일하기 때문에 시간이 소요될 것을 명심하라 (1/10초~3-4초). 확인을 건너뛰려면 gst_initialize 로 유효한 이미지 파일을 두 번째 인자로서 전달하라.


성공 시 gst_init_smalltalk의 결과는 0이고, 그 외의 경우는 오류 코드가 발생한다.


GNU Smalltalk를 확장 라이브러리로 사용 중이라면 두 개의 ObjectMemory 클래스 메서드, quit 과 quit: 메서드를 비활성화시켜야 할 것이다. 스몰토크 커널 코드는 변경하지 않길 권한다. 대신 자신의 확장 클래스를 로딩하는 스크립트에 아래 두 행을 추가하면:

ObjectMemory class compile: 'quit self shouldNotImplement'!
ObjectMemory class compile: 'quit: n self shouldNotImplement'!


문제의 두 메서드를 효과적으로 비활성화시킬 수 있을 것이다. 그 외 방법으로, 프로그램을 좀 덜 충격적 방식으로 종료하기 위해 atexit (C 라이브러리에서)를 이용하거나, 위의 메서드를 자신의 프로그램에서 C 루틴으로 호출을 종료하도록 재정의하는 방법이 있다.


또한 GNU Smalltalk 환경(자신만의 C call-out에 대한 defineCFunc를 호출하지 않을 것이다) 내에서 자신의 프로그램을 위한 클래스 라이브러리를 개발하더라도 문제가 되지 않는데, C call-out의 어드레스는 이미지가 복구되면 다시 검사되기 때문이다.


인큐베이터 지원

인큐베이터 개념은 새로 생성된 객체를 루트 집합(root set)에서 접근 가능한 객체로 소속되기 전에 뜻하지 않게 쓰레기로 수집되지 않도록 보호하기 위한 메커니즘을 제공한다.


여전히 "라이브" 객체(객체의 루트 집합에서 접근 가능한)로 즉시(즉, 다음 객체가 스몰토크 메모리 시스템으로부터 할당되기 전에) 소속되지 않을 객체 집합을 생성 중이라면, 이 인터페이스를 사용할 필요가 있다.


스몰토크로부터 C call-out을 작성하고 있다면 (예: 모듈 내부에), 인큐베이터로의 직접 접근성을 갖지 않을 것이다; 대신 57 페이지의 5.4절 [스몰토크 타입]에 설명된 함수는 그들이 생성한 객체를 자동으로 인큐베이터에 넣고, 가상 머신이 C call-out을 관리하여 인큐베이터 상태는 호출이 끝날 무렵에 복구된다.


이번 절은 libgst.a 로 연결된 프로그램의 관점에서 사용법을 설명한다. 그러한 프로그램은 인큐베이터에 훨씬 더 정밀한 제어력을 가진다. 인터페이스는 아래와 같은 연산을 제공한다:


void INC_ADD_OOP (OOP anOOP) [매크로]

새 객체를 보호된 집합에 추가한다.


inc_ptr INC_SAVE_POINTER ( ) [매크로]

현재 인큐베이터 포인터를 검색한다. 인큐베이터를 스택으로 생각하고, 이 연산은 incRestorePointer 함수를 이용해 추후 사용을 (복구) 위해 현재 스택 포인터를 리턴한다.


void INC_RESTORE_POINTER (inc_ptr ptr) [매크로]

인큐베이터 포인터를 주어진 포인터 값으로 설정(복구)한다.


보통 간접적으로든 직접적으로든 한 번에 하나 이상의 객체를 할당하는 함수 내부에 있다면 인큐베이터 메커니즘을 사용하길 원할 것이다. 먼저 현재 포인터의 복사본을 지역 변수에 저장할 것이다. 그리고 당신이 할당하는 각 객체마다 (최적을 위해서는 마지막 객체는 제외) 객체를 생성한 후에 그것을 인큐베이터의 리스트에 추가한다. 리턴 시에는 incSavePointer 로 얻은 값에 대한 인큐베이터의 포인터를 incRestorePoint 함수를 사용하여 복구할 필요가 있다.


cint.c 의 예를 소개하겠다:


기존의 오래된 코드는 다음과 같았다 (예제에 주석이 추가되었다):

desc = (_gst_cfunc_descriptor)
    new_instance_with (cFuncDescriptorClass, numArgs);
desc->cFunction = _gst_cobject_new (funcAddr); // 1
desc->cFunctionName = _gst_string_new (funcName); // 2
desc->numFixedArgs = FROM_INT (numArgs);
desc->returnType = _gst_classify_type_symbol (returnTypeOOP, true);

for (i = 1; i <= numArgs; i++) {
    desc->argTypes[i - 1] =
    _gst_classify_type_symbol(ARRAY_AT(argsOOP, i), false);
}

return (_gst_alloc_oop(desc));


desc는 본래 objectAlloc public 루틴에 의해 캡슐화되는 두 개의 private 루틴인 newInstanceWith와 allocOOP를 통해 할당된다. "1"에서 더 많은 저장공간이 할당되고, 쓰레기 수집기는 desc의 저장공간을 실행 및 해제할 가능성을 지닌다 (어떤 라이브 객체도 그것을 참조하지 않기 때문에). "2"에서는 다른 객체가 할당되고, GC가 실행될 경우 desc와 desc->cFunction 를 잃게 될 가능성이 존재한다 (실제로 발생하기도 했다!).


인큐베이터를 사용하기 위해 코드를 수정하려면 아래와 같이 수정하라:

OOP descOOP;
IncPtr ptr;

incPtr = INC_SAVE_POINTER();
desc = (_gst_cfunc_descriptor)
    new_instance_with (cFuncDescriptorClass, numArgs);
descOOP = _gst_alloc_oop(desc);
INC_ADD_OOP (descOOP);

desc->cFunction = _gst_cobject_new (funcAddr); 		// 1
INC_ADD_OOP (desc->cFunction);

desc->cFunctionName = _gst_string_new (funcName); 	// 2
/*나머지 함수 부분은 (또는 그것이 호출하는 함수들조차)
* 어떤 저장공간도 할당하지 않으므로, desc->cFunctionName을
* 인큐베이터의 객체 집합으로 추가할 필요가 없으나,
* 이 함수로부터 호출되는 함수의 구현에 일어나는 변경내용에서
* 완전히 안전하려면 추가해도 좋다.
*/

desc->numFixedArgs = FROM_INT (numArgs);
desc->returnType = _gst_classify_type_symbol (returnTypeOOP, true);

for (i = 1; i <= numArgs; i++) {
    desc->argTypes[i - 1] =
    _gst_classify_type_symbol(ARRAY_AT(argsOOP, i), false);
}

return (_gst_alloc_oop(desc));


두 개 이상의 함수가 협력하여 인큐베이터를 사용하도록 허락할 수 있음을 주목하라. 예를 들어, 함수 A는 객체 몇 개를 할당하고, 좀 더 많은 객체를 할당하는 함수 B를 할당한 다음, 할당된 객체를 이용해 추후 실행이 발생하는 A로의 리턴을 제어한다. B가 A에 의해서만 호출되는 경우 B는 인큐베이터 포인터의 관리를 A에게 남겨두고 인큐베이터로 할당하는 객체를 등록만 할 수도 있다. A가 INC_RESTORE_POINTER를 실행할 때는 인큐베이터의 객체 집합으로부터 B가 등록한 객체들도 자동으로 청소한다; 인큐베이터는 함수 A&B에 대해 알지 못하므로 그에 관한 한 등록된 객체는 모두 동일한 함수로부터 등록되었다.


Notes

  1. 가장 대표적인 플랫폼으로는 AIX와 Windows가 있다.
  2. -no-undefined 의 명시가 꼭 필요한 것은 아니지만 위에 설명된 이식성 조건을 (가상 머신 내 기호에 대한 참조가 존재하지 않도록) 만족시키기 위해 실행한다.
  3. 사실상 공통된 슈퍼클래스 CCompound를 갖고 있다.
  4. GLUT 바인딩은 콜백의 설치에 다른 스킴(scheme)을 사용한다.
  5. IS_NIL과 IS_CLASS는 유독 공유 라이브러리(모듈)에 정의되지 않은 기호가 표시되는 문제가 발생하여 제거되었다. 현재 libgst.a에 private하다. 해석기 프록시의 nilOOP 필드 또는 getObjectClass를 사용해야 한다.