load — 기계 코드를 로드하고 새 명령어 초기화하기
load — 기계 코드를 로드하고 새 명령어 초기화하기
C나 C++ 같은 언어로 만든 공유 라이브러리·DLL을 Tcl 인터프리터 안으로 불러와서, 그 안에 정의된 새 명령어를 쓰고 싶을 때 쓰는 명령어가 load예요. 라이브러리를 애플리케이션의 주소 공간에 적재하고, 초기화 프로시저를 호출해 해당 인터프리터에 통합해요. 확장 라이브러리를 만드는 데 핵심이 되는 명령어예요.
출처: 문서
본문
load 명령어는 파일에서 이진 코드를 애플리케이션의 주소 공간으로 로드하고, 라이브러리의 초기화 프로시저를 호출해 인터프리터에 통합해요. fileName은 코드를 담은 파일의 이름이고, 그 정확한 형태는 시스템마다 다르지만 대부분의 시스템에서 Solaris의 .so 파일이나 Windows의 DLL 같은 공유 라이브러리예요. prefix는 초기화 프로시저의 이름을 계산하는 데 쓰여요. interp는 라이브러리를 로드할 인터프리터의 경로 이름이에요(interp 매뉴얼 항목 참고). interp를 생략하면 load 명령어가 호출된 인터프리터가 기본이 돼요.
구문은 다음과 같아요.
load ?-global? ?-lazy? ?--? fileName
load ?-global? ?-lazy? ?--? fileName prefix
load ?-global? ?-lazy? ?--? fileName prefix interp
파일이 애플리케이션의 주소 공간으로 로드되면 새 코드에서 두 초기화 프로시저 중 하나가 호출돼요. 보통 초기화 프로시저는 Tcl 인터프리터에 새 명령어를 추가해요. 초기화 프로시저의 이름은 prefix와 대상 인터프리터가 safe 인터프리터인지 여부에 따라 정해져요. 일반 인터프리터의 경우 초기화 프로시저 이름은 pfx_Init 형태예요. 여기서 pfx는 prefix와 같되 첫 글자는 대문자로, 나머지 글자는 소문자로 바뀐 것이에요. 예를 들어 prefix가 foo나 FOo면 초기화 프로시저 이름은 Foo_Init이에요.
대상 인터프리터가 safe 인터프리터면 초기화 프로시저 이름은 pfx_Init 대신 pfx_SafeInit이에요. pfx_SafeInit 함수는 신뢰할 수 없는 코드가 쓰기에 안전한 라이브러리가 제공하는 일부 기능으로만 safe 인터프리터를 초기화하도록 신중하게 작성해야 해요. Safe-Tcl에 대한 자세한 내용은 safe 매뉴얼 항목을 참고해요.
초기화 프로시저는 다음 프로토타입과 맞아야 해요.
typedef int Tcl_PackageInitProc(
Tcl_Interp *interp);
interp 인수는 라이브러리가 로드될 인터프리터를 식별해요. 초기화 프로시저는 성공 여부를 나타내기 위해 TCL_OK 또는 TCL_ERROR를 반환해야 해요. 오류가 나면 인터프리터의 result가 오류 메시지를 가리키도록 설정해야 해요. load 명령어의 결과는 초기화 프로시저가 반환한 결과가 돼요.
파일의 실제 로드는 애플리케이션에서 fileName 하나당 한 번만 이루어져요. 주어진 fileName을 여러 인터프리터에 로드하면 첫 번째 load가 코드를 로드하고 초기화 프로시저를 호출하며, 이후 load들은 코드를 다시 로드하지 않고 초기화 프로시저만 호출해요. 8.5보다 낮은 Tcl 버전에서는 라이브러리를 언로드하거나 다시 로드할 수 없어요. 그러나 8.5부터는 unload 명령어가 Tcl의 언로딩 메커니즘을 아는 라이브러리에 대해 load로 로드한 라이브러리를 언로드할 수 있게 해줘요.
load 명령어는 Tcl_StaticPackage 프로시저를 호출해 등록된, 애플리케이션과 정적으로 링크된 라이브러리도 지원해요. fileName이 빈 문자열이면 prefix를 지정해야 해요.
prefix를 생략하거나 빈 문자열로 지정하면 Tcl이 prefix를 추측하려 해요. 플랫폼마다 다르게 할 수 있어요. 대부분의 UNIX 플랫폼에서 쓰는 기본 추측은 fileName의 마지막 요소를 취해서, 그 처음 세 글자가 lib이면 벗겨내고, 이어지는 알파벳·밑줄 문자를 titlecase로 변환해 prefix로 쓰는 거예요. 예를 들어 load libxyz4.2.so는 prefix Xyz를 쓰고, load bin/last.so {}는 prefix Last를 써요.
fileName이 빈 문자열이면 prefix를 지정해야 해요.
load 명령어는 먼저 그 이름의 정적으로 로드된 라이브러리(Tcl_StaticPackage 프로시저 호출로 등록된 것)를 찾고, 발견되면 그것을 사용해요. 그렇지 않으면 그 이름의 동적으로 로드된 라이브러리를 찾아 발견되면 사용해요. 라이브러리의 다른 버전으로 여러 파일이 load되었다면 Tcl은 먼저 로드된 파일을 골라요.
filename 앞에 -global을 지정하면 공유 라이브러리에서 발견된 모든 심볼이 다른 라이브러리들이 전역으로 쓸 수 있도록 내보내져요. -lazy 옵션은 심볼의 실제 로드를 처음 실제 사용할 때까지 지연시켜요. 옵션은 약어로 쓸 수 있어요. -- 옵션은 옵션의 끝을 나타내며, -로 시작하는 파일 이름을 쓰면서 load 명령어에 prefix를 주고 싶을 때 사용해야 해요.
-global·-lazy 옵션을 지원하지 않는 플랫폼에서는 옵션이 존재는 하지만 효과가 없어요. -global·-lazy 옵션을 쓰면 나중에 애플리케이션이 크래시할 수 있는데(심볼 충돌 · 누락 심볼의 경우) 이는 load 중에 감지할 수 없어요. 그래서 뭘 하는지 알 때만 쓰세요. 로드된 라이브러리에 문제가 있어도 깔끔한 오류 메시지를 못 받아요.
이식성 문제(Portability Issues)
Windows — load가 "library not found" 오류로 실패하면, 종속 라이브러리도 못 찾은 것일 수 있어요. 종속 라이브러리를 보려면 DOS 콘솔에 dumpbin -imports <dllname>을 입력해 그 라이브러리가 무엇을 import해야 하는지 확인해요. 현재 디렉터리의 DLL을 로드할 때 Windows는 ./를 경로 지정자로 무시하고 검색 휴리스틱으로 DLL을 찾아요. 이를 피하려면 다음으로 DLL을 로드하세요.
load [file join [pwd] mylib.DLL]
버그(Bugs)
같은 파일이 다른 파일 이름으로 load되면 프로세스의 주소 공간에 여러 번 로드돼요. 이런 동작은 시스템마다 달라요(어떤 시스템은 중복 로드를 감지하고, 어떤 시스템은 못 할 수도 있어요).
예시
다음은 최소한의 확장 예시예요.
#include <tcl.h>
#include <stdio.h>
static int fooCmd(void *clientData,
Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
printf("called with %d arguments\n", objc);
return TCL_OK;
}
int Foo_Init(Tcl_Interp *interp) {
if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
return TCL_ERROR;
}
printf("creating foo command");
Tcl_CreateObjCommand(interp, "foo", fooCmd, NULL, NULL);
return TCL_OK;
}
적당한 이름(예: Windows의 foo.dll, Solaris·Linux의 libfoo.so)의 공유/동적 라이브러리로 빌드하면 다음처럼 Tcl에 로드할 수 있어요.
# Load the extension
switch $tcl_platform(platform) {
windows {
load [file join [pwd] foo.dll]
}
unix {
load [file join [pwd] libfoo[info sharedlibextension]]
}
}
# Now execute the command defined by the extension
foo
더 알아보기
info sharedlibextension,package,Tcl_StaticPackage,safe