
Cim(Common input method)은 입력 시스템의 기본 구성 요소로서, 안정성과 확장성을 갖추는 것이 매우 중요합니다. 저는 기존 Cim 인터페이스의 한계를 보완하고자 Cim 자체를 개선하는 작업을 진행했습니다. 이번 개선은 내부 캡슐화, ABI 명확성, 확장성 확보를 목표로 하고 있으며, 별도의 플러그인 인터페이스를 만들지 않고 CimIcApi
를 플러그인 진입점으로도 활용할 수 있도록 설계했습니다.
기존 인터페이스의 문제점
기존 Cim 인터페이스는 아래와 같이 정의되어 있습니다:
struct _CimIc {
void (*focus_in) (CimIc* ic);
void (*focus_out) (CimIc* ic);
void (*reset) (CimIc* ic);
bool (*filter_event) (CimIc* ic, const CimEvent* event);
void (*set_cursor_pos) (CimIc* ic, const CimRect* area);
const CimPreedit* (*get_preedit) (CimIc* ic);
const CimCandidate* (*get_candidate) (CimIc* ic);
void (*set_vcallbacks) (CimIc* ic, va_list ap);
void (*activate_candidate_item) (CimIc* ic, int row, int col);
void (*change_candidate_page) (CimIc* ic, int page_index);
/* reserved */
void (*reserved_1) ();
void (*reserved_2) ();
void (*reserved_3) ();
void (*reserved_4) ();
void (*reserved_5) ();
void (*reserved_6) ();
};
주요 문제점은 다음과 같습니다.
- 가변 인수 사용의 복잡성:
set_vcallbacks
에서 가변 인수를 처리하기 위해va_list
를 사용함에 따라, 플랫폼 간의 차이나 루비, 파이썬 등 다른 언어의 바인딩 작업에서 불필요한 복잡함이 있었습니다. - 내부 구현 세부사항 노출: 모든 함수가
CimIc*
를 인자로 받아 내부 데이터가 외부에 노출될 위험이 있었습니다. 이는 객체지향 설계의 캡슐화 원칙에 어긋납니다. - 확장성 제한: 예약 슬롯을 함수 포인터 형태로 선언함으로써, 이후 새로운 기능 추가에 유연하게 대응하기 어려운 구조였습니다.
개선 방향과 새 접근법
저는 Cim 개선 작업을 통해 다음과 같은 방향의 개선을 시도합니다.
내부 상태 캡슐화: 모든 함수의 첫 번째 인자를 CimIc*
에서 void*
로 변경하여, 내부 구현(컨텍스트)을 감추고 사용자 인터페이스를 단순하게 유지합니다.
고정 인자 사용: 가변 인수 대신,
void (*set_callbacks) (void* context, const CimCallbacks* callbacks);
와 같은 고정 인자 시그니처를 도입하여 ABI를 명확히 하고, 바인딩 작업을 용이하게 합니다.
확장성 확보: 예약 슬롯을 함수 포인터 대신 void* reserved[6]
배열로 변경함으로써, 향후 새로운 기능이나 데이터 확장이 필요할 때 보다 유연하게 대응할 수 있도록 합니다.
플러그인 진입점 통합: 별도의 플러그인 인터페이스를 만들지 않고, 개선된 CimIcApi
구조체 자체를 플러그인 진입점으로 활용할 수 있도록 하였습니다. 이를 통해 기존 입력기와의 연동뿐만 아니라 플러그인 측에서도 동일한 인터페이스를 사용해 Cim에 접근할 수 있습니다.
인터페이스 명칭 변경: Cim 자체의 구조적 개선 의도를 보다 명확히 전달하기 위해, 기존 CimIc
대신 CimIcApi
라는 이름을 사용합니다.
개선된 CimIcApi 인터페이스 코드 예시
다음은 개선 사항을 반영하여 작성한 인터페이스 코드 예시입니다. (향후 변경될 수 있습니다.)
// 개선된 API 인터페이스 선언
typedef struct _CimIcApi {
// 객체 생성 및 소멸 함수
void* (*create) (void);
void (*destroy) (void*);
// input context 관련 함수들
void (*focus_in) (void* context);
void (*focus_out) (void* context);
void (*reset) (void* context);
bool (*filter_event) (void* context, const CimEvent* event);
void (*set_cursor_pos) (void* context, const CimRect* area);
const CimPreedit* (*get_preedit) (void* context);
const CimCandidate* (*get_candidate) (void* context);
// 콜백 설정 함수: 고정된 인자 시그니처 사용
void (*set_callbacks) (void* context, const CimCallbacks* callbacks);
// 후보 아이템 활성화 및 페이지 변경 함수
void (*activate_candidate_item) (void* context, int row, int col);
void (*change_candidate_page) (void* context, int page_index);
// 향후 확장을 위한 예약 슬롯 (일반 포인터 배열)
void* reserved[6];
} CimIcApi;
플러그인 연동 예시
다음은 개선된 인터페이스를 플러그인에서 직접 활용하는 예제입니다. 여기서 별도의 플러그인 인터페이스 없이 CimIcApi
를 그대로 플러그인 진입점으로 사용합니다.
/* plugin.c */
#include <stdio.h>
#include "cim.h"
#include <stdlib.h>
#include <stdbool.h>
// Dasom 구조체 예시 (내부 구현 캡슐화를 위해)
typedef struct {
int i; // example
} Dasom;
void dasom_ic_free (Dasom* ic)
{
Dasom* dasom = (Dasom*) ic;
puts ("dasom_ic_free");
free (dasom);
}
Dasom* dasom_ic_new ()
{
puts ("dasom_ic_new");
Dasom* dasom = malloc (sizeof (Dasom));
return dasom;
}
void dasom_ic_focus_in (Dasom* dasom)
{
puts ("dasom_ic_focus_in");
}
void dasom_ic_focus_out (Dasom* dasom)
{
puts ("dasom_ic_focus_out");
}
bool dasom_ic_filter_event (Dasom* ic, const CimEvent* event)
{
puts ("dasom_ic_filter_event");
return true;
}
// 플러그인 인터페이스 연결: 모든 함수가 void* 인자를 받아 내부 구조체(Dasom)를 캡슐화합니다.
// CimIcApi 구조체 자체를 플러그인 진입점으로 활용합니다.
CimIcApi cim_ic_api = {
.create = (void* (*) ()) dasom_ic_new,
.destroy = (void (*) (void*)) dasom_ic_free,
.focus_in = (void (*) (void*)) dasom_ic_focus_in,
.focus_out = (void (*) (void*)) dasom_ic_focus_out,
.filter_event = (bool (*) (void*, CimEvent*)) dasom_ic_filter_event,
};
결론
이번 Cim 개선 작업은 기존 인터페이스의 한계를 보완하여 Cim의 내부 구조를 좀 더 명확하고 확장 가능하게 만드는 것을 목표로 했습니다.
- 내부 상태 캡슐화: 모든 함수 인자를
void*
로 변경하여 내부 구현을 감춥니다. - 고정된 인자 사용: 가변 인수 대신 콜백 구조체를 사용해 ABI 및 바인딩의 안정성을 확보합니다.
- 확장성 확보: 예약 슬롯을
void* reserved[6]
배열로 전환하여 향후 확장이 용이하게 했습니다. - 플러그인 진입점 통합: 별도의 플러그인 인터페이스 없이
CimIcApi
를 플러그인 진입점으로 활용하여, 입력기와 플러그인 간의 연동을 단순화했습니다. - 명칭 변경:
CimIc
에서CimIcApi
로 변경하여 Cim 자체 개선의 의도를 명확히 전달합니다.
이와 같이 개선된 인터페이스를 통해 Cim은 보다 실용적이고 유지보수가 쉬운 구조로 거듭날 수 있을 것입니다. Cim 개선 작업에 대한 여러분의 의견이나 추가 질문이 있으시면 언제든지 공유해 주시기 바랍니다.