바이너리 데이터를 값으로 반환하는 커맨드를 만드는 방법
바이너리 데이터를 값으로 반환하는 커맨드를 만 들 경 우 Tcl 커맨드의 반환값으로 객체 자체를 반환할 수도 있습니다. 특히 바이너리 데이터를 반환하는 경우 Tcl_SetResult 대신 아래와 같이 Tcl_SetObjResult API를 사용합니다.
Tcl_Obj* outobj = Tcl_NewStringObj(string, length);
Tcl_SetObjResult(interp, outobj);
return TCL_OK;
여기에는 여러 가지 변형이 있을 수 있습니다. 예를 들어, 어떤 바이너리 데이터 뒤에 또 다른 바이너리 데이터를 연결하고 싶은 경우,
Tcl_AppendToObj(Tcl_GetObjResult(interp), string, length);
return TCL_OK;
위와 같이 Tcl_GetObjResult와 Tcl_AppendToObj를 사용합니다. 연결할 문자열이 일반 C 언어 타입의 문자열인 경우, length에 -1을 지정하면 자동으로 strlen(string) 값이 사용됩니다. 이 API는 Tcl/Tk 인터프리터 소스에서 이런 상황에서 많이 사용되고 있습니다. 또한, 어떤 바이너리 데이터가 들어있는 뒤에 다른 바이너리 데이터를 내용으로 하는 Tcl 객체를 연결하고 싶을 때도 사용할 수 있습니다.
Tcl_AppendObjToObj(Tcl_GetObjResult(interp), outobj);
위와 같이 Tcl_GetObjResult로 가져온 것에 Tcl_AppendObjToObj로 outobj를 연결합니다. 이렇게 결과로 바이너리 데이터가 들어있을 수 있는 예제를 보면, Tcl_Interp* interp의 결과를 보관하는 멤버 interp->result를 직접 참조하는 것이 매뉴얼에서는 '구식 방식 처리'로 되어 있는 이유를 알 수 있을 것입니다. 유의미한 바이너리 데이터 중간에 NULL 문자(0x00)가 포함되어 있는 경우, interp->result의 정보만으로는 결과가 어디까지인지 전혀 알 수 없기 때문입니다.
다음 샘플은 readfile이라는 새로운 Tcl 커맨드를 추가하는 코드입니다. 'readfile 파일명'으로 지정하면 지정한 파일의 내용 전체를 읽어 값으로 반환합니다. 파일이 바이너리인 경우에도 올바르게 반환될 것입니다.
#include <stdio.h>
#include <sys/stat.h>
#include "tcl.h"
static char ereason[1024];
#define RERROR \
{ Tcl_SetResult(interp,ereason,TCL_STATIC); return TCL_ERROR; }
static int getsize(char* filename){
struct stat sbuf;
if(stat(filename, & sbuf) < 0) return -1;
return sbuf.st_size;
}
static int readfileHandleProc(ClientData clientData, Tcl_Interp* interp,
int argc, char* argv[]){
FILE* fp;
int sz, len;
char* p;
Tcl_Obj* obj;
if(argc == 1){
sprintf(ereason, "too few arguments, usage: %s filename", argv[0]);
RERROR;
}
if((sz = getsize(argv[1])) == -1){
sprintf(ereason, "cannot stat file %s", argv[1]); RERROR;
}
if((fp = fopen(argv[1], "rb")) == NULL){
sprintf(ereason, "cannot open %s", argv[1]); RERROR;
}
if((p = (char* )Tcl_Alloc(sz)) == NULL){
strcpy(ereason, "memory exhausted"); fclose(fp); RERROR;
}
if((len = fread(p, 1, sz, fp)) != sz){
sprintf(ereason, "problems occurred in reading from %s", argv[1]);
Tcl_Free(p); fclose(fp); RERROR;
}
fclose(fp);
#ifdef TCL80 /* Tcl 8.0.x */
obj = Tcl_NewStringObj(p, len);
#else /* Tcl 8.1.x */
obj = Tcl_NewByteArrayObj((unsigned char* )p, len);
#endif
Tcl_SetObjResult(interp, obj);
#endif Tcl_Free(p);
return TCL_OK;
}
DLLEXPORT int Objtest_Init(Tcl_Interp* interp){
Tcl_CreateCommand(interp, "readfile", readfileHandleProc, NULL, NULL);
return TCL_OK;
}
바이너리 데이터를 인수로 받을 수 있는 커맨드를 만드는 방법
직접 Tcl 커맨드를 만들려면 Tcl_CreateCommand라는 API를 사용해야 하는데, 이 API로 등록하는 함수는 바이너리 데이터를 인수로 받을 수 없습니다. 즉, 바이너리 데이터를 인수로 받는 Tcl 커맨드는 추가할 수 없습니다. 바이너리 데이터를 인수로 받는 Tcl 커맨드를 추가하려면, 그 상위 API인 Tcl_CreateObjCommand를 사용합니다. 다음 예제는 'strings 바이너리 데이터'라고 가정해서, 바이너리 데이터 내의 연속된 5개 이상의 인쇄 가능 문자를 한 줄씩 표준 출력으로 출력합니다.
#include <stdio.h>
#include <ctype.h>
#include "tcl.h"
#define MAXLEN 1024
static int stringsHandleProc(ClientData clientData, Tcl_Interp* interp,
int objc, Tcl_Obj* CONST objv[]){
char* p, * q, buf[MAXLEN];
int i, len;
if(objc != 2){
Tcl_WrongNumArgs(interp, 1, objv, "binary-string");
return TCL_ERROR;
}
#ifdef TCL80 /* Tcl 8.0.x */
p = Tcl_GetStringFromObj(objv[1], & len);
#else /* Tcl 8.1.x or after */
p = (char* )Tcl_GetByteArrayFromObj(objv[1], & len);
#endif
for(i=0; i<len; ){
if(isgraph(*p)){
int clen;
for(clen=0,q=buf; clen<MAXLEN&&isgraph;(*p); clen++) *q++ = *p++;
*q = '\0'; p++; i+=clen+1;
if(clen > 5) fprintf(stdout, "%s\n", buf);
}
else{
p++; i++;
}
}
Tcl_SetResult(interp, "done.", TCL_STATIC);
return TCL_OK;
}
DLLEXPORT int Strings_Init(Tcl_Interp* interp){
Tcl_CreateObjCommand(interp, "strings", stringsHandleProc, NULL, NULL);
return TCL_OK;
}
위와 같이, Tcl_CreateObjCommand의 인자는 Tcl_CreateCommand와 완전히 동일하지만, 함수 인수는 약간 다릅니다.
int stringsHandleProc(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])
위처럼 Tcl 객체를 직접 전달받기 때문에, 이 objv[]를 Tcl_GetStringFromObj 등의 API로 추출하게 됩니다. Tcl_GetStringFromObj는 바이너리 데이터도 추출할 수 있으므로, 바이너리 데이터를 처리할 수 있는 커맨드를 만들 수 있습니다. 또한, 인수를 숫자로 해석해야 할 경우 Tcl_GetDoubleFromObj 등을 직접 사용할 수 있으며, 이러한 API는 인수가 Tcl 언어의 리터럴로 올바른 숫자 표현인지 여부도 판단해 주므로, 이 점에서 Tcl_CreateCommand 보다 편리하다고 할 수 있습니다. 참고로, objv[0]은 문자열 표현으로 Tcl 커맨드의 이름을 가진 Tcl 객체에 대한 포인터입니다. Tcl_GetStringFromObj(objv[0], & length)를 사용하면 호출된 Tcl 커맨드의 이름을 확인할 수 있습니다.