Tcl_Eval 로 간단한 GUI 만들기
Tcl_Eval은 Tcl API 중 가장 대중적인 API로, 이것만 알면 다른 많은 API를 몰라도 많은 부분을 커버할 수 있습니다. 예를 들어, Tcl 변수 var에 숫자 10을 설정하고 싶을 때, 원래는 Tcl_SetVar와 같은 API를 사용해야 하지만, Tcl_Eval로 해결할 수 있습니다.
char command[50];
strcpy(command, "set var 10");
Tcl_Eval(interp, command);
반대로,
char command[50];
int v;
strcpy(command, "set var");
Tcl_Eval(interp, command);
v = (int) strtol(Tcl_GetStringResult(interp),NULL,0);
위를 사용하면 Tcl_GetVar를 사용하지 않고도 Tcl 변수의 값을 얻을 수 있습니다. 하지만 다음과 같은 단점도 있습니다.
- 속도가 느리다
- 문자열 이스케이프가 복잡하다
- 바이너리 데이터는 다룰 수 없다
특히 Tk의 GUI를 만든 목적으로 C에서 Tcl/Tk를 호출하고 싶을 때는 Tcl_Eval만 알면 충분하다고 생각하는 경우가 대부분입니다.
#include "tcl.h"
#include "tk.h"
int main(int argc, char* argv[])
{
Tcl_Interp* interp;
interp = Tcl_CreateInterp();
Tcl_FindExecutable(argv[0]);
if(Tcl_Init(interp) != TCL_OK){
fprintf(stderr, "FATAL: Tcl initialization error: %s\n",
Tcl_GetStringResult(interp)); return 1;
}
if(Tk_Init(interp) != TCL_OK){
fprintf(stderr, "FATAL: Tk initialization error: %s\n",
Tcl_GetStringResult(interp)); return 1;
}
{
char command[] =
"label .la -text 이름:\n\
entry .ea\n\
button .cmda -text OK -command ok_clicked\n\
pack .la .ea -anc w; pack .cmda -anc e\n\
proc ok_clicked {} {\n\
tk_messageBox -message \"안녕하세요. [.ea get] 님.\"\n\
exit\n\
}";
Tcl_DString ds;
char* command_utf = Tcl_ExternalToUtfDString(NULL, command, -1, &ds);
if(Tcl_Eval(interp, command_utf) != TCL_OK){
fprintf(stderr, "Error: %s\n",
Tcl_GetStringResult(interp)); return 1;
}
Tcl_DStringFree(&ds);
}
Tk_MainLoop();
return 0;
}
예제를 보면 C언어 코드 안에 Tcl 언어의 코드가 그대로 담겨 있기 때문에 무슨 목적의 프로그램인지 한눈에 알 수 있을 것입니다. Tcl_ExternalToUtfDString 은 Tcl 코드에서 '안녕하세요'와 같은 (euc-kr, cp949 코드 등으로 작성된) 한글 문자열을 Tcl/Tk의 내부 문자 코드인 UTF-8로 변환하는 API입니다.
우선은 이렇게 계속 써보는 것만으로도 XLib이나 Win32 API보다 훨씬 쉽게 'C언어로 GUI 애플리케이션을 작성할 수 있다'는 점에서 재미있고 중독성이 있을지도 모르겠습니다.
C 언와 Tcl 언어를 넘나들기
위의 예제는 C에서 Tcl/Tk의 GUI를 띄운 후 이름을 입력받아 인사하고 끝나는 프로그램인데, 조금만 생각해 보면 입력받은 이름을 C 언어에서 사용할 수 없습니다. 이를 해결하기 위해 콜백 함수 개념으로 대체합니다. 그렇다고 해서 Win32나 OSF/Motif와 같은 의미의 콜백 함수가 아니라, Tk의 버튼을 눌렀을 때 실행하고 싶은 커맨드를 C언어로 작성하여 Tcl 인터프리터에 등록하면 됩니다.
#include "tcl.h"
#include "tk.h"
static int okClickedCmd(ClientData data, Tcl_Interp* interp, int argc, char* argv[])
{
char* username_utf, *username;
Tcl_DString ds;
/* 두 가지 방법 모두 가능 */
#if 1
char command[] = ".ea get";
if(Tcl_Eval(interp, command) == TCL_ERROR) return TCL_ERROR;
username_utf = Tcl_GetStringResult(interp);
#else
username_utf = Tcl_Eval2(interp, "UserName", NULL, TCL_GLOBAL_ONLY);
#endif
username = Tcl_UtfToExternalDString(NULL, username_utf, -1, &ds);
fprintf(stdout, "안녕하세요. %s 님.\n", username);
Tcl_DStringFree(&ds);
return TCL_OK;
}
int main(int argc, char* argv[])
{
Tcl_Interp* interp;
interp = Tcl_CreateInterp();
Tcl_FindExecutable(argv[0]);
if(Tcl_Init(interp) != TCL_OK){
fprintf(stderr, "FATAL: Tcl initialization error: %s\n",
Tcl_GetStringResult(interp)); return 1;
}
if(Tk_Init(interp) != TCL_OK){
fprintf(stderr, "FATAL: Tk initialization error: %s\n",
Tcl_GetStringResult(interp)); return 1;
}
Tcl_CreateCommand(interp, "ok_clicked", okClickedCmd, NULL, NULL);
{
char command[] =
"label .la -text 이름:\n\
entry .ea -textvariable UserName\n\
button .cmda -text OK -command ok_clicked\n\
pack .la .ea -anc w; pack .cmda -anc e\n\
";
Tcl_DString ds;
char* command_utf = Tcl_ExternalToUtfDString(NULL, command, -1, &ds);
if(Tcl_Eval(interp, command_utf) != TCL_OK){
fprintf(stderr, "Error: %s\n",
Tcl_GetStringResult(interp)); return 1;
}
Tcl_DStringFree(&ds);
}
Tk_MainLoop();
return 0;
}
C 언어 함수 'okClickedCmd'는 Tcl 커맨드 'ok_clicked'가 호출되었을 때 실행되는 C 함수입니다. 참고로 okClickedCmd 안에 이번에는 Tcl_UtfToExternalDString이라는 이전의 예제와 비슷한 이름의 API가 나오는데, Tcl의 내부 코드인 UTF-8을 cp949 등 C에서 읽을 수 있는 한글 문자 코드로 변환하는 API입니다.