본문으로 바로가기

지금까지의 강좌를 정리해 보면, C로 Tcl/TK의 커맨드를 추가하는 방법에는 공유 라이브러리의 형태로 만들어두고, 이것을 Tcl언어의 load 커맨드로 로드하는 방법이었습니다. 물론 이 방법이 많이 사용되고 있는 방법이지만, 이 라이브러리 파일 이름을 실제로 파일이 있는 위치와 함께 정확하게 지정하지 않으면 안 됩니다. 라이브러리 설치 위치를 변경하면, load 하는 위치를 변경해줘야 합니다. Tcl 8.0에서는 load 커맨드의 지원과 동시에, 이러한 확장 라이브러리의 관리를 위한 개념으로서, 패키지(Package)라고 하는 시스템이 도입되었습니다. 지금까지는 무의식적으로 패키지라고 부르고 있었으며, 패키지의 정식적인 사용방법을 소개합니다.

C언어로 라이브러리 만들기

패키지는 C언어 외에 순수하게 Tcl로 작성할 수 있으며, 사용자는 어느 언어로 작성되어 있는지 개의치 않고 사용할 수 있습니다. 우선 C언어로 패키지를 만드는 방법에 대해서 알아보겠습니다. 예제는 "88 서울 올림픽이 몇 년이 지났는지"를 보여주는 예제로 합니다.

#include <stdio.h>
#include <tcl.h>
 
static int afterolympicObjCmd(
   ClientData clientData, Tcl_Interp* interp,
   int objc, Tcl_Obj* CONST objv[]){
 
   int year;
   if(objc < 2){
      Tcl_WrongNumArgs(interp, 1, objv, "year(A.D.)");
      return TCL_ERROR;
   }
   if(Tcl_GetIntFromObj(interp, objv[1], &year) == TCL_ERROR) {
      return TCL_ERROR;
   }
 
   if(year <= 1988) {
      char ystr[20];
      sprintf(ystr, "%-19d", year);
      Tcl_AppendResult(interp, "no such year: ", ystr, NULL);
      return TCL_ERROR;
   }
   Tcl_SetObjResult(interp, Tcl_NewIntObj(year-1988));
   return TCL_OK;
}
 
DLLEXPORT int Afterolympic_Init(Tcl_Interp* interp){
   Tcl_CreateObjCommand(interp, "afterolympic", afterolympicObjCmd, NULL, NULL);
   return Tcl_PkgProvide(interp, "afterolympic", "1.00");
}

위 소스에서 눈여겨보실 부분은

DLLEXPORT int Afterolympic_Init(Tcl_Interp* interp){

이 부분입니다. 여기서는 반듯이 함수의 이름을 Afterolympic_Init라고 해야 합니다. 이 함수명의 ‘_Init’ 앞부분은, 반듯이 첫 문자는 영대문자, 나머지 문자는 소문자로 해야 합니다. 즉 첫 대문자, 나머지는 소문자로 하는 것이 패키지 만들 때의 법칙입니다. 그럼 컴파일을 해 보록 하겠습니다.

Visual C++

CC = cl
SHLD = cl /LD
TCLROOT = C:\TCL
CFLAGS =/nologo /O2 /I"$(TCLROOT)\include"
LDFLAGS =/nologo
LIBS ="$(TCLROOT)\lib\tcl84.lib" "$(TCLROOT)\lib\tk84.lib"
.c.obj:
   $(CC)$(CFLAGS) -c $*.c
OBJS = afterolympic.obj
afterolympic.dll:$(OBJS)
   $(SHLD) /o $@ $(LDFLAGS) $(OBJS) $(LIBS)
gcc
CC   = gcc
SHLD   = gcc -shared
CFLAGS   = -fPIC -I/usr/local/include

LDFLAGS   = -L/usr/local/lib
LIBS   = -ltcl8.4 -lm
.c.o:
$(CC) -o $@ $(CFLAGS) -c $?
libafterolympic.so : afterolympic.o
$(SHLD) -o $@ $(CFLAGS) $?

여기서 주의할 점은 반드시 확장 라이브러리의 확장자는 dll이나 so로 지정해야 합니다. 확장자라고 한 것은 사용하는 플랫폼에서의 라이브러리 확장자로써, 윈도즈에서는 dll이며, 대다수 UNIX계열에서는 so입니다. 예외로 HP-UX는 sl입니다.

순수 Tcl로 라이브러리 만들기

앞에서 패키지는 순수 Tcl로도 만들 수 있다고 알려드렸습니다. 이것은 여러 procedure를 모아 놓은 것이라 생각하시면 쉬울 것입니다. (펄의 모듈(Module)과 같은 것입니다.) 그럼 소스를 보겠습니다.

proc unafterolympic {year} {
     return [expr $year-1988]
}
package provide unafterolympic 1.00

이 소스를 우선 afterolympic.tcl로 저장을 합니다. 이 소스에서 눈여겨보실 부분은 아래 부분입니다.

package provide afterolympic 1.00

우선 package provide로 시작하여 첫 번째 인자는 패키지 이름, 두 번째 인자는 패키지의 버전입니다. 버전은 새로운 기능 추가나 버그가 수정이 될 때마다 올리시면 됩니다. 여기서의 이름은 프로그래머 자유입니다만, "패키지이름.tcl" 로 붙이는 것이 좋습니다.

설치하기

그럼 이제 디렉토리 하나를 만들고 그곳에 C로 작성한 확장 라이브러리와 Tcl로 작성한 확장 라이브러리를 복사합니다. 디렉토리는 Tcl이 설치된 lib에 패키지 이름-버전으로 만듭니다. 이것이 보통 패키지가 있는 디렉토리의 네이밍 룰(rule)입니다.

이제 디렉토리에서 tclsh로 tcl 인터프리터를 실행합니다. 그리고 아래와 같이 입력합니다.

다음과 같은 pkgIndex.tcl 파일이 생성됩니다.

# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script.  It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands.  When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
 
package ifneeded afterolympic 1.00 [list load [file join $dir afterolympic.dll]]
package ifneeded unafterolympic 1.00 [list source [file join $dir unafterolympic.tcl]]

이것으로 패키지가 만들어졌습니다.

패키지 사용 하기

이제 패키지를 사용해 볼 차례입니다. 이 패키지를 사용하기 위한 방법은 load커맨드나 source커맨드가 아닌, package require입니다.

위와 같이 버전이 리턴된다면 성공입니다. '이러한 패키지를 어떻게 자동으로 찾는 걸까?' 하고 의문을 가지시는 분이 계실 겁니다. 이 패키지는 보통 Tcl이 설치된 디렉토리의 lib 디렉토리내에서 찾습니다. 다른 디렉토리에서도 찾기를 원한다면 auto_path 변수에 그 디렉토리를 추가합니다.

lappend auto_path /tcl/lib

또한 환경변수 TCLLIBPATH에 지정을 해주면 그 패스에서도 찾습니다. TCLLIBPATH의 설정법은 다음과 같습니다.

TCLLIBPATH=/THE/PATH/OF/DIR1 /THE/PATH/OF/DIR2

아래와 같은 방법은 안됩니다.

TCLLIBPATH="/THE/PATH/OF/DIR1 /THE/PATH/OF/DIR2"

또 아래와 같은 방법도 안됩니다.

TCLLIBPATH={/THE/PATH/OF/DIR1 /THE/PATH/OF/DIR2}
TCLLIBPATH=/THE/PATH/OF/DIR1:/THE/PATH/OF/DIR2
TCLLIBPATH=/THE/PATH/OF/DIR1;/THE/PATH/OF/DIR2

마지막으로 우리가 만든 확장 패키지를 사용해 봅니다.