본문으로 바로가기

순수 Tcl 확장 패키지 만들기

category 카테고리 없음 2025. 8. 19. 10:01

순수 Tcl 확장 패키지 만들기

Tcl 확장은 C/C++ 언어로 만들 수도 있지만, 여기서는 100% 순수 Tcl로 확장 패키지를 만드는 방법을 설명합니다. Tcl에는 Tcllib라는 100% 순수 Tcl로 작성된 확장 패키지가 있습니다. Tcl에서 자주 사용하는 편리한 프로시저들을 패키지화한 것입니다. Tcllib는 Tcl로 Tcl 확장 패키지를 만드는 데 좋은 본보기가 될 것입니다.

프로시저 만들기

먼저, 패키지에 넣을 프로시저를 만듭니다. 여기서는 예제로 리눅스의 cat, head 명령과 비슷한 프로시저를 정의합니다. cat은 파일의 내용을 모두 출력하는 프로시저이고, head는 파일의 앞부분에서 지정한 행 수만큼 출력하는 프로시저입니다.

#
# cat file ...
#
proc cat {args} {
    set out {}
    foreach file $args {
        if {[catch {open $file r} fd]} {
            error "No such file or directory"
        }
        while {![eof $fd]} {
            if {[gets $fd line] < 0} {
                break
            }
            append out $line "\n"
        }
        close $fd
    }
    return $out
}

#
# head file ?number?
#
proc head {file {num 10}} {
    if {[catch {open $file r} fd]} {
        error "No such file or directory"
    }
    set out {}
    set count 0
    while {![eof $fd]} {
        if {[gets $fd line] < 0} {
            break
        }
        append out $line "\n"
        incr count
        if {$count == $num} {
            break
        }
    }
    close $fd
    return $out
}

패키지 이름 정하기

여기서는 mylib이라는 이름으로 패키지를 만듭니다. 파일명은 mylib.tcl로 합니다.

# mylib.tcl
package require Tcl 8.0
package provide mylib 1.0

package require 명령은 이 패키지를 동작시키는 데 필요한 패키지를 지정합니다. 이 예에서는 Tcl 8.0 이상이 필요합니다. package provide 명령은 패키지 이름과 버전을 정의합니다.

네임스페이스화 하기

프로시저 이름이 다른 패키지와 충돌할 경우를 대비해, 프로시저를 namespace로 감쌉니다.

# mylib.tcl
namespace eval ::mylib {
    namespace export *
}

namespace export 명령은 모든 프로시저를 외부에 공개합니다. cat과 head 프로시저는 namespace로 감싸기 위해 수정합니다.

# mylib.tcl

#
# cat file ...
#
proc ::mylib::cat {args} {
    set out {}
    foreach file $args {
        if {[catch {open $file r} fd]} {
            error "No such file or directory"
        }
        while {![eof $fd]} {
            if {[gets $fd line] < 0} {
                break
            }
            append out $line "\n"
        }
        close $fd
    }
    return $out
}

#
# head file ?number?
#
proc ::mylib::head {file {num 10}} {
    if {[catch {open $file r} fd]} {
        error "No such file or directory"
    }
    set out {}
    set count 0
    while {![eof $fd]} {
        if {[gets $fd line] < 0} {
            break
        }
        append out $line "\n"
        incr count
        if {$count == $num} {
            break
        }
    }
    close $fd
    return $out
}

인덱스 생성

Tcl에서 패키지를 자동으로 로드할 수 있도록 인덱스를 생성합니다. tclsh 또는 wish로 mylib.tcl이 있는 위치로 이동하여 pkg_mkIndex 명령을 실행합니다.

$ cd mylib
$ pkg_mkIndex . mylib.tcl

mylib.tcl과 같은 디렉토리에 pkgIndex.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 mylib 1.0 [list source [file join $dir mylib.tcl]]

이로써 mylib 패키지가 완성되었습니다.

패키지 설치

mylib 패키지를 시스템의 tcl8x/lib에 설치합니다. Tcl8x/lib 아래에 mylib1.0 디렉터리를 생성합니다. 생성한 Tcl8x/lib/mylib1.0 에 mylib.tcl 과 pkgIndex.tcl을 복사합니다. 만약, 어떤 이유로 시스템에 설치할 수 없다면, auto_path 변수에 mylib1.0이 있는 디렉터리를 추가함으로써 해결할 수 있습니다.

lappend auto_path {d:/work/mylib1.0}

이렇게 하면 mylib 패키지 설치가 완료됩니다.

패키지 사용해보기

이제 mylib 패키지를 사용해 봅시다. package require 명령으로 mylib 패키지 버전 1.0을 사용한다고 선언하면, Tcl이 인덱스에서 mylib 패키지를 찾아서 로드합니다. 모든 프로시저를 import 하면 네임스페이스 스코프 지정 없이 사용할 수 있습니다. 

package require mylib 1.0

::mylib::cat foo.txt bar.txt
::mylib::head foo.txt 5

namespace import mylib::*
cat foo.txt bar.txt
head foo.txt 5

이와 같이 Tcl로 Tcl 확장 패키지를 만드는 것은 의외로 간단합니다.