1. HTTP 통신 고수준 API
오늘날 운영체제의 GUI API가 qt 같은 별도의 프레임워크가 나온 걸 제외하면 통일된 게 없고(통일이 될 수가 없음..) 그래픽 API가 통합된 게 없듯이..
네트워크, 특히 HTTP/HTTPS 기반 통신 API 역시 내가 아는 한 뭔가 ANSI/ISO 차원의 표준이 나온 게 없이 운영체제마다 다 파편화돼 있는 것 같다. 물론 제일 저수준의 소켓 API는 그럭저럭 표준화가 돼 있지만 오늘날 그것만 써서 밑바닥부터 프로그램을 짜는 건 클라이언트건 서버건 무리이니 말이다. 그러니 Windows, 안드로이드, iOS/macOS마다 또 API를 새로 익혀야 하는 게 다소 번거롭다.
요즘 인터넷에서 다른 프로토콜들은 거의 듣보잡이 돼 가고 HTTP/HTTPS만 남은 것 같다.
통합 라이브러리는 스위치만 달리해서 동일한 정보를 (1) GET와 POST 두 방식으로 간편하게 보낼 수 있으며, 이때 argument를 적절히 배치해 줘야 한다. 전자는 주소에다가 넣고 후자는 헤더에다가 넣어야 할 것이다.
URL 인코딩 처리를 적절히 해 줘야 하며, 바이너리 데이터 덤프를 base64로 인코딩, 또는 디코딩 하는 라이브러리도 덤으로 제공하면 좋을 것이다. post로 보낼 때는 Content-Type, Content-Length 같은 상투적인 헤더를 당연히 자동으로 넣어 줘야 한다.
Windows에서는 메시지를 보내는 방식이 post의 반대가 send인데, HTTP에서는 요청을 보내는 방식이 post의 반대가 get이다. get은 어떤 정보를 얻어 오기만 하는 read-only operation용으로 쓰고, post는 서버다 데이터를 등록하고 변경하고 삭제하는 등 side effect가 남는 요청을 할 때 쓴다. 일단은 그러하지만 홍보가 부족해서 현실에서는 그 용도가 엄밀하게 지켜지지는 않고 있다.
POST 방식 요청의 연장선이겠다만 (2) 다운로드뿐만 아니라 업로드도 당연히 지원해야 할 것이고, 받는 데이터 내지 올리는 데이터는 (3) 메모리, 파일명, 임의의 스트림 형태로 자유롭게 공급이 가능해야 한다. 전부 다 필요하기 때문이다.
그리고 아주 짤막한 파일을 주고받을 때나 UI 없이 그냥 명령줄 프로그램을 만들 때를 대비해서 그냥 (4) 동기형(응답이 올 때까지 block) + 짧은 대기 시간 형태도 지원하고, 별도의 스레드에서 돌아가는 비동기 방식도 자체 지원해야 한다.
비동기 방식은 결과가 왔을 때 콜백 함수 호출 하나로 끝나는 간편한 것을 생각할 수 있고, 한편으로 수십~수백 MB짜리 파일을 주고 받으면서 전송 상태를 늘 확인할 수 있는 복잡한 형태도 생각할 수 있는데, 둘 다 가능해야 한다.
단순 업데이트 체크 같은 건 굳이 지금 당장 안 돼도 상관 없는 것이니 프로그램의 반응성에 영향을 줘서는 안 될 것이다. 스마트폰 앱 같은 게 비행기 모드이거나 3G 데이터를 사용하고 있을 때는 괜찮은데 외부의 희미한 와이파이에 접속해 있을 때는 굼뜨는 경우가 있다. 이런 일이 없어야 할 것이다.
Java는 네트워크 쪽 클래스를 사용할 때는 try catch로 예외 처리가 무조건 돼 있어야 하고 안 그러면 컴파일조차 되지 않는데, 안드로이드에서는 그것도 모자라서 네트워크 통신은 무조건 별도의 스레드에서만 동작하게.. main GUI 스레드에서는 쓸 수도 없게 만들어 놓았다. 네트워크 에러 때문에 프로그램 전체의 동작· 반응이 멎는 걸 원천봉쇄하기 위한 조치인데, 이것 때문에 아주 간단한 소규모 통신에 대해서까지 일일이 스레드 만들고 함수 분리하는 건 번거롭기도 한 게 사실이다.
통신 API로 할 수 있는 일을 생각해 보니 이 정도인 것 같다. 이 모든 상황에 대한 대처가 가능하고 크로스 플랫폼까지 지원한다면 훌륭한 통신 API가 될 수 있을 것이다.
2. Windows CE 프로그래밍
회사에서는 이것저것 찝적대느라 안드로이드, macOS 코코아에 이어 근래엔 골동품인 Windows CE까지 만질 일이 있었다.
도스 기반의 Windows (1985), NT 커널(1993)에 비해 CE는 임베디드 환경을 타겟으로 나름 제일 나중에(1996) 등장했다. PC용 OS들과는 달리 CE는 리얼타임 OS이다.
그리고 CE는 훗날 Embedded Compact라고 이름이 미묘하게 바뀌었다가 2010년대부터 Windows Mobile, Windows Phone 등 스마트폰 OS로 변모하는가 싶더니.. 그냥 Windows 10 자체가 ARM용으로도 나오면서 모바일 분야를 흡수해 버렸다. 또한, 스마트폰 OS는 마소가 판단하기에도 Windows가 안드로이드와 iOS의 적수는 도저히 안 되겠다고 여겨져서 그냥 접어 버리고.. 그 대신 Visual Studio가 무려 안드로이드 개발도 지원하는 식으로, 완전히 다른 형태로 변화하게 되었다.
아무튼, 이 와중에 지금으로서는 좀 구닥다리가 된 Windows CE를 만져 봤다.
GUI에서 본인에게 아주 익숙한 Windows API로 프로그래밍 가능한 건 좋은 점이었다. 단, 같은 API를 사용하더라도 헤더 파일 구조가 PC와 동일하지는 않았으며, 생성되는 바이너리도 비록 PE 방식의 실행 파일이긴 하지만, PC Windows 버전처럼 kernel32, gdi32, user32 이런 DLL들 import가 있지는 않는 게 흥미로웠다.
그 대신 coredll.dll에 API들이 한데 몰빵돼 있는 듯하다. 그리고 메모리 절약을 위해 함수들은 이름이 아니라 ordinal 번호만으로 import하게 돼 있다.
static 라이브러리 같은 걸 만들어도 PC용과 CE 기기용을 동일 소스 기반으로 유지하기는 좀 어려워 보였다. 글쎄, #include 부분에 조건부 컴파일 로직을 정교하게 잘 짜 놓으면 불가능하지 않을지도 모르겠지만.. 거기까지는 잘 몰라서 말이다.
그러고 보니 옛날엔 Visual C++도 Embedded VC++라고 CE 개발 전용 에디션이 있긴 했다가 그건 2000년대 이후로 본가에 흡수됐다.
Visual Basic 6이라든가 Visual C++ 6은 후대 버전과 단절이 커서 꽤 오랫동안 쓰인 개발툴이라는 공통점이 있다. 그런데 Visual C++ 2008도 비슷한 면모가 있다. 바로 Windows Phone이 등장하기 전의 Windows CE 시절, 더 정확히는 Embedded Compact 6.5와 그 이전 버전 레거시 플랫폼의 개발을 지원하는 마지막 버전이라는 것이다.
자동차 내비에서도 이 운영체제가 돌아가는 경우가 있다.
PC용으로 오랫동안 개발되어 온 C++ 코드를 이 플랫폼과 컴파일러용으로 포팅하려다 보니 지금까지 편하게 써 왔던 auto와 lambda, nullptr 따위를 몽땅 구 문법으로 다시 써 주는 불편을 감수해야 했다. 저기서는 2010년대 이후에 새로 도입된 C++ 문법이 지원되지 않기 때문이다.
이 특정 플랫폼만 그런지는 모르겠지만, 인상적인 점은 뭔가 듬성듬성 빠진 함수가 많다는 것이었다. 덩치를 줄이기 위해서 다른 우회 대체제가 있다 싶은 건 몽땅 빼 버린 모양이다.
일례로, DrawText와 ExtTextOut은 있지만 더 단순한 함수인 TextOut은 없다(더 범용적인 대체제가 있으니까..). 파일 시스템도 도대체 뭐 어찌 되는지 GetCurrentDirectory, GetWindowsDirectory 이런 거 없다.
칼질은 C 함수 쪽도 예외가 아니다. 엄연히 ANSI 표준인 time 함수가 없어서 Windows API를 이용한 대체 함수를 직접 만들어야 했다.
그리고 어찌 된 일인지 FILE* 스트림 기반의 fopen 계열 말고, 정수형 파일 핸들을 주고 받는 저수준 _create, open 계열의 함수도 없더라. PC에서 쓰던 코드를 곧장 포팅하는 건 생각만치 쉽게 되지는 않았다.
제일 압권인 건 CE는 9x와는 정반대로.. API 함수에 W 버전만 있고 A 버전이 깔끔하게 없다는 것이었다.
9x는 W 버전도 껍데기는 있지만 실행이 에러 코드와 함께 몽땅 실패한다. CE에서는 A 버전이 컴파일은 되지만 링크가 되지 않고 곧장 실패했다. 1996년에 첫 개발된 신흥 OS이다 보니, 안 그래도 용량도 부족한데 구닥다리 레거시인 A는 처음부터 배제해 버린 셈이다.
Windows CE에는 W만 있고 A가 없다는 건 먼 옛날에 제프리 릭터 아저씨의 책에서 처음으로 봤는데 실제로 그렇다는 걸 그로부터 10수 년 이상 뒤에야 실제로 확인할 수 있었다.
3. 라이브러리의 형태의 변화
도스 시절에 프로그래밍에 필요한 무슨 라이브러리, API, 또는 SDK라 하면..
어셈블리어로 도스의 각종 특이한 인터럽트를 호출해서 하드웨어를 직통으로 건드리는 마우스 라이브러리, 그래픽/사운드 라이브러리, 심지어 한글 라이브러리 같은 게 많았다. 이런 기능들은 타 언어도 지원했지만 가장 먼저는 C/C++용 헤더와 라이브러리의 형태로 제공되었다.
Windows로 넘어가서는 어지간한 하드웨어 제어는 운영체제의 자체 API만 써도 커버가 되니 3rd party 라이브러리 같은 건 필요가 크게 줄어들었다. 그냥 MS Office와 Visual Studio의 외형을 흉내 내 주는 GUI 툴킷이라든가.. 이미지 파일 처리 라이브러리가 쓰였다. 아 그리고, DirectX SDK는 Windows 플랫폼 SDK로부터 완전히 분리 독립했기 때문에 따로 설치해야 하는 물건이 되긴 했다.
그런데 요즘은 그런 API, 라이브러리라는 게 용도와 형태가 바뀌고 있다. PC 안에서 혼자 돌아가는 프로그램이 고차원적인 하드웨어 제어 기능을 사용하는 게 아니라.. 웹사이트나 스마트폰 앱에서, (1) 특정 대규모 서버에서 제공하는 각종 실시간 정보 제공 기능(날씨, 버스와 지하철 위치, 지리 정보 등등)이라든가 (2) 아주 고수준의 인공지능 내지 패턴 인식 기능(이미지에서 사람 얼굴 인식, 음성 인식 등) 같은 것을.. 1인 개미 개발자가 자기 앱이나 사이트에서 곧장 사용할 수 있게 해 준다.
방대한 인공지능 데이터 검색과 계산은 서버가 담당한다. 바둑 AI로 치면 AI 코드가 라이브러리에 담겨 있는 게 아니라, 별도로 돌아가는 알파고 서버와 통신만 하는 거다.
언어는 무겁고 부담스러운 C/C++과는 거리가 멀며, JavaScript, Go, 파이썬 같은 것을 곧장 지원한다.
과금 체계도 과거의 전통적인 형태와는 사뭇 다르다. 비싼 라이브러리 제품을 몇만~몇십만 원 일시불로 지불하고 사 오는 게 아니라.. 월 얼마씩~ 사용 트래픽이 얼마까지는 무료이고 그 뒤부터는 한 건당 얼마 이런 식으로 매겨진다. 구매 대신 임대 형태이다.
기술은 그 자체는 온통 오픈소스다 뭐다 하면서 무료에 가깝게 공개되고 상향평준화되고 있다. Google API의 기능들을 개발하는 데 컴퓨터공학· 전산학 박사들이 얼마나 많이 투입됐을까..? 그 다음으로는 그냥 자본과 데이터 물량전이 대세이다.
그리고 소프트웨어로 돈 버는 건 기술과 기능 자체가 아니라 그걸로 온통 사용자들의 감성을 사로잡고, 자아· 정체성을 표현해 주는 대가로 형태가 바뀌는 것 같다. 온라인 게임의 부분 유료화라든가 각종 아바타가 대표적인 예이고 말이다.
아, 그렇다고 지금이 전통적인 형태로 판매되는 라이브러리· 미들웨어가 전멸했다는 얘기는 아니다. 게임에서 주로 쓰이는 네트워크 라이브러리, 동영상 캡처 라이브러리 같은 것들은 전적으로 로컬에서 기능이 동작하며, 단품 또는 출시되는 제품 타이틀의 규모 단위로 판매되고 있다. 돈을 한번 지불하고 끝인 것도 있고, 일정 주기로 꼬박꼬박 로얄티를 내는 형태인 것도 있다.
4. 함수 호출과 금융의 관계
좀 엉뚱한 생각을 하자면, 금융을 프로그래밍에다가 비유하고 돈이 오가는 걸 함수 호출과 데이터 전달에다가 비유할 수 있을 것 같다.
현금이 오가는 건 데이터 실물을 통째로 스택에다 얹는 call by value이다. 제일 단순하고 확실하지만 거액의 현금을 매번 들고 다니는 건 번거롭고 귀찮고 위험하다.
그러니 현금, 또는 가까운 미래에 들어올 예정인 돈에 대한 포인터가 등장하는데, 이것들이 바로 수표나 어음이다. call by reference가 현실 세계에도 존재하는 셈이다.
부도는 당연히 잘못된 포인터 접근으로 인한 page fault 내지 access violation과 직통 대응이다.
컴퓨터에는 지금 물리적으로 실제로 있는 메모리보다 더 많은 주소 공간을 끌어다 쓰고, 공간이 없으면 디스크 스와핑이라도 하는 '가상 메모리'라는 개념이 있다. 이게 어찌 보면 '대출'과 비슷한 개념으로 보인다.
물론 컴퓨터의 동작에 경제 용어인 '신용'과 정확히 대응하는 개념이 존재하지는 않는다. 그러니 완전 동일하게 대응하지는 않겠지만.. 그래도 이런 식으로 비유해 보면 생각보다 그럴싸하고 씽크가 맞아 보인다.
참고로, 성경이 말하는 헌금 원칙은 철저하게 리얼 모드이다. 빚을 내거나, 없는 돈을 미리 작정하는 보호 모드 가상 메모리 같은 개념은 없다.
5. 보안 정책
오래된 일이긴 하지만 마이크로소프트는 회사가 돌아가는 방식이 2002년 1월 1일 이전과 이후가 서로 싹 달라졌다. 더 엄격한 보안 정책과 더 일관성 있는 제품 지원 주기 정책이 적용되기 시작했기 때문이다.
일단, 이때를 기점으로 해서 모든 마소 제품에서 이스터 에그가 사라졌다. 문서화되지 않은 특정 동작을 하면 프로그램 개발자 명단이 나타나고 숨겨진 게임이나 애니메이션이 뜨는 것 말이다.
그리고 (a) 일반 지원은 제품 출시 후 몇 년 또는 차기 버전이 나온 지 몇 년 중 짧은 기간까지, (b) 연장 지원은 일반 지원이 종료된 뒤부터 5년간.. 요런 식의 규정이 추가되어 오늘날에 이르고 있다. 그와 함께 도스, Windows 3.x/95 같은 구닥다리 제품들은 지금까지 알음알음 지원되어 오던 것이 2001년 12월 31일을 끝으로 지원이 완전히 끊겼으며, 공식적으로 abandonware 판정을 받았다.
인터넷 덕분에 소프트웨어를 배포하는 데 물리적인 장벽은 사라지다시피한 반면, 원격으로 프로그램을 조종하고 컴퓨터를 장악할 수 있는 보안 위협이 커졌기 때문이다. 결국 소프트웨어라는 건 한번 만들고 나서 끝인 게 절대 아니라 계속해서 지원을 해야 하며, 굳이 기능과 컨텐츠에 아무 변화가 없더라도 보안 취약점이 발견되면 계속해서 고치고 지원해 줘야 하는 반제품 정도로 위상이 바뀌었다.
그러니 소프트웨어의 판매 비용에는 이런 잠재적 사후 지원 비용도 포함되어야 하며, 지원을 한도 끝도 없이 영원무궁토록 해 줄 수는 없으니 구체적인 기간이 법적으로 명시될 필요도 생긴 것이다.
그 뒤로 마소에서는 2000년 중반에 프로그램 개발의 기본 블록이라 할 수 있는 C 라이브러리에서부터 보안 결함을 잡겠다고 strcpy, gets 같은 함수들에 칼질을 가했으며, Visual Studio 2005에서 이를 최초로 적용했다. 컴파일러에 /GS 같은 검사 기능도 추가했다.
재래식 도움말인 WinHlp32 엔진은 너무 구닥다리 기능으로 전락한 데다, 이런 새로운 보안 기준을 충족하게 개량을 하기에는 가성비가 너무 안 맞는 관계(시간과 예산..)로 Windows Vista에서부터는 짤렸다.
Vista에서는 프로그램의 실행 주소를 그때 그때 랜덤화하는 보안 기능이 추가되기도 했다. Windows는 애초에 position independent code를 쓰지도 않는 운영체제인데, 오로지 보안을 위해 그 이념을 근본적으로 부정하고 성능을 희생하는 기능을 넣은 것이다.
이렇게 런타임 환경에다가는 예측 불가능한 요소를 일부러 가미한 반면, 빌드 타임 환경에는 정반대의 정책이 적용되고 있다. 같은 소스 코드를 같은 컴파일러 제품으로 빌드했으면 시간과 장소를 불문하고 바이너리 수준에서 언제나 완전히 동일한 실행 파일이 나오게 하자는 것이다. 이름하여 Reproducible builds이다.
단순히 생성되는 기계어 코드를 넘어서 객체들의 명칭과 배열 순서라든가 내부적으로 생성되는 각종 시그니처까지 완전히 똑같게.. 상상할 수 없을 정도로 다양한 환경에서 실행되는 프로그램을 테스트· 디버깅 할 때 문제를 재연하기 어렵게 만드는 변화 요인를 털끝만치라도 줄이겠다는 의도로 보인다.
시범타로 예전에 실행 파일의 헤더 차원에서 timestamp가 들어가던 곳에도 Windows 10에 들어가는 바이너리들은 그냥 고정된 hash값으로 바뀌었다고 한다. 시간이야 난수의 씨앗으로도 쓰일 정도로 매번 달라지는 값이니 말이다.
이렇게 21세기 들어서 세월이 흐를 수록 마소는 내부의 보안 정책이 갈수록 엄격해지는 것이 느껴진다. 그 와중에 실행 주소 랜덤화와 Reproducible builds라는 두 이념이 본인이 보기에 흥미로운 대조를 이루는 듯해 보였다.
Posted by 사무엘