1. 외부 모듈 핵심부의 EXE 분리

오래 전부터 조금씩 풀었던 썰이긴 한데, 마침 최근에 회사에서 유사 개발 업무를 한 적도 있고 해서 다시 얘기를 꺼내 보겠다.
Windows는 타 OS들과는 달리 IME가 EXE가 아닌 DLL 형태이다. 한 프로세스의 주소 공간에 완전히 속해 있는 덕분에 성능이 좋다는 장점이 있겠지만, 한영 상태가 스레드들마다 제각각 따로 놀고 거기에다 memory-mapped 방식으로 로딩된다는 특성까지 겹쳐서 IME의 on-the-fly 업데이트가 몹시 난감하다.

EXE라면 업데이터 하나 띄워서 자신을 종료한 뒤 업데이트 된 놈으로 재시작만 하면 간단하게 끝났을 일인데 Windows용 IME는 업데이트 하려면 자신을 사용하는 프로그램들을 몽땅 종료해야 하고, 그게 여의치 않으면 그냥 운영체제를 재시작/로그오프 해야 한다. 거기에다 32/64비트까지 모두 신경 써야 한다.

그래서 <날개셋> 한글 입력기 외부 모듈도 인제 와서 처음부터 다시 만드는 건 무리이겠지만, 앞으로 덩치 큰 IME를 만들 일이 있으면 DLL은 거의 업데이트 할 일 없는 껍데기만 남겨 놓고 실질적인 문자 조합은 EXE 기반의 '서버'에 담당시키면 어떨까 생각을 해 왔다. 업데이트도 IME가 통신하는 EXE만 하고 말이다.

이렇게 하면 모든 IME들의 설정과 상태 동기화는 자동으로 이뤄진다. 서버와는 함수 호출이 아니라 메시지와 memory-mapped file 같은 간접적인 방법으로 통신을 하니 서버는 굳이 바이너리 구분을 할 필요 없다. 64비트 OS에서는 64비트 서버 하나만 띄워 놓으면 32비트와 64비트 IME가 모두 통신이 가능하니 더욱 좋다.

실제로 실험용 IME를 만들어 본 결과는 흥미로웠다. 서로 다른 프로세스끼리 메시지를 주고받을 때는 단일 스레드끼리 메시지를 주고받을 때에 비해 고려해야 할 점이 더 많았다. 받는 쪽에서 자체적으로 대화상자 같은 걸 출력하고 그 상태로도 자체 메시지를 처리하지 못하는 block 상태가 되지 않으려면 대화상자는 modal이 아니라 반드시 modeless 형태로 만들어야 했다.

SendMessage와 PostMessage를 조심해서 가려 써야 하며, 리턴값을 꼭 받기 위해 Send를 하면서도 신속한 반응성을 보장하기 위해서는 지금까지 머리로만 알고 있던 ReplyMessage 같은 함수를 난생 처음으로 써 보기도 했다. 특히 호스트가 클라이언트로부터 Send된 메시지를 받은 뒤에 대화상자 같은 modal UI를 띄운다면 말이다.

여기까지 생각을 하긴 했으나.. IPC 기법들은 근본적으로 IME들이 쓰라고 만들어진 메커니즘이 아니다 보니 한계도 많다.
가장 먼저 권한 문제가 걸리니, IME 서버는 번거롭게 관리자 권한으로 실행하거나 아니면 애초에 운영체제의 서비스 같은 급으로 만들어야 한다. 메트로와 데스크톱 앱 사이의 소통도 문제이고..
IME가 글쇠 입력을 받은 것을 서버로 요청을 보내는 건 그럭저럭 할 만하나, 반대로 서버가 IME로 문자 입력 요청을 하는 것은.. IME가 제각각 스레드 동기화 오브젝트나 윈도우를 만들어야 가능할 것이다.

서버는 자신과 접속하거나 종료하는 클라이언트들을 파악하고 있어야 하는데 자고로 프로세스라는 건 강제 내지 비정상 종료될 때도 있다. 그렇기 때문에 모든 프로그램들의 근황을 언제나 정확하게 파악하고 있는 것도 훅킹이라도 동원하지 않으면 의외로 쉽지 않더라.

이것저것 가성비를 생각해 보니 서로 장단점이 있고 근본적으로 한 방식이 다른 방식을 완전히 대체 가능해 보이지는 않았다. 안 그래도 날개셋의 경우 EXE 기반의 입력기 개발 실험은 입력 패드를 만들면서 이미 그럭저럭 하기도 했다. Windows에서 어떤 DLL이 타 프로세스에 합법적으로 침투할 수 있는 양대 통로는 미우나 고우나 훅킹 아니면 IME이다.

다만, 지금 MS 일본어 IME가 이미 그런 것처럼 제어판 대화상자만은 EXE로 분리하는 게 나은 점도 있다.
실행되는 응용 프로그램에 따라서는 공용 컨트롤.. 특히 6.0 이상에서만 지원되는 syslink나 split button, 에디트 컨트롤의 풍선 도움말(cue banner) 같은 게 초기화되지 않은 경우가 종종 있어서 내 날개셋 제어판도 그거 영향을 받아 제약을 받기도 하기 때문이다.

뭐 그건 그렇고..
기존 데스크톱 앱인 '제어판' 말고 메트로 앱인 '설정'에서 돌아가는 환경설정은 어떻게 만드는지 모르겠다. MS 한글 IME에는 그런 게 있던데..;;

2. 설치 시스템 개편

예전에도 여러 번 언급한 바와 같이, <날개셋> 한글 입력기는 Visual Studio가 기본 제공하는 Windows Installer 기반 msi 패키지 형태로 배포되고 있다. 이 솔루션은 MS 본가에서 만든 만큼, 프로그램을 설치하거나 제거하는 본연의 성능은 일정 수준 이상의 퀄리티가 보장된다. 프로그램 디렉터리 어딘가에 uninstall stub 프로그램 같은 게 덕지덕지 붙어 있을 필요도 없고 아주 seamless + 깔끔하다. 하지만 개발툴이 제공하는 GUI 템플릿은 customize가 매우 제한적이고 불만족스러운 점이 많기 때문에 다른 솔루션을 써 볼까 생각도 자주 해 왔다.

이상적인 설치/배포 솔루션은 다음과 같은 조건을 만족해야 한다. 다른 프로그램이라면 몰라도 <날개셋> 한글 입력기에 대해서는 내가 보다시피 욕심이 좀 많다.

  1. CPU 통합: 한 exe 파일 단독으로 32비트와 64비트 OS에서 잘 동작하고, 32비트에서는 당연히 64비트 바이너리를 설치하지 않아야 한다. EXE처럼 32/64비트 중 사용 가능한 상위 바이너리 하나만 설치하면 되는 파일은 선별이 옳게 돼야 한다. 필요한 디스크 공간 계산도 이 모든 변수를 감안해서 돼야 한다.
  2. 언어 통합: 한 exe 단독으로 운영체제의 기본 언어가 한국어이면 한국어, 그렇지 않으면 자동으로 영어로 설치 프로그램의 UI가 출력되어야 한다.
  3. 유니코드 통합: 평상시에 유니코드 API를 사용하는 건 너무 당연한 얘기이고, 그럼에도 불구하고 구닥다리 Windows 9x에서도 유니코드만 포기하고 기본적인 실행이 돼야 한다. 이것도 물론 단일 파일로 말이다. <날개셋> 한글 입력기 본제품이 Windows 95/NT4부터 꼬박꼬박 다 지원하는 프로그램이기 때문이다.
  4. known 폴더: 또한 아무 액세서리 없는 깡통 Windows 95 RTM에서 실행되더라도 IE 4~5 이상에서 첫 도입된 ProgramData (Application Data)같은 known 디렉터리를 인식해서 파일을 제 위치에 설치해야 한다.
  5. 원활한 제거: Windows용 IME는 그 특성상 on-the-fly로 업데이트나 제거가 꽤 난감한 물건인데, 당일 제거를 못 했으면 재부팅 요청 같은 후처리를 적절히 수행해야 한다.
  6. 그 밖에: 관리자 권한 드립 치는 UAC 화면은 setup을 실행하자마자 뜨는 게 아니라, 실제로 설치가 시작되어 관리자 권한이 정말로 필요해졌을 때 직전에 뜨는 게 바람직함.

진짜 유명한 세계구급 소프트웨어인 경우, 설치 프로그램이 언어를 선택받는 대화상자부터 띄우는 경우가 있다. 하지만 날개셋의 경우 그렇게 유명한 물건은 아니니 그냥 운영체제의 기본 GUI 언어가 한국어가 아닐 때에만 영어로 동작하는 걸로도 충분할 듯하다. 날개셋은 GetSystemDefaultLangID() 함수를 써서 판별하는데, 이게 GetUserDefaultLangID와 차이가 무엇이 있는지는 개인적으로 궁금하다.

msi는 (1)과 (2)는 전혀 만족하지 않는 것으로 보여서 문제다.
그러나 (3)과 (4)는 Windows installer runtime (non-Unicode 9x용 에디션)자체만 미리 설치하면 그럭저럭 어렵지 않게 충족된다. 2.0 런타임은 의외로 깡통 Windows 95에서도 깔끔하게 설치 가능하다. 이것 때문에 <날개셋> 한글 입력기가 MSI 외에 다른 배포 솔루션으로 갈아타기가 어렵다.

"HKCU 또는 HKLM"\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders라는 레지스트리를 참조하면 known 폴더 위치를 얻을 수 있다. MSI가 이런 것까지도 생성해 준다. 이렇게 레지스트리를 수동으로 뒤지는 방법은 오늘날에는 마소에서 권장하지 않는 방법이지만, The old new thing 블로그의 설명에 따르면 아직도 여기를 참조하는 고집쟁이 옛날 어플 때문에 어쩔 수 없이 호환성 차원에서 레지스트리를 계속 지원해 주는 거라고 한다. 사실, IE 4~5가 없고 SHGetSpecialFolderPath 함수가 존재하지 않는 골동품 Windows 95 환경에서는 여기를 뒤지는 것밖에 선택의 여지가 없기도 하다.

다음으로 (5)의 경우 msi는 딱 기본만 수행한다. "다음 프로그램들이 요 DLL을 사용하고 있습니다" 대화상자도 찍어 주고, 뭔가 못 건드린 파일이 있으면 "재부팅 하시겠습니까?"라는 여운을 남기기도 하는데, 가끔은 안 그럴 때도 있는 듯하다.

msi 말고 3rd party installer 중에서는 오픈소스이기도 한 NSIS가 세계적으로 제일 유명하다. WinAmp는 역사 속으로 사라졌고 개발사인 Nullsoft도 없어졌지만, 그래도 NSIS만은 유용성 덕분에 오픈소스 진영에서 살아남아 있다. Nullsoft의 개발자들이 왕년에 불멸의 작품 하나로 이름을 남겼다.

얘는 어떤가 하면..
(1)과 (2)는 기술적으로 일단 가능하다. msi보다 분명 우월한 점이다. 그러나 그냥 '가능하다'에서 끝일 뿐, 막~ 적극적으로 지원되고 깔끔하고 편한 형태로 가능하지는 않아 보인다.

단적인 예로, 생성되는 installer에 붙는 런타임 바이너리가 기본적으로 32비트 기반이다 보니, 거기 스크립트 언어에서 기본 제공하는 등록 명령만 이용해서는 64비트 DLL에 대해서 DllRegisterServer(시스템 등록) 호출을 할 수 없다. 뭐, 운영체제가 제공하는 regsvr32 /s를 이용하면 되긴 하지만, 사용자가 직접 저렇게 외부 유틸리티나 플러그 인을 끌어들일 필요 없이 NSIS가 내장 명령어 차원에서 저걸 지원하면 더 좋을 것이다.
홈그라운드 운영체제의 지원빨이 있는 msi에서는 반대로 DLL의 등록쯤은 전혀 걱정할 것 없는 사항이다. 64비트용 msi라면 64비트 DLL이건 32비트 DLL이건 불문하고 등록이 깔끔하게 잘 처리되기 때문이다.

(3)은 NSIS가 한동안 정식으로 지원하지 않아서 Unicode NSIS라는 별도의 프로젝트 브랜치가 나돌 정도이다가 비교적 최근에 NSIS가 3.x 버전에 진입하고 나서야 유니코드를 정식으로 수용하게 됐다.
그러나 NSIS는 기술 수준이 그냥 이미 있는 Windows API를 감싸는 정도를 벗어나지 않기 때문에 유니코드와 Windows 9x를 동시에 지원한다거나, 구버전 OS에서 신버전의 known 디렉터리를 만들어 주는 정도의 과잉 친절을 베풀지는 않는다.
(5)의 경우는 NSIS가 어디까지 자비롭게 대처하는지 아직 제대로 확인을 못 해 봤다.

요약하면, 완전한 스크립트 기반인 NSIS가 당장 자유도가 뛰어나 보이기는 하지만, 그래도 레거시 운영체제 지원이나 시스템 차원에서의 융통성은 그래도 msi가 나은 게 있어서 한 솔루션이 다른 솔루션을 완벽하게 대체하지는 못하는 실정이다.
NSIS의 스크립트는 무슨 파이썬이나 Lua 급으로 복잡한 연산식이나 복합 자료구조를 지원하는 본격적인 고급 언어가 아니다. 스크립트의 문법은 반쯤 어셈블리어에다가 C언어의 전처리기를 얹은 것 같은 구조이며 생각보다 제약이 많다.

어셈블리어 같은 문법인데 CPU 인스트럭션이 들어가는 게 아니라 Windows API의 함수와 각종 속성 명칭이나 상수들이 들어간다는 점만 다르다. if-then-else, switch 같은 조건 판단과 분기조차도 언어의 키워드가 아니라 그냥 분기문을 표현하는 매크로 형태로 구현되었을 정도이다.
그나저나, 파일 경로를 많이 다루고 역슬래시를 필연적으로 많이 쓴다는 특성상 \ 자체는 탈출문자로 쓰지 않고 $를 붙여서 $\n으로 개행문자를 표현하는 건 인상적이었다.

설치 스크립트도 당장 필요한 기능만 주먹구구식으로 구현하는 게 아니라 치밀하게 잘 만들려면 끝이 없겠다.

  • 한 스크립트로 몇몇 스위치만 달리하여 동일 제품의 여러 파생형이나 변형 에디션(가령, 셰어웨어 데모/정식 같은)을 조건부 컴파일로 간단히 감당 가능
  • 한 제품에서는 아까 말한 언어와 아키텍처를 단일 출력 바이너리만으로 모두 커버 가능
  • 모든 문자열 값들은 언어 중립적인 값과 언어 종속적인 값으로 나눠서 관리 가능하고, 제품 이름 같은 건 한 곳에서 값을 바꾸면 등장하는 모든 곳에서 값이 알아서 바뀌어야 함
  • 컴퓨터의 상태 파악을 알아서 해야 함. 처음 실행됐을 때 지금이 첫 실행인지, 동일 버전, 구 버전, 또는 동일 버전의 바리에이션이 이미 설치돼 있는지, 이전에 설치를 하다가 만 상태인지, 심지어 자신이 중복 실행됐는지 같은 걸 사용자가 수동으로 파일이나 레지스트리 삽질 안 해도 알아서 감지해야 함
  • 설치할 파일과 삭제할 파일을 NSIS는 수동으로 일일이 써야 하는 것 같던데, 마치 C++ 개체 선언하듯이(생성자, 소멸자) 설치하는 파일, 추가하는 레지스트리 같은 걸 한 곳에다만 명시하면 역순의 제거 작업 역시 자동으로 파악돼야 하며, 작업을 실제로 수행하기 전에 예상 디스크 공간 계산 같은 것도 알아서 돼야 함.
  • 서로 다른 소프트웨어 제품이 동일한 파일을 설치하고 사용하는 경우, 그런 공용 파일은 reference counting을 해서 그 제품들이 모두 제거되었을 때에만 최종적으로 삭제되게 해야 함.
  • 그리고 uninstall 시엔 사용자가 생성한 데이터처럼 이 프로그램이 초기에 설치하지 않은 파일이나 레지스트리도 필요하다면 싹 제거하는 메커니즘이 제공돼야 함. (조건 범위 지정)
  • 최종 생성된 msi 내지 exe 파일에 대한 디지털 서명 후처리도 언어 내지 툴 차원에서 명시해서 자동 처리하기.

오픈소스 프로젝트에 기여한 것도 없는 주제에 불평만 길게 늘어놓은 것 같다만.. 이게 NSIS를 좀 써 보고 개인적으로 느꼈던 아쉬운 점이다. 오죽했으면 반디소프트에서 개발하고 있는 유명 파일 압축 유틸리티인 반디집도 6.0부터는 NSIS 대신 자체 개발한 인스톨러로 갈아탔다. 다만, NSIS는 저 정도 꽤 적지 않은 기능을 제공하고도 exe에 붙는 자기 런타임 stub 크기가 겨우 몇만 바이트에 불과할 정도로 작은 건 굉장히 인상적이었다.

그냥 간단한 파일 몇 개만 복사하고 끝나는 게 아니라 컴퓨터를 좀 깊게 제어하는 설치/제거 패키지를 만든다면 이걸 만드는 툴도 GUI 위주의 가벼운 툴이 아니라 그냥 핵심 기능만 SDK 형태로 만들고, 자주 쓰는 프로그램 패턴은 Visual C++의 프로젝트 마법사 형태로 구축하는 것도 나쁘지 않을 것 같다. 배포 패키지 자체를 그냥 C/C++로 만들라고 말이다. 그러면서 파일 압축 풀어서 복사하거나 지우는 등의 정말 핵심적인 공통 기능만 라이브러리를 쓰라고..

근데 생각해 보니 애시당초 그러라고 만들어진 라이브러리가 Windows Installer이긴 하다. 쟤도 사실은 단순 GUI 껍데기가 아니라 라이브러리가 본질이니까. 하지만 저 라이브러리도 구조가 워낙 복잡하고 설치, 제거, 롤백이 어떻고 알아야 할 사전 배경지식이 많다 보니 그 저수준 함수를 직통으로 쓰면서 배포 패키지를 만들 일이라곤 없을 듯하다.

Posted by 사무엘

2017/05/01 08:30 2017/05/01 08:30
, ,
Response
No Trackback , 4 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1355

Trackback URL : http://moogi.new21.org/tc/trackback/1355

Comments List

  1. kippler 2017/05/03 13:30 # M/D Reply Permalink

    오늘도 재미있는 글 잘 봤습니다. ^^
    몇가지 사족을 달자면,

    GetUserDefaultLangID() 사용자 언어 설정 값을 가져오고, GetSystemDefaultLangID() 는 시스템 언어 설정을 가져옵니다만, 실제로 NSIS 는 GetUserDefaultUILanguage()를 사용하고 있습니다.
    생각난 김에 테스트 해 보았는데, 사용자 입장에서 각각의 값을 바꾸고 방법이 조금씩 다 다르네요.

    인스톨러 만들 때 Win9x 지원은 쉽지 않은 문제로 보이네요. 일단 MBCS로 프로그램을 짜면, 언어 선택 화면에 각각의 언어로 언어 선택을 보여주기도 힘들고, 한국어 OS 사용자가 일본어를 선택하면 일본어가 깨지는 등의 문제가 발생하기 때문에 이 부분은 해결이 쉽지 않아 보입니다.

    그리고 언급하신 것처럼 NSIS 플러그인 사용할 때 32/64 비트 플러그인 DLL 호출이 자유롭지 못하기 때문에, 작은 외부 exe를 만들어서 플러그인에서 exe 를 호출하는 방식을 종종 사용했는데, 문제가 요즘 AV 프로그램의 휴리스틱엔 진은 덩치 작은 EXE를 너무 심하게 의심하더군요. 간단한 유틸 EXE를 만들어서 설치할 때 호출하는 방식을 선호했는데 나중에 이 문제가 너무 심해서 포기하고 말았습니다.

    1. 사무엘 2017/05/03 14:11 # M/D Permalink

      소환에 응해 주셔서 대단히 고맙습니다. ^_^

      1. 저도 그럼 System 대신에 User을 쓰는 게 낫겠네요. 어쩐지 외국인 중에서 편집기의 대화상자 UI가 한국어로 나오는 걸 문의하는 경우가 종종 있었는데, User는 영어이지만 System이 한국어여서 그런 것 같습니다.

      2. 당연히 Win9x 지원한다고 해서 프로그램 기반을 MBCS로 만들지는 않습니다. NT 계열에서는 원래 지원되는 언어 선택 ui가 모두 나오고, 9x에서는 영어 아니면 해당 운영체제 언어(영어가 아닌 경우) 둘 중 하나만 고를 수 있겠지요.

      3. 헐.. AV 휴리스틱 문제도 있는지는 몰랐네요. 디지털 서명 해도 막무가내로 의심하는가 봅니다.
      하긴, installer가 설치/제거 중에 잠깐 압축 풀어서 실행만 하고, 실제로 사용자의 컴에 설치는 안 되는 용도의 임시 EXE/DLL 및 데이터 같은 개념도 installer 엔진이 정식 지원해야 할 것 같습니다. feature list에 하나 추가입니다. ^^

  2. kippler 2017/05/04 12:07 # M/D Reply Permalink

    헐... 그냥 인스톨러 이야기 인줄 알았는데, 댓글 보니 인스톨러 만드시나 보네요. 응원합니다. ^^

    1. 네, 이 부분은 GetUserDefaultUILanguage() 가 제일 정확한듯 합니다. 저도 어제 궁금해서 여러가지 테스트를 해 봤는데, 영문 OS에 캐나다 언어팩을 설정하면 아래처럼 리턴값이 나옵니다. 한국, 일본, 중국 정도는 1:1 매치가 되어서 딱히 문제가 없는데, 스페인어처럼 다양한 국가에서 쓰이는 언어인 경우 GetUserDefaultUILanguage()를 써야 그나마 복잡도가 덜해 지더군요.
    GetSystemDefaultLangID () 1033 0x409 English ENGLISH_US
    GetUserDefaultLangID() 4105 0x1009 English ENGLISH_CAN
    GetUserDefaultUILanguage() 2057 0x809 English ENGLISH_UK

    2. 생각해보니 W계열 함수가 98에서도 다 작동했던 것 같기도 하고… 오랜만이라 기억이 가물 가물 하네요. (생각해 보니 작동을 하기는 하는데 내부에서 MBCS 로 다시 바꿔서 작동하기 때문에 일부 함수는 버그가 있었던 것 같기도 하고…) ㅎㅎ 어쨌거나 이거 다 고려하면 정말 쉽지 않은 작업이네요.

    1. 사무엘 2017/05/04 21:40 # M/D Permalink

      1. 아, "인스톨러를 만약 직접 만든다면 아무리 그래도 그렇지 MBCS를 쓰지는 않을 거다"라는 뜻이었습니다. 실제로 직접 만든다는 얘기는 아니고요. 그러기에는 제가 시간과 여유가 부족합니다. ^_^

      2. 네, 저도 관련 함수들을 검색하면서 그걸 알게 돼서 호출하는 함수를 교체했습니다.

      3. 9x 계열에서 W 계열 함수를 지원하는 건 MessageBoxW, TextOutW, GetTextExtentPoint32W처럼 최소한의 에러 메시지 출력 내지 글자를 찍고 픽셀 크기 측정하는 기본 함수 정도가 전부입니다. 아, 95 말고 98부터는 imm32 관련 함수들도 예외적으로 W를 지원하기 시작했고요. 물론 MessageBoxW는 내부적으로 문자열 변환 후 A를 호출하는 형태입니다.
      그러니 9x 시절에는 일반적인 함수들은 A를 써야 하는데 COM 내지 IME 관련 함수들과는 유니코드 문자열을 주고받으니 그게 또 프로그래밍을 골치 아프게 하는 요인이었습니다.

Leave a comment
« Previous : 1 : ... 205 : 206 : 207 : 208 : 209 : 210 : 211 : 212 : 213 : ... 1459 : Next »

블로그 이미지

철도를 명절 때에나 떠오르는 4대 교통수단 중 하나로만 아는 것은, 예수님을 사대성인· 성인군자 중 하나로만 아는 것과 같다.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2018/12   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          

Site Stats

Total hits:
1074594
Today:
202
Yesterday:
713