앨리스의 토끼

javahawk.egloos.com

포토로그 방명록


최근 포토로그


GetProcAddress - DLL에서 제공하는 함수를 사용하자 by Kircheis

프로그램을 시작하고 조금 맛을 보다보면 DLL을 사용하게 될 경우를 참 많이 만나게 된다.
기본적으로 자신이 직접 모든 것을 구현해 낸다면 좋을 지 모르지만, 요즘같이 화려한 UI를 제공하며 복잡한 알고리즘을 가진 프로세스를 만들다보면 한계를 느끼게 될 수 밖에 없다.
그럴 때면 누군가 내가 필요로 하는 이 기능을 만들어 두지 않았을까 생각하게 되고 여기 저기 웹서핑을 하면서 이것저것 많은 정보를 찾을 것이다.
뭐 그러면 그런 대로 그런 과정을 통해서 개발자는 자신이 알지 못하던 것을 배우게 되고, 자신의 활동 분야를 더욱더 넓힐 수 있으니 굳이 반대할 이유는 없다.
간단한 기능의 경우는 정말 쉽게 함수 한두개를 만들어서 소스를 오픈해 둔 것이라도 찾으면 정말 쉽게 구현이 될 것이지만, 복잡한 기능은 그것이 쉽지 않다.
얘를 들어 윈도우에서 지원하는 현재 콘솔의 세션을 구하는 방법이나, 로그인 사용자의 토큰을 구하는 등의 계정 친화적인 기능이 필요한 경우에는 윈도우에서 제공하는 함수를 쓰지 않을 수가 없다.
물론 직접 구현해도 된다. 그러기 위해서는 요구하는 기능이 내부적으로 수행하는 기능부터 시작해서 OS에서 관리하는 사용자 관리의 내용을 뿌리까지 이해하면서 만들면 되는 일이다.
하지만, 귀찮다.
시간이 없다.
내가 왜 그 간단할 것 같은 기능을 하나 사용하려고, 윈도우의 뿌리를 이해하고 들춰내야 하는가?
OS라면 OS에서 기본적으로 그런 것들을 지원해야 하는 것 아닌가?
당연하다.
OS에서 지원한다.
하지만, 이것을 지원하기 위해 윈도우가 원시 코드를 모두 제공할 필요는 없다.
그것은 MS같은 OS 개발업자에게는 그다지 좋은 생각이 되지 못한다.
그렇다면 방법이 없나?
왜 그래?
이미 알고 있으면서...
그네들이 만든 함수를 제공하는 DLL을 가져다 사용하면 된다.
물론 그런 함수를 제공하는 것이 DLL 만은 아니다.
실행파일에서도 함수를 제공할 수 있다.
뭐 실행파일이면서 함수를 제공한다고 하면, 기본적으로 서비스를 예로 들 수 있다.
서비스라고 하면 분명 EXE 파일이지만, 제공하는 기능은 분명 DLL이 포함되어 있다.
물론 VS에서 기본적으로 만드는 서비스가 제공하는 기능은 일반의 Win32 DLL이 제공하는 기능이 아니라 ATL COM과 같은 Object 개념으로 제공하는 것이라 사용하는 방법에 분명 차이가 있다.
그렇다면 일단은 가장 기본이 되는 Win32 API를 사용하는 방법을 알아보자.
그중에서도 오늘은 특별한 함수를 사용하여 함수의 포인터를 얻어오는 방법을 사용해 보고자 한다.

FARPROC GetProcAddress(
    HMODULE hModule,
    LPCSTR lpProcName
);

이 함수의 반환값을 보면 FARPROC으로 되어 있다. 이 FARPROC으로 반환되는 값이 함수의 포인터이다.
왜 이런 함수의 포인터를 받는 것일까?
간단하다.
그 DLL이 없거나, 그 DLL에서 해당하는 기능을 제공하지 않을 경우가 있기 때문이다.
귀찮다.
몇명이나 본다고...
길게 쓰기 싫으니까 더 이상의 설명은 줄이고 사용법이다.
아... 역시 게으른 종족은 이런 것도 힘들어...

hModule에는 메모리에 로드된 DLL에 대한 핸들을 준다.
물론 DLL이 아니라 실행 파일일 수도 있다.
여기서 핸들을 얻기 위한 방법은 대개 LoadLibrary 매크로를 사용한다.
LoadLibrary 매크로는 유니코드 여부에 따라 LoadLibraryW나 LoadLibraryA 함수로 치환된다.
LoadLibraryA는 LPCSTR형태의 lpLibFileName이라는 하나의 인자를 받는다.
이 인자에 DLL 파일의 파일명을 주면, LoadLibraryA는 HMODULE 형으로 핸들값을 반환한다.
이 핸들값을 GetProcAddress 함수의 첫번째 인자로 주면된다.
다음은 winbase.h 파일에 있는 LoadLibraryA와 LoadLibraryW 함수의 선언부이다.

WINBASEAPI
__out
HMODULE
WINAPI
LoadLibraryA(
    __in LPCSTR lpLibFileName
    );
WINBASEAPI
__out
HMODULE
WINAPI
LoadLibraryW(
    __in LPCWSTR lpLibFileName
    );

다음은 LoadLibrary 매크로의 정의이다.

#ifdef UNICODE
#define LoadLibrary  LoadLibraryW
#else
#define LoadLibrary  LoadLibraryA
#endif // !UNICODE

그러면 우선 Kernel.dll 파일의 핸들을 얻어보자.

HMODULE hKernel = LoadLibrary( "Kernel32.dll" );

사용법은 의외로 간단하다.

다음으로 살펴 볼 것은 LPCSTR 형으로 지정된 lpProcName 변수이다.
이 변수는 type으로도 알 수 있듯이 함수의 이름을 나타내는 문자열이다.

이제 중요한 것은 반환받을 함수 포인터이다.
함수라는 것이 워낙 다양한 형태로 만들어지기 때문에, 적어도 우리는 우리가 호출할 함수의 기본형에 대해서는 알고 있어야 한다.
여기서는 다음의 함수를 예로 들어보자.

DWORD WTSGetActiveConsoleSessionId(void);

이 함수는 인자를 받지 않고 호출하여 DWORD값을 반환한다.
이런 경우의 함수 포인터 사용을 위한 기본형 선언부는 다음과 같이 선언할 수 있다.

typedef DWORD ( WINAPI * WTSGetActiveConsoleSessionIdFunc )();

이것은 인자를 받지 않고 결과값은 DWORD를 반환하는 함수포인터의 기본형을 선언한 것이다.
이제 GetProcAddress를 호출하여 이 선언부에 중요한 함수 포인터를 받아보자.

WTSGetActiveConsoleSessionIdFunc WTSGetActiveConsoleSessionId = ( WTSGetActiveConsoleSessionIdFunc )GetProcAddress( hKernel, "WTSGetActiveConsoleSessionId" );

이것으로 이제 우리는 WTSGetActiveConsoleSessionId 함수를 사용할 수 있게 되었다.

DWORD dwRet = WTSGetActiveConsoleSessionId();

물론 이런 함수를 가져올 때 인자를 1개 이상 사용하는 함수에 대해서도 대동소이한 방법으로 사용이 가능하다.

Wtsapi32.dll 파일이 제공하는 WTSQueryUserToken 함수를 예로 들어보자.

HMODULE hWtsapi = LoadLibrary( "Wtsapi32.dll" );
typedef BOOL ( WINAPI * WTSQueryUserTokenFunc )( ULONG SessionId, PHANDLE phToken );
WTSQueryUserTokenFunc WTSQueryUserToken = ( WTSQueryUserTokenFunc )GetProcAddress( hWtsapi, "WTSQueryUserTokenFunc" );




메모장