EXE와 DLL의 경계

1.
프로그래밍을 하다 보면 단독 실행이 가능한 EXE 형태의 프로그램만 만드는 게 아니라, 다른 프로그램에 부속물로 붙거나 여러 프로그램들 사이에서 공유되는 라이브러리, 플러그 인 같은 걸 만들 때가 있다.
플러그 인 정도야 호스트 프로그램이라도 분명하게 존재하니 양반이지만, 임의의 프로토콜을 갖는 공용 라이브러리는 static LIB이든 DLL이든, 그 자체로 단독 실행이 가능하지 않다. 그렇다 보니 그 라이브러리를 사용하는 프로그램을 또 별도로 만들어야 해서 테스트와 디버깅이 여러 모로 불편하다.

그래서 Windows에서 DLL을 만드는 솔루션의 경우, 그 솔루션에다가 DLL을 테스트하는 간단한 EXE도 프로젝트로 따로 만드는 게 보통이다.
Visual C++은 지난 2005부터인가 프로젝트를 새로 생성할 때, 솔루션 디렉터리 아래에 동명의 프로젝트 디렉터리가 한 단계 더 생기고, 한 솔루션에 소속된 프로젝트들의 생성물은 다 동일한 output 디렉터리에 만들어지도록 기본 동작 방식이 바뀌었다. obj 같은 임시 파일들만이 프로젝트별로 자기 고유한 위치에 생성된다. 이것은 나름 바람직한 조치라 여겨진다.

2003 이하 2005 이상
프로젝트1\Release\프로젝트1.exe
프로젝트1\Release\프로젝트1.obj
프로젝트1\프로젝트2\Release\프로젝트2.dll
프로젝트1\프로젝트2\Release\프로젝트2.obj
솔루션\Release\프로젝트1.exe
솔루션\Release\프로젝트2.dll
솔루션\프로젝트1\Release\프로젝트1.obj
솔루션\프로젝트2\Release\프로젝트2.obj

그런데, 발상을 전환하면 DLL을 생성하는 소스를 기반으로 곧바로 EXE를 만들어 DLL의 함수들을 의외로 굉장히 간편하게 테스트를 할 수 있다.
링커의 SUBSYSTEM 옵션 하나만 바꿈으로써 WinMain을 사용하는 GUI 프로그램과 main을 사용하는 콘솔 프로그램을 곧바로 전환할 수 있듯, EXE와 DLL은 똑같이 PE 헤더가 있는 실행 파일이며 본질적인 차이가 거의 없다. 구조체 필드 값이 일부 차이가 나고 entry point에서 같이 전달되는 인자의 타입이 다를 뿐이다.

DLL 프로젝트에서 configuration을 하나 만든다. 테스트와 디버그가 목적이므로 Debug 빌드 것을 초기값으로 가져오면 되겠다. configuration 이름은 Debug EXE 정도로 하자.
그 뒤 프로젝트 속성의 General (일반)으로 가서 Target Extension (대상 확장명)은 .dll이던 것을 당연히 .exe로 바꾼다.
그리고 제일 중요한 Configuration Type (구성 형식)을 Dynamic Library (.dll)이던 것을 Application (.exe)으로 바꾼다.

'확인'을 누른 뒤, DLL 소스의 한구석엔 원래의 DLL엔 없던 WinMain 내지 main 함수를 추가하고, 그 안에다 호출하고 싶은 DLL 클래스/함수들을 마음껏 사용하며 테스트한다.
이것만 해 주면 끝이다. 프로젝트를 이 configuration대로 빌드해서 돌리면 된다.

별도의 EXE를 따로 만들어서 테스트를 하는 거라면 그 EXE에 또 테스트 대상 DLL을 로딩하는 코드가 추가되어야 하지만 DLL 자체의 소스로부터 EXE를 생성하면 그런 번거로운 절차가 필요하지 않으니 더욱 좋다. EXE 자체에 DLL의 코드가 그대로 포함되기 때문이다.

static LIB을 만드는 프로젝트도 이런 식으로 별도의 EXE 생성 configuration을 만들어서 테스트가 가능할 것이다.
다만 DLL/EXE와는 달리 static LIB는 링크 절차가 존재하지 않고 그냥 컴파일만 가능하면 라이브러리 파일이 만들어지기 때문에 이로부터 온전한 EXE를 만들려면 추가적인 링커 설정 같은 게 필요할 것으로 보인다.

2.
여담이다만 DLL뿐만 아니라 EXE도 DLL처럼 export 심벌을 가질 수 있으며 그걸 GetProcAddress를 통해 얻어 올 수 있다.
EXE만 자신이 로딩한 플러그 인 DLL로부터 함수를 얻어 오는 게 아니라, DLL 역시 자신을 로드한 EXE로부터
GetProcAddress( GetModuleHandle(NULL), "GetHostInfo") 이런 식으로 코드를 얻을 수 있다. 이것도 참 기발한 발상이 아닐 수 없다. 어디 활용할 데가 없을까?

내가 개인적으로 굉장히 놀란 것은, 저렇게 한 프로세스 공간의 주인 역할을 하는 EXE가 아니라..
완전히 다른 EXE를 로딩해서 거기에 있는 코드를 실행하는 것도 가능하다는 것이다. EXE는 보통 0x400000 같은 고정된 주소에 로드되며 재배치 정보가 존재하지 않기 때문에 자기 위치에 로드가 못 되면 로딩이 실패한다.

그런데 자신과 로드 주소가 겹치는 EXE도 LoadLibrary를 하면 일단 작업이 성공하며 리소스 추출뿐만이 아니라 GetProcAddress도 실행 가능한 듯하다. 이쯤 되면 EXE와 DLL의 경계가 어찌 되는지가 궁금해진다.

3.
아무 중간 계층 없이 C/C++ 언어만으로 뭔가 라이브러리를 남에게 제공하는 건 애로사항이 적지 않다.

  • 디버그 or 릴리스?
  • 32 or 64비트?
  • 최종 형태는 DLL or LIB?
  • VC++ 어느 버전? (보안 기능 링크 에러)
  • 사용하는 CRT의 형태는 DLL or static?

이런 식으로 상호 일치해야 하는 변수가 급격히 늘어나기 때문이다. 조건부 컴파일이 괜히 필요했던 게 아니다.
C/C++ 런타임 라이브러리도 비주얼 C++의 버전이 바뀜에 따라 내부적으로 야금야금 더해지고 바뀌는 기능이 있기 때문에--특히 보안 관련-- static 링크하는 경우 빌드 툴의 버전이 안 맞으면 이상한 심벌명에서 링크 에러가 나고 각종 문제가 생기기 쉽다.

그나마 같은 비주얼 C++끼리이니까 망정이지 서로 다른 컴파일러끼리 C++ 클래스 라이브러리를 공유한다면 name decoration까지 문제가 됐을 것이다. 사실상 공유 불가능이다.
옛날에는 문자 집합의 크기(일명 유니코드/ANSI)조차도 변수가 따로 있었을 정도이지만 요즘은 그래도 유니코드, 정확히는 wide string만 고려하면 되니 그건 그나마 나아졌다.

이 문제가 워낙 복잡하니..
일차적으로는 COM 같은 바이너리 표준이 나왔을 것이다.
아니면 그냥 소스 코드를 통째로 넘겨줘서 필요한 사람이 알아서 빌드해서 쓰게 하든가. 그 라이브러리가 애초부터 오픈소스 진영의 작품이라면 다행이지만, 상업용 코드라면 인터페이스 부분을 제외한 나머지에다가는 난독화 처리가 필요할 것이다.

그것도 싫으면 저런 골치아픈 요소들을 싹 잊어버리고 자바/C# 같은 바이트코드 기반으로 가는 수밖에 없는데... 그건 물론 성능은 COM보다도 엄청나게 더 희생시킨 귀결일 것이다.
그래도 아무 클래스에나 public static void Main만 있으면 그게 곧 실행 가능한 물건이고 빌드 속도도 안드로메다 급으로 빠르며 골치 아픈 32/64비트 구분 같은 것도 없는 환경이.. C++ 프로그래머로서 참 부럽게 느껴질 때가 있다.

Posted by 사무엘

2014/12/31 08:30 2014/12/31 08:30
, , , ,
Response
No Trackback , 10 Comments
RSS :
http://moogi.new21.org/tc/rss/response/1045

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

Comments List

  1. Lyn 2014/12/31 14:58 # M/D Reply Permalink

    맥의 유니버셜 바이너리가 부러워지는 때입니다 ㅡㅜ

    1. 사무엘 2014/12/31 15:49 # M/D Permalink

      맥OS는 osxcr???.so (OS X C-runtime) 이런 문제가 있지는 않나 궁금하네요. ㅋㅋㅋㅋ
      xcodecr???.so (xcode C-runtime)가 운영체제 보급품과는 따로 논다거나..

    2. Lyn 2014/12/31 16:44 # M/D Permalink

      맥OS는 .so 가 아니라 .dylib 에요 ㅎㅎ

      맥OS는 전통적으로 dynamic 이 아닌 static link 를 굉장히 선호 하고, 만약 dynamic 으로 배포 하더라도 한 파일로 다 패키징 해서 배포하는게 보통이라 (요즘은 규모가 커지다보니 덜 그렇지만... 간단하게 윈도우에서 DLL 이나 모든 데이터를 다 exe 파일의 리소스로 넣는 식이라고 보시면 편합니다) 그런문제가 덜합니다. 심지어는 iOS 는 아예 dynamic link 자체가 안되요

      그리고 컴파일러와 OS 의 버전 차이로 생길수 있는 문제는.... 그냥 새 컴파일러로 빌드 안하면 새 OS에서 안돌아가게 만들어 버리는걸로 해결합니다 (...)

      윈도우 처럼 하위버전 컴파일러에서 상위 OS의 API 를 동적으로 끌어다 쓰는것도 가능 하긴 하지만 ... 사실상 OS가 아니라 펌웨어가 되버린 상황이라 거의 대부분의 유저들이 버전 업을 하니 그런 면에서는 부담이 적은 편이지요

    3. Lyn 2014/12/31 16:44 # M/D Permalink

      게다가 MacOS 는 베이스가 되는 런타임이 C 기반이 아니라 Obj-C 기반인 Cocoa 이고, Obj-C 는 매소드 링크가 컴파일 타임이 아닌 런타임에 이루어 지기 때문에 비교적 그런 문제에서 자유롭습니다.

      ... 문제는 매소드 호출의 오버헤드가 그만큼 크다는거지만

    4. 사무엘 2014/12/31 19:53 # M/D Permalink

      감사합니다. 맥까지 정말 모르는 게 없으시네요~ ^^;; OS가 아니라 펌웨어래.. ㅋㅋㅋㅋ
      확실히 Windows만 유난히도 DLL+스레드를 좋아하긴 하는군요.
      그리고 옵씨는 메시지 방식이어서 Windows의 C++ 개발처럼 가상 함수가 바뀔 때마다 DLL 심벌이 다 바뀌고 바이너리 호환성이 깨지는 걱정을 할 필요가 없어서 참 좋긴 하겠습니다.

    5. Lyn 2015/01/01 01:53 # M/D Permalink

      유닉스 계열은 전통적으로 fork 기반으로 많이 움직였으니까요.

      그 당시엔 커넥션간 인터렉션 자체가 거의 없는 시대이기도 했고(사실 요즘 그 시절로 다시 돌아가는게 아닌가 싶기도 합니다.... 게임이나 메신저 같은게 아니라면) 프로세스 생성이 굉장히 가볍기도 하고... Linux 같은경우는 상당히 오랜 기간 Kernel mode thread 가 없었다는 이유도 있었겠지요. 작업용이 아니라 서버용이었으니까

      윈도는 프로세스 생성이 아무래도 상당히 느린 편이고 대신 Thread 성능이 굉장히 좋은 편인데다 개인용으로 많이 쓰이는 이슈가 컸을 것 같습니다.

      개인용으로 쓸땐 아무래도 프로세스가 여러개 체인이 걸리면 사용자 실수로 죽일 가능성도 높으니까요

  2. Lyn 2014/12/31 16:15 # M/D Reply Permalink

    dll의 exe의 경계는...

    WinMain이 엔트리포인트냐 아니냐의 차이 같습니다 ㅋㅋ

    1. 사무엘 2014/12/31 19:55 # M/D Permalink

      진짜로 엔트리포인트 함수의 프로토타입의 차이밖에 없는 거나 마찬가지 같네요.
      EXE는 int func() 밖에 없고 나머지 핸들 같은 부가정보는 C 라이브러리가 GetStartupInfo, GetModuleHandle, GetCommandLine 같은 Windows API를 따로 호출해서 얻어 와서 WinMain에다 전달하는 반면,
      DLL과 함께 전달되는 reason은 운영체제가 직통으로 함수 인자로 전해 주지요~

  3. 김 기윤 2015/01/05 09:17 # M/D Reply Permalink

    프로그래밍도 프로그래밍이지만, 저는 A와 B의 경계라고 하니까, 오덕력이 발휘해 버리네요(..)

    파동과 입자의 경계
    인간과 요괴의 경계
    생과 사의 경계
    이차원과 삼차원의 경계
    색과 공의 경계

    등의 기술을 가지고 있는 캐릭터라거나 (...)


    각설하고, 플밍을 접한 지로 10년이 넘어가는데, 저는 아직도 DLL 을 한번도(!!) 만든 적이 없네요.
    아직까지는 그냥 exe 만으로 충분했거나, 혹은 이제 그런게 아무래도 좋은 모바일 계열로 넘어간다거나
    하다보니, dll 을 쓸 일이 없었습니다..(..

    1. 사무엘 2015/01/05 11:07 # M/D Permalink

      오덕 인증. ㅋㅋㅋㅋ
      그나저나 확실히 개발 분야뿐만 아니라 개발 환경의 세대차이를 느끼게 하는 대목이네요. 기윤 님 정도의 Windows 프로그래밍 경력으로 DLL을 만들 일이 지금까지 없으셨다니. <날개셋> 한글 입력기는 커널 라이브러리는 말할 것도 없고 플러그 인, IME 외부 모듈, 훅 프로시저까지 전부 DLL이지요.

Leave a comment
« Previous : 1 : ... 1288 : 1289 : 1290 : 1291 : 1292 : 1293 : 1294 : 1295 : 1296 : ... 2204 : Next »

블로그 이미지

그런즉 이제 애호박, 단호박, 늙은호박 이 셋은 항상 있으나, 그 중에 제일은 늙은호박이니라.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2024/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:
3052202
Today:
509
Yesterday:
2713