많은 경우에 라이브러리를 DLL 형태로 만들고 DLL Wrapper 를 만들어 필요할 때마다 LoadLibrary로 로드하여 사용한 후 FreeLibrary로 언로드한다. 이렇게 할 경우는 필요할 때마다 DLL이 동적으로 로드/언로드 되는 것을 ProcExp나 ProcHack으로 쉽게 확인할 수 있다. 참 깔끔하다.
그러나 이 DLL 로드/언로드 자체가 CPU 나 메모리를 좀 많이 필요로 한다는 것을 종종 느낀다. 예컨데 다른 프로세스 때문에 CPU 100% 가까이 유지되고 있고, 가상메모리가 가득차고 페이지폴트가 자주 발생하는 상황에서는 DLL 로드 자체가 실패하는 경우가 있을 정도니 말이다. (물론 아직도 원인이 그 때문이라고 100% 확신할 수는 없다!)
이런 경우 최선의 방법은 동적 로드/언로드 방법을 버릴 수 밖에 없다. 그냥 애초에 프로세스가 시작될 시점에서 계속 로드하고 있는 것이다. 마치 정적 링크한 것처럼 말이다. 그렇다면 이렇게 유틸리티성 DLL 라이브러리의 로드/언로드 방식의 패턴을 다 고쳐야 할까?... 아니면 그냥 FreeLibrary 코드만 주석으로 처리하면 될까?...
일단 기본적으로 제프리 리처 아저씨가 아래의 사실을 알려준바 있다.
LoadLibrary를 사용하게 되면 프로세스별로 라이브러리에 대한 사용 카운트 값을 증가시키며, FreeLibrary 함수를 호출하게 되면 이 값을 감소시키게 된다. 예를 들어 DLL을 로드하기 위해 LoadLibrary 함수를 최초로 수행한 경우, 시스템은 DLL 파일 이미지를 프로세스의 주소 공간에 매핑하고 DLL의 사용 카운트 값을 1로 설정한다. 만일 동일 프로세스 내의 스레드가 동일한 DLL 파일 이미지에 대해 LoadLibrary 를 또다시 호출하게 되면, 시스템은 DLL 파일 이미지를 프로세스의 주소 공간에 두 번 매칭하지 않고 관련 DLL의 사용 카운트 값만 증가시킨다.
그럼 실제로 LoadLibrary 만 계속 호출하면 어떻게 될까? 오늘은 시간이 넉넉한 관계로 실습을 해보자.
위에서 언급된 사용 카운트란 것이 어디 있는지 부터 살펴봐야 하는데 정확히는 아래와 같다.
프로세스의 정보가 들어있는 PEB 구조체 멤버 중에는 PEB_LDR_DATA 구조체 정보가 있다.
또 그 안의 구조체 정보에는 InLoadOrderModuleList 라는 링크드 리스트가 있는데 이 링크드 리스트에는 프로세스에서 로드된 모듈의 정보가 담긴 구조체의 포인터가 담겨있다. 즉, LDR_DATA_TABLE_ENTRY의 주소값이 들어가 있는데 LDR_DATA_TABLE_ENTRY의 멤버에 보면 LoadCount 라는 USHORT 형의 변수가 있다. 이 변수가 바로 제프리 리처 아저씨가 말한 사용 카운드 이다.
대충 LoadCount 값을 출력해주는 프로그램을 만들었다.
그리고 일단 임의의 DLL 을 만들어 먼저 정적 링크하여 실행한 결과가 아래와 같다.
정적 링크의 경우는 USHORT 형의 최대값인 0xFFFF 가 설정되어 있는 것을 확인할 수 있다. (정적 링크의 경우 프로세스 실행시점에 로드되고 종료 시점에서 같이 언로드 된다.)
이제 동적로드를 해보자.
위 그림과 같이 LoadCount 가 1로 나온다. 즉, 최초 로드시점에서 LoadCount++ 시킨것이다.
여기서 LoadLibrary 횟수를 증가시키기 시작했다. 20 번을 더 눌렀고
FreeLibrary 를 5 번 눌렀더니 LoadCount 가 16이 되었다.
자~! 이제 갈 때까지 가보자.
USHORT의 최대값 근처인 65535 에 하나 못 미치는 65534 까지 LoadLibrary를 호출했다.
여기에서 FreeLibrary를 한번 해봤다.
예상되로 LoadCount 는 줄어들었다.
그런데 다시 LoadLibrary 를 최대값인 65535 까지 LoadLibrary 를 눌러보니 이제는 FreeLibrary를 눌러도 LoadCount 가 줄어들지가 않았다.
즉, LoadLibrary 횟수가 65535 까지 가게 되면 해당 DLL 은 동적로드 된 것이 아니라 정적로드 되었을 경우 설정되는 값인 0xFFFF로 설정되어 버리는 것이다. 아마도, MS 에서 의도한 것이 아닌가 하는 생각이 든다.
결론적으로 LoadLibrary 를 65534번까지만 할 경우에는 언로드 할 수 있는 기회가 있지만 65535 번까지 LoadLibrary만 호출한다면 더 이상 언로드할 수 없는 정적 링크한 상태가 되어 버린다.
따라서 의문의 본질인 "동적 로드/언로드 패턴의 DLL 사용에 있어서 DLL 로드/언로드 방식을 피하고자 할 때에는 단지 FreeLibrary 만 주석으로 처리한다고 하더라도 그로 인한(주석처리) 문제는 발생하지 않을 것으로 예상된다" 이다. 뿐만 아니라 의도와 정확하게 맞게 정적링크한 형태로 되어 버린다.