Windows

DLL의 모든 것

_침묵_ 2006. 11. 10. 01:18

출처 :http://haje.kaist.ac.kr/~oedalpha/tips/WindowsProgramming/07-dll.txt

 

Win32 아키텍처 완전 해부 ?


DLL의 모든 것


이번 호에서는 DLL에 관한 전반적인 내용을 살펴보기로 한다. DLL을 사용하기 위해 알아야 할 일반적인 내용과 DLL의 생성 및 사용법을 알아보고, 필자가 코딩한 FTP 프로그램을 통해 어떻게 DLL을 exe 프로그램에서 사용할 수 있는지 살펴보겠다.

마이크로소프트의 윈도우 오퍼레이팅 시스템을 사용하는 사람이라면 DLL, 즉 Dynamic Linking Library를 모르는 사람이 거의 없을 것이다. 필자가 사용하는 윈도우 NT 4.0의 winnt\system32 및 winnt\system 디렉토리에는 200개가 넘는 DLL이 있으며, 윈도우 98 윈도우 디렉토리에 자그마치 450여 개의 DLL들이 있다. 물론 윈도우에서 DLL을 사용하는 응용프로그램들의 종류에 따라 차이가 있겠지만 우리가 사용하는 DLL의 수는 이보다 많을 수도 있다. 이렇게 많은 수의 DLL을 사용하고 있지만 정작 DLL에 대해 충분한 지식을 가진 사람은 그리 많지 않다.
이 글을 통해 어떻게 하나의 DLL이 생성되는지, 그리고 라이브러리 파일(.LIB) 및 실행파일 (.EXE)과는 어떻게 연동되는지에 대한 구체적인 내용을 살펴보겠다. 이것은 DLL과 실행 파일의 개발자, 테스터, 시스템 관리자, 윈도우 32 플랫폼 사용자 등 모든 윈도우 사용자에게 필요한 도움을 줄 것으로 생각한다. 이 글을 통해 윈도우에서 사용되는 DLL의 개발 과정, 윈도우 실행 프로그램과의 연동을 비롯하여 DLL에 관한 제반 사항들을 이해하게 될 것이다. 여기서는 DLL의 개념과 장점, 자원공유, DLL의 사용법 등 DLL을 사용하는데 알아야 할 일반적인 내용을 살펴보겠다.

 

DLL의 정의와 사용법


흔히 말하는 DLL이란 실행파일이 요구하는 함수, 변수, 또는 데이터를 담고 있는 프로그램을 의미한다. 보통 윈도우 32 플랫폼과 같은 프로세스 내에 존재하는 실행 프로그램의 필요에 따라 동적으로 사용되도록 하는데 그 목적이 있다. DLL은 흔히 같은 시스템 내에 존재하기 마련이지만 COM DLL과 같이 COM 클라이언트를 위해 만들어진 DLL도 있다. 어느 DLL이나 그 개념은 동일하므로 여기서는 윈도우 32 환경에서 사용되는 일반적인 DLL에 초점을 맞추어 설명하도록 하겠다. DLL은 공통된 프로그램만 하나의 파일 형태로 묶어 놓았으므로 사용할 때 다음과 같은 여러 이점이 있다.

·공통 프로그램은 다시 코딩할 필요가 없으므로 개발자의 개발 시간을 단축한다.
·해당 exe 파일의 크기가 축소되므로 하드디스크를 보다 효율적으로 사용할 수 있다.
·같은 프로세스 내에서는 동일한 DLL을 여러 번 메모리에 올리지 않아도 되므로 시스템의 메모리를 절약할 수 있다.

 

DLL과 자원 공유


DLL을 사용할 때 비록 같은 시스템이라도 다른 프로세스에 의해 사용되면 같은 DLL이 여러 번 메모리에 올라가게 된다. 윈도우 32는 다중 프로세스 환경을 지원하는데, 이는 여러 프로세스가 하나의 시스템에서 작동할 수 있음을 의미한다. 이 때 각 프로세스는 자신만의 가상 메모리 영역을 차지하게 되며 특별한 코딩 없이는 프로세스간의 메모리 영역은 침범할 수 없도록 되어 있다.
따라서 윈도우 32 환경에서 DLL이 필요한 어느 한 실행 프로그램을 사용자가 두 번 실행했다면 두 개의 프로세스가 생성되며, 각 프로세스는 자신의 가상 메모리 영역 내에서 같은 DLL을 따로따로 자신의 메모리 영역에 올리게 된다. 또한 DLL은 새로운 윈도우를 생성하지 않고 메시지 처리에 관한 코딩을 하지 않는다. 이 두 가지 작업은 모두 사용자 윈도우에서 처리해야 할 사항들이다.
윈도우에서 없어서는 안될 중요한 DLL 세 개를 꼽는다면 Kernel32.dll, user32.dll, 그리고 gdi32.dll이다. Kernel32.dll은 윈도우 파일 시스템, 메모리 관리 등에 관련된 기능을 가지고 있으며, user32.dll은 사용자 인터페이스와 관련된 함수를, 그리고 gdi32.dll은 그래픽에 필요한 제반 함수를 담고 있다.
다음에 나열된 .LIB 파일들은 필자가 본 글과 관련된 예제 프로그램을 링크하기 위해 사용한 DLL의 이름이다. 이들 중 wininet.lib를 제외한 모든 프로그램들은 Visual C++의 Win32로 링크하는 경우 기본적으로 따라오는 라이브러리들이다. wininet.lib을 제외한 다른 모든 .lib 파일들은 Visual C++에서 윈도우 32 프로그램을 만들 때 기본적으로 포함되는 DLL들을 사용할 수 있게 해주며, wininet.lib은 인터넷 서비스를 담고 있는 DLL을 사용할 수 있게 해 준다.
 
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib
advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib
odbc32.lib odbccp32.lib wininet.lib


물론 여기의 링크 리스트에 포함되었다고 해서 이들 DLL이나 DLL에 포함되어 있는 함수들을 사용할 수 있는 것은 아니다. 이들 함수를 사용하기 위한 환경을 만들어 주는 적절한 헤더파일과 이에 해당하는 DLL 파일들이 제 위치에 존재하고 있어야 한다.
예를 들어 kernel32.lib에서 제공되는 함수들은 윈도우 NT 4.0의 경우 681개이다. 여기에는 LoadLibraryA, LoadLibraryExA, LoadLibrary ExW, LoadLibrar yW 등을 포함한 수많은 함수가 제공되고 있다.
앞으로 나오게 될 LoadLibrary를 예로 들어보자. LoadLibrary를 사용하기 위해서는 이 함수를 담고 있는 DLL, 즉 Kernel32.dll이 우선 시스템 내에 존재하고 있어야 하며 Kernel32.dll을 가리키는 Kernel32.lib 파일이 또한 존재해야 한다. 그리고 이 DLL을 사용하는데 필요한 준비를 하기 위해 헤더파일을 소스 프로그램에 첨부하여 컴파일하고 링크해야 비로소 알맞은 버전의 LoadLibrary를 시스템에서 사용할 수 있게 된다.
이렇게 한번 메모리에 적재된 DLL은 같은 프로세스 내의 어떠한 exe 파일에서도 접근이 가능하며 자유롭게 사용할 수 있다. DLL에 내장된 이런 함수들은 시스템의 여러 다른 프로그램들에 의해 계속 사용되며, 사용이 끝나면 프로세스의 메모리에서 삭제되어 시스템으로 자원을 환원하므로 불필요한 자원의 사용을 피하게 된다.

 

프로그램에서 DLL 사용하기


DLL을 우리가 만드는 exe 프로그램에서 사용하기 위해서는 우선 DLL 코드를 메모리에 적재 또는 매핑해야 하는데, 여기에는 implicit와 explicit의 두 가지 방법이 있다. implicit은 본 예제 프로그램과 같이 DLL에 내장된 함수를 exe 프로그램에서 그냥 사용하는 것이며, explicit은 LoadLibrary나 LoadLibraryEx를 사용하여 프로그래머가 직접 메모리에 올려주는 방법이다. 어떤 방법을 사용하든지 해당 DLL을 필요한 시기에 메모리로 올려주며 사용한 DLL의 사용 회수를 증가시킨다는 점에 있어서는 다를 바 없으나 운영에는 다소 차이가 있다.

 

Implicit Linking


Implicit은 정확하게 표현하면 Implicit 로더 타임 링킹이라 할 수 있는데, 이는 해당 exe 프로그램에서 사용될 DLL 정보를 exe 코딩에 내장하는 방법이다. 이렇게 하면 윈도우가 하나의 exe 파일을 실행할 때 어떤 DLL이 필요한지 알려주어 exe 파일이 착오 없이 실행된다. 이런 상태에서 exe 프로그램이 DLL에 내장된 어느 함수를 부르면 오퍼레이팅 시스템은 해당 함수가 실린 DLL을 정해진 알고리즘에 의해 찾아서 프로세스의 메모리 공유 지역에 적재하여 함수의 사용이 가능하게 된다. Implicit linking을 사용하기 위한 절차는 다음과 같다.

 

1. 원하는 함수가 적재하고자 하는 DLL에 내장되어 있음을 확인한다.
2. 원하는 함수가 다음과 같은 위치에 존재하고 있음을 확인한다.

·원도우 등록기의 KnownDlls 폴더의 값은 그림 1과 같이 윈도우 등록기의 “/HKEY_LOCAL_MACHINE/System/CurrentControl Set/Control/SessionManager/knownDlls” 폴더에 명시되어 있다.
·exe 실행 파일과 같은 디렉토리
·프로세스의 현재 디렉토리
·윈도우 시스템 디렉토리나 DLL 디렉토리이다. DLL 디렉토리는 보통 %systemRoot%를 의미하며 윈도우 NT 4.0의 경우 이 디렉토리는 위에서 언급한 등록기에 명시되어 있는데, 이 값은 특별히 관리자가 변동시키지 않았다면 WinNT/System32가 된다.
·윈도우 디렉토리이다. 윈도우 NT 4.0의 경우 이 값은 WinNT이다.
·PATH 환경 변수가 가리키는 디렉토리이다. 이 때 필요한 DLL을 항목 2에 명시한 위치의 어느 곳에서도 찾을 수 없다면 시스템은 오류 메시지가 담긴 다이얼로그 박스를 사용자에게 보여주고 해당 프로그램을 완전히 취소하게 된다.
3. 프로그램에서 필요한 함수를 불러 사용한다.
4. 해당 프로세스의 소멸과 함께 DLL 또한 메모리에서 소멸되며 프로그램의 실행이 종결된다.

 

Explicit Linking


Explicit linking은 Implicit linking에 비해 그 사용이 조금 까다롭지만 원하는 시점에 마음대로 올리고 내릴 수 있다는 점에서 관리가 훨씬 자유롭다. Explicit linking은 LoadLibrary나 LoadLibraryEx에 의해 처리되며, 프로그램에서 이러한 함수들을 호출함과 동시에 알고리즘에 의해 필요한 DLL을 찾은 후 해당 DLL을 프로세스의 메모리에 매핑하여 실행을 위한 준비를 끝낸다. 여기서 사용되는 LoadLibrary는 DLL의 이름을 LPCTSTR 타입의 인자로 받으며, 실행이 성공하면 HINSTANCE를, 실패하면 NULL을 각각 반환한다.
LoadLibrary와 비슷하지만 좀 더 많은 인자를 요구하는 함수가 LoadLibraryEx이다. 이 함수는 DLL이 프로세스의 메모리에 매핑되는 구체적인 방법을 명시하는 dwFlag가 DWORD 형태로 요구된다는 점 이외에는 LoadLib rary와 같은 일을 한다. 즉 LoadLibraryEx를 사용하면 DONT_RESOLVE_DLL_REFERENCES, LOAD_LIBRARY_AS_DATAFILE, 그리고 LOAD_WITH_ALTERED_SEARCH_ PATH와 같은 다양한 방법으로 DLL을 프로세스의 메모리에 매핑할 수 있다.

·DONT_RESOLVE_DLL_REFERENCES : 이 옵션은 윈도우 NT에서만 사용할 수 있으며 DllEntryPoint를 부르지 않을 때 사용한다. DllEntry와 DllExit 코드는 DllMain을 설명할 때 함께 하기로 하고, 지금은 Dll의 Entry Point에서 명시된 코드를 실행하지 않을 때 이 옵션을 사용한다는 것만 이해하도록 하자.
·LOAD_LIBRARY_AS_DATAFILE : 이 옵션은 DLL 파일을 하나의 단순한 데이터 파일로 이해하고 적재하도록 하는데 그 목적이 있다. 실행 코드가 전혀 없는 DLL들은 시스템에서 DLL의 실행을 위한 특별한 준비가 필요하지 않으므로 실행 시간을 절약할 수 있다.
·LOAD_WITH_ALTERED_SEARCH_PATH : 이 옵션을 사용하면 위에서 언급한 DLL을 탐색하는 경로를 LoadLibraryex(LPTCSTR)에 명시된 대로 바꿀 수 있다는 이점이 있으나 아직 필자도 이 옵션은 사용해 보지 않았다.

이렇게 LoadLibrary나 LoadLibraryex로 프로세스의 가상 메모리에 적재된 DLL은 사용이 끝나면 ‘BOOL FreeLibrary (HINSTANCE hInstDll)’을 사용하여 풀어주면 된다. 이상에서 설명한 Implicit linking과 Explicit linking의 과정을 종합하면 그림 2와 같다. DLL은 시스템에서 사용되는 하나의 자원으로서 독립된 카운터를 가진다. DLL이 메모리에 적재될 때, 그리고 사용 후 메모리에서 풀어줄 때 카운터의 값이 이에 따라 바뀌게 된다. 이 때 카운터가 0이면 해당 DLL은 더 이상 사용되지 않으므로 시스템은 자동으로 이 DLL을 메모리에서 삭제하게 된다.


Implicit linking으로 DLL을 사용하고자 할 때는 해당 LIB을 링크 옵션에 더해준 뒤 헤더파일을 소스코드에 포함시키고 원하는 함수를 사용하면 되므로 간단하다. 하지만 Explicit linking으로 DLL을 사용하고자 할 때는 몇 가지 과정이 더 포함되어야 한다. 이에 대한 자세한 내용은 ‘DLL의 생성과 사용’에서 다루어 보기로 하겠다.

 

 

DLL의 구현


여기서는 실제 DLL을 구현하는 과정에 대해서 살펴보기로 하겠다.


DLLMain


DllMain은 함수를 DLL을 통하여 export 할 때 적용할 수 있으며, 이 DllMain은 어떤 DLL 프로그램에 있어서 시작점이면서 또한 끝나는 점이기도 하다. 즉 DLL이 사용될 때와 풀어질 때 시스템은 DllMain을 거치도록 되어 있다. DllMain은 프로그래머가 생성해 주지 않을 때는 다음과 같은 기본값이 시스템에 의해 제공되지만, 스레드나 프로세스 레벨에서 DLL의 코드 실행과 관련된 초기화 작업이 필요하면 DllMain의 인자 중 하나인 ‘ul_reason_for_call’을 분석하여 필요한 초기화 작업을 처리해 줄 수 있다.

BOOL   WINAPI   DllMain (HANDLE hInst,
                        ULONG ul_reason_for_call,
                        LPVOID lpReserved)
{
 return TRUE;
}
‘ULONG ul_reason_for_call’은 네 개의 옵션을 가지며 다음과 같은 방법으로 사용할 수 있다.

BOOL APIENTRY DllMain( HANDLE hModule,
                        DWORD ul_reason_for_call,
                        LPVOID lpReserved )
{
    switch( ul_reason_for_call ) {
    case DLL_PROCESS_ATTACH:
    ...
    case DLL_THREAD_ATTACH:
    ...
    case DLL_THREAD_DETACH:
    ...
    case DLL_PROCESS_DETACH:
    ...
    }
    return TRUE;
}
이 코드에서 DllMain의 두 번째 옵션인 ul_reason _for_call을 분석하여 스레드와 프로세스 레벨에서 필요한 초기화 작업을 수행할 수 있다. DLL_PROC ESS_ATTACH 옵션은 새로운 프로세스가 기본 스레드와 함께 생성되면서 DLL을 사용할 때 시스템이 DllMain을 부르기 위한 옵션이다. 따라서 이 옵션은 프로세스 레벨에서 필요한 초기화 작업에 유용하게 사용할 수 있다. 예를 들어 프로세스 레벨에서 정의된 하나의 변수를 해당 프로세스에 속한 모든 스레드가 사용하고자 할 때 같은 변수지만 각 스레드마다 다른 값을 독립적으로 저장하도록 하기 위해 우리는 Thread Local Storage를 사용한다. 이 때 변수는 프로세스 레벨에서 정의되고 그 값은 스레드마다 독립되므로 초기화해 줄 수 있는 좋은 예가 된다. DLL_THRE AD_ATTACH 옵션은 기본 스레드 외에 다른 스레드가 새롭게 생성되어 DLL을 필요로 할 때 시스템이 사용하는 옵션이다. 이 옵션 역시 스레드 레벨에서 초기화하고 싶은 작업을 수행하기에 좋다. DLL_PROCESS_DETACH 및 DLL_THREAD_DETACH는 프로세스와 스레드가 사용 중인 DLL을 그만 사용하고자 할 때 시스템이 DllMain을 부르는 옵션으로 초기화된 데이터나 변수를 원래 값으로 환원할 때 사용한다.
시스템이 exe 실행 파일의 WinMain 직접 부르지 않는 것처럼 DllMain 함수 역시 시스템에서 직접 부르는 것이 아니다. DLL의 Attach 및 Detach 이벤트가 발생하면, 즉 DLL이 프로세스의 가상 메모리에 매핑이 되면 시스템은 dll의 시작을 위해 만들어진 DLL CRT startup 함수를 부른다. 이 DLL CRT 함수는 C Run-Time에 필요한 초기화 작업들을 수행하며, 이 DLL CRT startup code가 DllMain()을 실행하도록 되어 있다. DllMain에서 작업이 끝나면 exit 값을 다시 CRT startup code로 반환하여 DLL의 실행이 끝난다.

 

DLL의 생성과 사용


DLL 파일은 다른 exe 프로그램에서 사용할 여러 함수를 내장하고 있으며 exe 파일들은 필요한 함수를 DLL에서 불러와서 사용한다. 즉 DLL 파일은 함수를 export하게 되며, exe 파일은 함수를 import하는 방식이 된다. 물론 DLL 함수를 부르는 것이 반드시 exe 프로그램만은 아니다. 하나의 DLL이 또 다른 DLL을 부르는 경우도 많다. 중요한 것은 공유하는 함수를 제공해 주는 것은 export로, DLL에서 제공하는 공유 함수를 가져와서 사용하는 경우는 import라는 개념으로 정의한다는 것이다.
DLL 파일은 필요하다면 DllMain과 다른 여러 가지 함수를 export하기 위해 정의해 주게 되는데, 다음으로 DLL에서 어떻게 함수들을 export하기 위해 준비하는지 알아보도록 하겠다. 심볼 import와 export 테이블에 대한 보다 자세한 내용은 ‘DumpBin으로 WinInet.dll 보기’를 참조하기 바란다.

 

심볼 export


많은 경우 시스템에서 제공되는 DLL의 함수를 import하여 사용하지만 공통된 기능을 사용하는 여러 가지 프로그램이 포함된 큰 프로젝트를 만들 때는 자체적으로 필요한 함수를 정의하여 DLL 실행 파일로 만들어 export 할 수도 있다. 우선 하나의 함수를 DLL로부터 export하기 위해서는 특별한 정의가 필요한데, 이것이 바로 __declspec (dllexport)이다. 예를 들어 BOOL processData라는 함수를 DLL을 사용하여 다른 DLL이나 exe 파일에서 사용이 가능하도록 export하려면 다음과 같이 정의할 수 있다.

 

__declspec(dllexport) BOOL processData (DWORD, DWORD);

 

컴파일러에서 __declspec(dllexport)로 정의된 함수나 변수를 보면 .obj 파일에 특별한 정보를 첨부하여 export를 위한 함수라는 것을 표시한다. 이 정보는 링크로 인해 해당 DLL 파일과 같은 이름의 .LIB 파일을 생성하도록 해준다. 이 때 많은 함수가 DLL 파일로부터 export 되도록 정의되었다면 링크는 export 되는 모든 함수의 이름, 즉 심볼들을 알파벳 순서대로 저장한다. DLL을 만들 때 한 가지 유의할 점은 Visual C++로 만든 DLL의 심볼을 다른 컴파일러로 만든 exe나 DLL에서 export 할 때 심볼 이름의 차이점을 이해하는 일인데, export와 import에서 모두 같은 컴파일러를 사용한다면 걱정할 필요가 없다.
하지만 현재 우리는 다양한 종류의 컴파일러를 사용하고 있으므로 누가, 언제, 어떤 컴파일러로 export된 DLL 함수를 사용하게 될지 알 수 없다. 필자도 그렇게 다양한 컴파일러를 사용하는 편은 아니다. 윈도우 32 프로그램의 컴파일에는 주로 Visual C++와 Borland C++를 사용하고 있으며, 노벨 NLM의 컴파일에는 왓캄이나 코드와리어를 사용한다. 자바를 사용하기는 하지만 주로 JDK 레벨에서 사용하므로 특정 벤더의 자바 컴파일러를 사용하지는 않는다. 그렇다 하더라도 역시 다른 컴파일러에 의해 생성된 심볼의 통일성 문제에서 완전히 자유롭지는 않다. 어떤 이유에서건 하나의 DLL을 export 할 때 우리는 직접 만든 DLL이 어떤 컴파일러에서도 import 되도록 할 필요가 있다.

 

심볼 import


DLL에서 export 된 심볼을 다른 DLL이나 exe 파일에서 import하여 DLL 함수의 공유가 이루어진다. 하나의 exe 프로그램을 만들 때 지역 변수나 지역 함수들은 우리가 흔히 사용하는 방법대로 필요한 장소에 정의해 주면 된다. 하지만 DLL의 함수나 변수를 사용할 때는 컴파일러에게 특정한 정보를 제공할 필요가 있는데, 이것이 바로 심볼 export에서 사용한 것과 비슷한 방법인 __declspec (dllimport)이다. 따라서 위에서 export한 심볼을 import 하기 위해서는 다음과 같이 정의해 주면 된다.

 

__declspec(dllimport) BOOL processData (DWORD, DWORD);


컴파일러가 이 코드를 처리하면서 .OBJ 파일에 특별한 정보를 넣어 두는데, 이 정보는 링크가 EXE 파일을 만들 때 어떤 DLL의 함수를 .LIB 파일에서 찾을 수 있는지를 알려주는 역할을 한다. 링크가 .LIB 파일에서 import 심볼들을 발견하면 링크는 이러한 import 심볼들을 exe 파일의 import 심볼 테이블에 삽입하여 링크가 exe 파일을 하드디스크에 쓰기 위한 작업을 마치게 된다. 이렇게 준비된 exe 파일은 import된 심볼을 어떤 DLL에서 export하고 있는지 알고 있으므로 Implicit 또는 Explicit 링크 프로세스에 의해 함수의 사용이 요구되었을 때 해당 DLL을 현재 실행 중인 프로세스의 가상 메모리에 즉시 올릴 수 있게 된다.

 

DLL 헤더파일 만들기


심볼의 import, export를 위해 헤더파일이 반드시 필요한 것은 아니다. 하지만 import와 export 프로세스를 쉽고, 간편하게 코딩하기 위해 헤더파일을 사용하기도 한다. import 및 export 프로그램에서 정의된 ‘__declspec() ...’를 #include ‘myDll.h’와 같이 간단하게 정의할 수 있다고 해보자. 이렇게 하면 myDll.h 파일을 다음과 같이 만들 수 있다. EXPORTAPI를 DLL 파일에 정의한 후 본 헤더파일을 첨가하여 같은 파일을 import와 export 프로그램 모두에 사용할 수 있다.

 

#if !defined (EXPORTAPI)
#define  EXPORTAPI  __declspec(dllimport)
#endif


위의 헤더파일을 사용하기 위해 DLL의 export 파일에 있는 함수들을 #define EXPORTAPI로 정의하고 다음 줄에 #include “myDll.h”를 써주면 된다. import 프로그램에서는 EXPORTAPI를 정의할 필요 없이 바로 #include “myDll.h”를 사용하면 된다. 심볼을 import, export 하기 위해 각 함수를 __declspec(dllimport), 그리고 __declspec(dllexport)으로 정의한다고 언급한 바 있다. 이렇게 헤더파일을 사용하여 하나의 헤더파일을 import와 export 프로그램에 모두 사용할 수 있다. 수정이 필요할 때는 import와 export 모두를 수정할 필요없이 헤더파일 하나만 수정하면 되므로 프로그램의 관리가 용이하다.

 

DumpBin으로 WinInet.dll 보기


DumpBin.exe 프로그램은 마이크로소프트의 비주얼 C++ 컴파일러와 함께 제공되는 프로그래밍 툴이다. 이 툴은 주로 기존의 DLL에서 제공되는 함수들을 파악하고자 할 때 사용할 수 있고 실행에 필요한 다양한 옵션을 제공하고 있는데, 필자가 DLL DumpBin 실행 결과를 출력하기 위해 사용한 옵션은 다음과 같다.


DumpBin.exe /imports /exports /out:wininet.txt c:\winnt\system32\wininet.dll


이 명령의 실행 결과로 윈도우의 C:\WinNT\Sys tem32 디렉토리에 위치하는 WinInet.DLL의 import와 export 함수들은 다음과 같다.

 

Dump of file c:\winnt\system32\wininet.dll
File Type: DLL
         Section contains the following Imports
            SHLWAPI.dll
               70BD75E0    D2   StrCmpNIA
               70BD2720    D6   StrCpyNW
  . . .
            ADVAPI32.dll
               77DC208E    E9   GetTokenInformation
               77DD9815   19C   RegOpenKeyA
  . . .
            KERNEL32.dll
               77F0D109   23D   ReadFile
               77F1B2C1   1D0   IsBadReadPtr
  . . .
            USER32.dll
               77E71BF1   15A   GetWindowLongA
               77E72575   218   SendMessageA
  . . .
         Section contains the following Exports for WININET.dll
            ordinal hint   name
                115    0   CommitUrlCacheEntryA  (00004C42)
                116    1   CommitUrlCacheEntryW  (0004F103)
  . . .
     Summary
        3000 .data
        4000 .reloc
        A000 .rsrc
       60000 .text


이 데이터를 살펴보면 import, export, 그리고 summar y 부분으로 크게 나눌 수 있고, import 부분은 다시 DLL 별로 구분되어 해당 DLL로부터 import 되는 모든 함수들을 심볼 주소, 인스턴스 핸들, 그리고 심볼 이름의 순으로 나열되어 있다.
따라서 우리는 WinInet.DLL이 StrCmp NIA, StrCpyNW 등의 함수들을 SHLWAPI.dll로부터, GetTokenInformation, RegOpenKeyA 등은 ADVAP I32.DLL로부터, ReadFile, IsBadReadPtr 등은 Kernel32.DLL로부터, 그리고 GetWindowLongA, SendMessageA 등은 USER32.DLL로부터 각각 import 하고 있음을 알 수 있다. Export 부분은 차례순, 인스턴스 핸들, 알파벳순으로 나열된 심볼 이름을, 그리고 괄호 속에는 심볼의 주소를 기록해 놓았다. 따라서 WinInet.DLL에서 export하는 많은 함수들 중에 CommitUrlCache EntryA, CommitUrlCacheEntryW 등이 포함되어 있음을 알 수 있다.
마지막으로 summary 부분을 보면 먼저 숫자가 나오고 점으로 시작하는 문자열이 나열되어 있는데, 처음 나오는 숫자는 16진법의 바이트의 크기를 나타내고 뒤따르는 문자열은 데이터 형태를 나타낸다. 모든 DLL이나 exe 이미지 파일은 정해진 특별한 부분, 즉 섹션들로 나누어지는데 이에 해당되는 부분은 DLL 이미지의 summary 부분에 나타나도록 되어 있다.
즉 .data는 초기화된 데이터를, .reloc은 relocation size를, .rsrc는 리소스를, 그리고 .text는 프로그램 코드를 저장하는 부분을 각각 나타내며 그 외에도 WinInet.DLL에서 사용하지는 않지만 .bss는 초기화되지 않은 데이터를, .rdata는 런타임 데이터, .edata는 export된 함수 이름의 테이블, .xdata는 예외 처리(exception handling) 테이블, .idata는 import 된 함수 이름 테이블, .CRT은 C 런타임 데이터, .debug는 디버깅 정보, 그리고 .tls는 thread local storage의 데이터를 각각 나타낸다.
이와 같이 이미 정해진 섹션 이름들이 있지만 필요하다면  프로그래머의 재량에 따라 임의로 이름을 정해주고 그 섹션으로의 접근을 Read, Write, Execute, Shared의 네 개의 모드(Access Mode)로 접근 권한을 조정할 수 있다.
임의로 섹션 만들기에 대해 간단하게 설명하도록 하겠다. 윈도우 프로그램에서 임의로 섹션을 만드는 것은 그리 어려운 일이 아니다. 프로그램의 소스 코드를 만들 때 다음과 같은 두 가지 지시어를 사용하여 섹션을 생성하고 필요한 권한을 부여할 수 있다.
첫째, 원하는 데이터를 #pragma data_seg()라는 명령어로 묶는다. 예제에서 보는 것과 같이 int global Counter=0;을 섹션 이름 “my_section”으로 표시하고자 한다면 다음과 같이 할 수 있다.

 

#pragma data_seg(“mysection”)
int globalCounter = 0;
#pragma data_seg()


이렇게 하면 컴파일러는 #pragma data_seg (“mys ection”)와 #pragma data_seg() 사이에 있는 초기화된 모든 변수를 새로운 섹션인 “.mysection” 내에 저장하게 된다.
이 때 globalCounter 변수가 초기화되지 않았다면 .bss 섹션으로 저장이 되므로 지정한 섹션으로 변수를 저장하기 위해서는 반드시 초기화하는 작업이 필요하다.
둘째, 이렇게 정해진 섹션에 필요한 접근 권한을 부여해 준다. 앞서 언급한 것처럼 프로그래머는 생성된 섹션에 읽기(Read), 쓰기(Write), 실행하기(Execute), 그리고 공유(Share) 권한을 부여할 수 있는데, 위에서 생성한 globalCounter에 읽기, 쓰기 및 공유 권한을 부여하고자 한다면 #pragma data_seg() 아래에 다음과 같은 명령어를 사용하면 된다.

 

#pragma comment(linker, “/section:mysection, RW”)


이렇게 하면 mysection은 다른 여러 프로세스가 읽고, 쓰고, 공유할 수 있게 되므로 프로세스간의 데이터 공유가 가능해진다.

 

예제 프로그램 이해하기


마지막으로 윈도우 32 API를 간략하게 소개하고, 필자가 코딩한 인터넷 FTP 프로그램을 살펴보도록 하자.

 

DLL과 관련된 윈도우 32 API


여기 소개된 윈도우 32 함수들은 예제 프로그램에서는 잘 사용하지 않은 API들이다. 이번 기회에 이러한 함수들을 본 예제 프로그램에 첨가하여 사용법을 익히고 반환값을 살펴보기 바란다.

·HINSTANCE LoadLibrary(LPCTSTR lpLibFileName) : LoadLibrary는 DLL의 이름을 입력받아 핸들을 반환하도록 되어 있다. 반환된 핸들은 GetProcAddress API를 사용하여 해당 DLL이 가상 메모리에 올려진 주소를 알려준다. LoadLibrary 실행 후 오류가 발생하면 NULL을 반환하며, 이 때는 GetLastError를 불러 오류의 종류를 알아볼 수 있다.
·HINSTANCE LoadLibraryEx(LPCTSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) : LoadLibraryEx는 모든 면에서 LoadLibrary와 그 성격이 동일하지만 dwFlags를 통해 DLL을 메모리에 올릴 때 여러 가지 옵션을 사용할 수 있다는 점이 다르다.
·BOOL FreeLibrary(HMODULE hLibModule) : FreeLibrary를 사용하므로 DLL의 사용 횟수를 한번 줄여주고, DLL 사용 회수가 0이 되면 시스템은 DLL이 사용하고 있는 메모리를 풀어서 다시 시스템으로 환원해 준다. FreeLibrary 실행이 성공하면 TRUE를, 실패하면 FALSE를 반환하도록 되어 있다.
·HMODULE GetModuleHandle(LPCTSTR lpModuleName) : GetModuleHandle은 DLL 이름을 변수로 입력해 줌으로써 메모리에 올려진 DLL의 핸들을 반환해 준다. GetProcaddress를 사용하여 시스템에 올려진 메모리의 값을 알아낸다는 점은 LoadLibrary와 동일하나 DLL 사용 횟수를 변경하지 않는다는 점이 LoadLibrary와 다르다. 실행이 성공적이면 DLL의 핸들을 반환하지만 실패하는 경우는 NULL을 반환한다.
·DWORD GetModuleFileName( HMODULE hModule, LPTSTR lpFilename, DWORD nSize ) : 이 함수는 모듈 핸들에 해당하는 파일 이름을 알고자 할 때 사용한다. hModule의 값으로 NULL을 입력하면 현재 실행 중인 모듈의 파일 이름을 반환하게 된다. 인자로 모듈의 핸들, 스트링 포인터, 그리고 DWORD 사이즈를 받으며 실행 후 DWORD를 반환한다. hModule은 모듈 핸들을 입력하며 nSize는 lpFileName으로 복사하게 될 문자의 수를 입력한다. 이 핸들을 받아 시스템은 lpFileName 버퍼에 모듈에 해당되는 파일 이름을 채워주고, lpFileName으로 복사한 문자의 수를 반환해 준다.
·VOID FreeLibraryAndExitThread( HMODULE hLibModule, DWORD dwExitCode ) : 이 함수는 FreeLibrary(hLibModule)와 ExitThread (dwExitCode)를 순차적으로 실행시켜 준다. 따라서 이 함수를 사용하면 시스템에서 사용 중인 DLL의 사용 회수를 한 번 감소시키고 즉시 해당 스레드를 끝낸다.
·FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName) : 하나의 DLL에서 export된 함수의 메모리 위치를 알기 위해 사용하는 API이다. 즉 hModule에 DLL 이름을, lpProcName에 DLL이 export하는 함수의 이름을 인자로 입력하여 실행하면 lpProcName에 해당하는 메모리의 주소를 반환해 준다.
 
예제 프로그램


여기 소개하는 예제 프로그램은 마이크로소프트의 WinInet.DLL의 함수들을 사용한 FTP 프로그램이다. WinInet.DLL에서 export하는 함수들을 본 예제 프로그램이 LoadLibrary() 함수를 통해 import하는 과정을 참조하기 바란다. 사실 DLL은 윈도우의 어느 프로그램에서나 사용하므로 필자는 간단한 인터넷 FTP 프로그램을 코딩하면서 DLL을 사용하는 방법을 소개하고자 한다.
본 예제 프로그램의 사용법은 간단하다. 우선 소스로 제공되는 6six.exe를 실행하면 그림 3과 같은 FTP 콘솔 화면이 나타난다. 이 콘솔에는 ‘Run Program’, ‘About’, 그리고 ‘Quit’ 같은 세 개의 메뉴가 나타나는데, ‘Run Program’ 메뉴를 실행하면 FTP를 실행할 수 있는 다이얼로그 화면이 그림 4와 같이 나타난다. 여기에 FTP 서버의 주소를 입력하고 ‘File for FTP Manipulation’의 파일 형태를 수정한 후 GETter/PUTter 옵션을 선택하면 된다. 이 때 FTP 된 파일을 목적지에서 삭제하고자 한다면 ‘Delete the copy after GET/PUT operation’ 박스에 체크해 주면 된다. 그리고 끝으로 ‘Run’ 버튼을 누르면 FTP 세션이 시작된다. FTP 세션이 시작되면 이 다이얼로그 박스는 ‘Close’ 버튼으로 닫아도 프로그램이 계속되며, 모든 FTP 진행 상황은 FTP 콘솔 윈도우로 출력이 된다. FTP 프로그램의 소스코드는 리스트 1과 같다.


리스트 1 : FTP 프로그램 소스코드
/* ************************************************************
프로그램 이름 : FTP 클라이언트 프로그램
저자 : 성철기
*************************************************************/
#include <windows.h>
#include <wininet.h> // FTP 함수 불러오기
#include “resource.h”

/* ************* Protocol type definitions ****************** */

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
 // 메인 윈도우 메시지 펌프
BOOL    CALLBACK dlgProc (HWND, UINT, WPARAM, LPARAM) ; 
 // FTP 다이얼로그 메시지 펌프
BOOL    CALLBACK aboutProc (HWND, UINT, WPARAM, LPARAM) ;
 // About 다이얼로그 메시지 펌프
DWORD   WINAPI   ftpThread(LPVOID lpParam); // FTP 세션을 위한 스레드
void ToConsole(); // FTP 콘솔로 상태 보고를 위한 출력 코딩
void CloseAll(); // 윈도우 핸들 및 인터넷 연결 종결
HINTERNET GetFirstFile(HINTERNET hFtp);
 // 인터넷의 FTP 서버에서 처음 보여지는 파일 가져오기
HANDLE   GetWinFirstFile();
 // 지역 디렉토리에서 첫 파일을 가져와서 PUT을 위한 준비를 한다

/********************** Global Variables **********************/
// 전역 변수들을 정의하고 초기화한다
//static int InetServiceType = INTERNET_SERVICE_FTP;
HINTERNET hInternet, hFtp, hFile;
HINSTANCE ghInst;
HANDLE  ghwnd, gdlghwnd, hWinFile;
HWND  hCheckBox, hRadioButton1, hRadioButton2;
int  iCheckBox, iRadioButton1, iRadioButton2;
static TCHAR * szTitle = TEXT(“FTP 프로그램”);
static TCHAR   szBuffer[80];
static RECT  rectScroll ;
static  WIN32_FIND_DATA finddata;
/* ************* Input Storage from a Dialog Box  ************* */
// 다이얼로그 박스의 FTP 세션에 필요한 변수들을 초기화한다
static TCHAR FtpServerAddr[40] = “www.pserang.co.kr”;
static TCHAR UserName[40] = “anonymous”;
static TCHAR Password[40] = “”;
static TCHAR FilePattern[40] = “*.*”;
static int    FtpPort = INTERNET_DEFAULT_FTP_PORT; //21

/************************** WinMain ***************************/
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
/* 지역 변수들을 정의하고 필요하다면 초기화한다 */
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;
     ghInst = hInstance;
     ghwnd =  hwnd;
/* 예제 프로그램을 등록하기 위한 준비를 갖춘다 */
     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_WINLOGO) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground=(HBRUSH) GetStockObject(WHITE_BRUSH) ;
     wndclass.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU1);
     wndclass.lpszClassName = szTitle ;
// 프로그램을 등록한다
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT
 (“본 프로그램은 윈도우 32 플랫폼에서 실행됩니다.”),
                      szTitle, MB_ICONERROR) ;
          return 0 ;
     }
     // FTP 프로그램의 상태를 보고하게 될 윈도우를 열어 둔다
     hwnd = CreateWindow (szTitle, TEXT (“Console for FTP Thread”),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
// 메인 윈도우의 메시지 펌프
    while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

/**************** 인터넷과 관련된 모든 핸들을 종료한다 ****************/
void CloseAll()
{
 wsprintf(szBuffer, “Fail: Getting first file. 
    Exiting”);ToConsole();
 InternetCloseHandle(hFile);
 InternetCloseHandle(hFtp);
 InternetCloseHandle(hInternet);
}

/* ****** 윈도우의 버퍼에서 받은 메시지를 화면에 출력한 후 재페인팅 한다 ****** */
void ToConsole()
{
 InvalidateRect(ghwnd, NULL, FALSE);
}

/* ****** FTP의 GET에 사용될 첫 번째 파일을 가져오기 위해 준비한다 ****** */
HINTERNET GetFirstFile(HINTERNET hFtp)
{
wsprintf(szBuffer, “FTP서버 %s:%u의 현재 디렉토리에서 첫 번째 파일을 가져옴”,
   FtpServerAddr,FtpPort);
ToConsole();
 hFile = FtpFindFirstFile(hFtp, FilePattern,
    &finddata, 0, 0);
 return hFile;
}     

/************ FTP의 PUT에 사용할 첫 번째 파일을 준비한다 ************* */
HANDLE GetWinFirstFile()
{
 wsprintf(szBuffer, “Browsing for the first file in the current local directory.”);ToConsole();
 hWinFile = FindFirstFile (FilePattern, &finddata);
 return hWinFile;
}     
/************** FTP 세션을 위해 스레드를 따로 마련해 둔다 **************/
DWORD WINAPI ftpThread(LPVOID lpParam)

 BOOL  bSuccess;
 TCHAR  prevFileName[80]=”test”;
 BOOL  endOfFileList;
 /* FTP 다이얼로그 박스의 컨트롤 핸들을 준비한다 */
 hCheckBox     = GetDlgItem  (gdlghwnd, IDC_CHECK1);
 hRadioButton1 = GetDlgItem(gdlghwnd,IDC_RADIO1);
 hRadioButton2 = GetDlgItem(gdlghwnd,IDC_RADIO2);
 wsprintf(szBuffer, “인터넷을 열기 위한 준비를 한다...”);
  ToConsole(); 
 hInternet = InternetOpen(szTitle, INTERNET_OPEN_TYPE_
  PRECONFIG, NULL,NULL, INTERNET_FLAG_ASYNC);
 // 인터넷 준비가 실패하면 열린 핸들을 닫고 스레드를 종료한다
 if(hInternet == NULL)
 {
  wsprintf(szBuffer, “Error (%i) InternetOpen. 
     Exiting Program ...”, GetLastError());
     ToConsole();
  CloseAll();
  ExitThread(0);
  return 0;
 }
 wsprintf(szBuffer, “done.”);ToConsole();
 
 wsprintf(szBuffer, “Trying Ftp Open ...”); ToConsole();
 // 인터넷의 FTP 연결을 시도한다
 hFtp=InternetConnect(hInternet, FtpServerAddr, FtpPort,
     UserName, Password, TERNET_SERVICE_FTP, 0, 0);
 /* FTP 연결이 실패하면 기존의 윈도우 핸들들을 닫고 스레드를 종결한다 */
    if(hFtp == NULL)
 {
  wsprintf(szBuffer, “Error (%i)
     InternetConnection for FTP. Exiting ...”,
     GetLastError()); ToConsole();
  CloseAll();
  ExitThread(0);
  return 0;
 }
 /* 연결이 성공하면 메시지를 윈도우 화면으로 출력한다 */
 wsprintf(szBuffer, “Success: Ftp Sessions
    Open.”);ToConsole();
 // 1초 동안 쉰다.
 Sleep(1000);

 /* FTP 다이얼로그 박스에서 꼭 필요한 컨트롤이 체크되었는지 확인하고 그 상태를
 기억한다. Radio 버튼은 둘 중 하나는 반드시 선택하여야 하므로 whileloop을
 통해 사용자가 체크할 때까지 기다린다 */
 do
 {
  iRadioButton1 = SendMessage (hRadioButton1,
     BM_GETCHECK, 0, 0);
  iRadioButton2 = SendMessage (hRadioButton2,
     BM_GETCHECK, 0, 0);

  if ( (iRadioButton1 || iRadioButton2)
     == BST_CHECKED)
  {
   break;
  }
  else
  {
   wsprintf(szBuffer, “GET 또는 PUT 중
   하나를 꼭 선택하셔야 합니다.”);ToConsole();
   Sleep(1000);
  }
 } while(1);

 iCheckBox = SendMessage (hCheckBox, BM_GETCHECK, 0, 0);

 /* 사용자가 GET을 선택하면 원격 FTP 서버에서 파일을 가져온다 */
 if(iRadioButton1==BST_CHECKED)
 {
  hFile = GetFirstFile(hFtp);
  if(hFile == NULL)
  {
   CloseAll();
   ExitThread(0);
   return 0;
  }
 }
 /* PUT이 선택되었으면 지역 디렉토리의 파일을 원격 FTP 서버로 올린다 */
 else if(iRadioButton2==BST_CHECKED)
 {
  hWinFile = GetWinFirstFile();
  if(hWinFile == NULL)
  {
   CloseAll();
   ExitThread(0);
   return 0;  
  }
 }
 
 do
 {  /* GET을 선택했을 때, 다음 파일을 원격 FTP 서버에서 가져온다 */
  if(iRadioButton1)
  {
   wsprintf(szBuffer, “Getting a next file ...”);
      ToConsole();
   bSuccess = FtpGetFile (hFtp, finddata.cFileName,
    finddata.cFileName, FALSE, FILE_ATTRIBUTE_NORMAL,
    FTP_TRANSFER_TYPE_BINARY, 0);
   if(bSuccess)
   {
    wsprintf(szBuffer, “Done “);ToConsole();
   }
   else
   {
    wsprintf(szBuffer, “Failed “);ToConsole();
   }
   Sleep(1000);
  /* 만약 파일 지움 박스에 체크가 되었으면 지금 가져온 파일을 즉시 지워준다 */
   if(iCheckBox && bSuccess)
   {
    wsprintf(szBuffer, “Deleting file (%s) at the local
     drive.”, finddata.cFileName); ToConsole();
    DeleteFile(finddata.cFileName);
   }
  }
  /* PUT을 선택하였으면 원격 FTP 서버에서 다음 파일을 가져온다 */
  else if(iRadioButton2)
  {
    wsprintf(szBuffer, “Putting %s “, finddata.cFileName);
    ToConsole();
   bSuccess = FtpPutFile(hFtp, finddata.cFileName,
    finddata.cFileName, FTP_TRANSFER_TYPE_BINARY, 0);
   if(bSuccess)
   {
    wsprintf(szBuffer, “Done “);ToConsole();
   }
   else
   {
    wsprintf(szBuffer, “Failed “);ToConsole();
   }

   Sleep(1000);
   /* 다음 파일의 복사가 성공적으로 끝나고 파일 지움 박스가 체크되었으면
      지금 복사한 파일을 지워준다 */
   if(iCheckBox && bSuccess)
   {
    wsprintf(szBuffer, “Deleting file (%s) at %s.”,
       finddata.cFileName, FtpServerAddr); ToConsole();
    FtpDeleteFile(hFtp, finddata.cFileName);
   }
  }

  Sleep(1000);

  /* 더 이상 가져올 파일이 없으면 메시지를 화면으로 출력한다 */
  if( lstrcmp(prevFileName, finddata.cFileName) ==0 )
  {
   wsprintf(szBuffer, “End of File List”); ToConsole();
   endOfFileList=TRUE;;
  }
  
  lstrcpy(prevFileName, finddata.cFileName);
  Sleep(1000);

  /* 모든 파일의 복사가 끝나면 열었던 핸들들을 닫아준다. */
  if(endOfFileList==TRUE)
  {
   endOfFileList=FALSE;
   
   if(iRadioButton1)
   {
    InternetCloseHandle(hFile);
    hFile = GetFirstFile(hFtp);
    if(hFile == NULL)
    {
     wsprintf(szBuffer, “Fail: Getting the first file”);
    }
   }
   else if(iRadioButton2)
   {
    CloseHandle(hWinFile);
    hWinFile = GetWinFirstFile();
    if(hWinFile == NULL)
    {
    wsprintf(szBuffer, “Fail: Getting the first file”);
    }
   }

   Sleep(1000);
   wsprintf(szBuffer, “One Cycle has been completed.”);
    ToConsole();
  }

  else
  {
   if(iRadioButton1)
   {
    InternetFindNextFile (hFile, &finddata);
   }
   else if(iRadioButton2)
   {
    FindNextFile(hWinFile, &finddata);
   }
  }

  Sleep(1000);

 } while(1);
 return 0;
}

/* ******* About 다이얼로그 메시지 엔진 ******* */

BOOL CALLBACK aboutProc (HWND hwnd, UINT message, WPARAM wParam,
 LPARAM lParam)
{
 switch(message)
 {
 case WM_INITDIALOG:
  return TRUE;
 case WM_COMMAND:
  switch(LOWORD(wParam))
  {
  case IDOK:
   EndDialog(hwnd, 0);
   return TRUE;
  }
 }
 return FALSE;
}

/* ******* FTP 다이얼로그 메시지 엔진 ******* */

BOOL CALLBACK dlgProc (HWND hwnd, UINT message, WPARAM wParam,
 LPARAM lParam)
{
 TCHAR * param = “this is a test”;
 DWORD dwCreationFlags=0;
 DWORD dwThreadId;
 HANDLE hThread;
 TCHAR * temp = “test”;

 gdlghwnd = hwnd;

 switch(message)
 {
 /* 다이얼로그 박스가 열리기 직전에 필요한 초기화 작업을 한다 */
 case WM_INITDIALOG:
   SetDlgItemText(hwnd, IDC_ADDR, FtpServerAddr);
   SetDlgItemText(hwnd, IDC_USER, UserName);
      SetDlgItemText(hwnd, IDC_PASS, Password);
   SetDlgItemText(hwnd, IDC_PATTERN, FilePattern);
   SetDlgItemText(hwnd, IDC_PORT, “21”);
  return TRUE;


 case WM_COMMAND:
  switch(LOWORD (wParam))
  {
  case IDC_CLOSE:
   EndDialog(hwnd, 0);
   return TRUE;

  /* Run 버튼을 눌렀을 때 FTP 다이얼로그 박스에 입력된 데이터를 모아 FTP
    세션을 시행한다 */
  case IDC_RUN:
   GetDlgItemText(hwnd, IDC_ADDR, FtpServerAddr,
    lstrlen(FtpServerAddr)+1);
   GetDlgItemText(hwnd, IDC_USER, UserName,
    lstrlen(UserName)+1);
      GetDlgItemText(hwnd, IDC_PASS, Password, lstrlen
   (Password)+1);
  GetDlgItemText(hwnd, IDC_PATTERN, FilePattern, 
   lstrlen(FilePattern)+1);
  GetDlgItemText(hwnd, IDC_PORT, temp, lstrlen(temp)+1);
   FtpPort = atoi(temp);

   if(hInternet)
   {
    CloseAll(); 
   }
   /* FTP 세션을 위한 스레드를 하나 따로 생성해 준다 */
   hThread = CreateThread(NULL,0, ftpThread, (LPVOID)
    param, 0, &dwThreadId );
   CloseHandle (hThread);

   return TRUE;
  }
 }
 return FALSE;
}

/* ******* Message Engine for Main Window ******* */

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,
 LPARAM lParam)
{
 static int   cyClient;
 static int   cyChar;
 static int   cLines = 0;
 static BOOL  isNewLine;
    static RECT  rectAll;
    HDC          hdc ;
    PAINTSTRUCT  ps ;
    TEXTMETRIC   tm ;
 ghwnd = hwnd;

 switch (message)
    {
 
 case WM_COMMAND:

  switch(LOWORD (wParam))
  {
  case IDC_QUIT:
   CloseAll();
   SendMessage(hwnd, WM_DESTROY, wParam, lParam);
   return 0;
  /* Run 메뉴를 선택했을 때 다이얼로그 박스를 화면으로 출력해 준다 */
  case IDC_RUNPROGRAM:
   DialogBox(ghInst, MAKEINTRESOURCE(IDD_DIALOG1), hwnd,
    dlgProc);
   InvalidateRect(hwnd, NULL, FALSE);
   return 0;

  case IDC_ABOUT:
   DialogBox(ghInst, MAKEINTRESOURCE(IDD_DIALOG2), hwnd,
    aboutProc);
   return 0;
  }

     case WM_PAINT:

  GetClientRect(hwnd,&rectAll);
  cyClient = rectAll.bottom;
  rectScroll = rectAll;

  rectScroll.top = rectScroll.top+(cyChar*cLines);
  hdc = BeginPaint (hwnd, &ps) ;
        SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
        SetBkMode (hdc, TRANSPARENT) ;

  GetTextMetrics (hdc, &tm) ;
  cyChar = tm.tmHeight ;
  TextOut (hdc, 0, cyChar*cLines, szBuffer, lstrlen
   (szBuffer)) ;
  ZeroMemory(szBuffer, lstrlen(szBuffer));

  cLines++;
  EndPaint (hwnd, &ps) ;

  if(cyChar*cLines+cyChar > cyClient)
  {
   isNewLine=TRUE;
   cLines = 0;
  }
  if(isNewLine)
  {
   isNewLine=FALSE;
   rectScroll.top = 0;
   InvalidateRect(hwnd, &rectScroll, TRUE);
  }

        return 0 ;

     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

 

'Windows' 카테고리의 다른 글

Windows 시스템레벨 개발자에게 도움이 되는 사이트  (0) 2006.11.10
DllMain  (0) 2006.11.09
WMI 스크립팅 입문: 3부 (2003년 3월 13일)  (0) 2005.12.20