막상 Windows 라이브러리를 만드려면 어떻게 해야 하는지 기억이 잘 안 나서 정리를 한번 해보려 합니다.
Visual Studio 를 열고 DLL 프로젝트 하나 만들어 줍니다. DllTest라 명명하겠습니다.
구성 형식이 동적 라이브러리로 잘 되어 있는지 확인합니다. exe나 정적 라이브러리 (lib)으로 되어 있으면 dll로 바꿔 줍니다.
아래 와 같은 dll 진입시 처리 하는 파일이 생성되어 있습니다. 없으면 #include <windows.h> 하여 하나 만들어 줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include "pch.h" BOOL APIENTRY DllMain( HMODULE 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: break; } return TRUE; } |
그리고 이제 dll 에서 참조할 API를 정의해 줘야 하는데 코드 예제는 아래 링크를 참조하였습니다.
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 32 33 |
#pragma once #ifdef MATHLIBRARY_EXPORTS #define MATHLIBRARY_API __declspec(dllexport) #else #define MATHLIBRARY_API __declspec(dllimport) #endif // The Fibonacci recurrence relation describes a sequence F // where F(n) is { n = 0, a // { n = 1, b // { n > 1, F(n-2) + F(n-1) // for some initial integral values a and b. // If the sequence is initialized F(0) = 1, F(1) = 1, // then this relation produces the well-known Fibonacci // sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, ... // Initialize a Fibonacci relation sequence // such that F(0) = a, F(1) = b. // This function must be called before any other function. extern "C" MATHLIBRARY_API void fibonacci_init( const unsigned long long a, const unsigned long long b); // Produce the next value in the sequence. // Returns true on success and updates current value and index; // false on overflow, leaves current value and index unchanged. extern "C" MATHLIBRARY_API bool fibonacci_next(); // Get the current value in the sequence. extern "C" MATHLIBRARY_API unsigned long long fibonacci_current(); // Get the position of the current value in the sequence. extern "C" MATHLIBRARY_API unsigned fibonacci_index(); |
fibo.h와 fibo.cpp 파일을 만들고 API를 선언 및 구현해 줍니다.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
// fibo.cpp : Defines the exported functions for the DLL. #include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier #include <utility> #include <limits.h> #include "fibo.h" // DLL internal state variables: static unsigned long long previous_; // Previous value, if any static unsigned long long current_; // Current sequence value static unsigned index_; // Current seq. position // Initialize a Fibonacci relation sequence // such that F(0) = a, F(1) = b. // This function must be called before any other function. void fibonacci_init( const unsigned long long a, const unsigned long long b) { index_ = 0; current_ = a; previous_ = b; // see special case when initialized } // Produce the next value in the sequence. // Returns true on success, false on overflow. bool fibonacci_next() { // check to see if we'd overflow result or position if ((ULLONG_MAX - previous_ < current_) || (UINT_MAX == index_)) { return false; } // Special case when index == 0, just return b value if (index_ > 0) { // otherwise, calculate next sequence value previous_ += current_; } std::swap(current_, previous_); ++index_; return true; } // Get the current value in the sequence. unsigned long long fibonacci_current() { return current_; } // Get the current index position in the sequence. unsigned fibonacci_index() { return index_; } |
이렇게 코딩하고 컴파일 하면 dll, lib 파일이 생성되는데 그전에
MATHLIBRARY_EXPORTS을 속성 -> C / C++ -> 전처리기 -> 전처리 정의에 넣어 줍니다.
DllTest 프로젝트는 말하자면 Server 역할이므로 dllexport 해주고
아래 만들 DllTestConsole은 Client 역할을 하므로 dllimport를 해 줍니다.
dll을 배포할 때 보통 header도 같이 배포 하는데
Client에서 MATHLIBRARY_EXPORTS 전처리 정의를 안 해주면 자동 dllimport 됩니다.
dll을 만들었으니 직접 테스트 해볼 DllTestConsole 프로젝트도 만들어 봅시다.
이번엔 콘솔 앱 하나를 추가 합니다. DllTestConsole이라 명명하였습니다.
그러면 위와 같이 DllTest 솔루션에 2개의 프로젝트가 생성됩니다.
dll을 암시적으로 import 하려면 DllTestConsole 속성에서 몇가지를 설정해 줘야 합니다.
속성 -> C/C++ -> 일반에서 추가 포함 디렉토리에 dll header를 넣어줘야 합니다. 여기서는 fibo.h
그 다음에 속성 -> 링커 -> 일반 -> 추가 라이브러리 디렉터리에서 DllTest.lib이 있는 디렉터리를 포함시켜줍니다.
마지막으로, 속성 -> 링커 -> 입력 -> 추가 종속성에 DllTest.lib 을 명시해줍니다.
그리고 아래와 같이 암시적으로 API를 호출해 봅니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// #include "pch.h" Uncomment for Visual Studio 2017 and earlier #include <iostream> #include <windows.h> #include "fibo.h" int main() { // Implicit way: Need to include header, library. // Initialize a Fibonacci relation sequence. fibonacci_init(1, 1); // Write out the sequence values until overflow. do { std::cout << fibonacci_index() << ": " << fibonacci_current() << std::endl; } while (fibonacci_next()); // Report count of values written before overflow. std::cout << fibonacci_index() + 1 << " Fibonacci sequence values fit in an " << "unsigned 64-bit integer." << std::endl; return 0; } |
그리고 컴파일 하여 실행하면 fibonacci 수열이 출력되게 됩니다.
client에서 dll 을 호출 하는 방법에는 2가지가 있는데,
암시적 호출 (implicit): header, library를 미리 포함하여 호출하는 것인데 보통 이 방법이 많이 쓰입니다.
쓰기 편합니다. 대신 dll 파일은 exe에 둬야 합니다. 환경 변수에 추가하는 방법도 있으나 같은 이름이 있을 경우 에러가 날 수 있습니다.
명시적 호출 (explicit): header, library 없이 dll 자체만을 호출하는 것인데 API를 미리 알고 있어야 합니다.
쓰기 어려운 대신 dll을 원하는 위치에 넣고 호출 할 수 있습니다.
참고로, 암시적으로 호출 하였을 경우 dll 로딩 시 검색 순서입니다.
1. DLL을 호출한 EXE파일이 있는 디렉토리
2. 프로세스의 현재 디렉토리
3. 윈도우 시스템 디렉토리
4. 윈도우 디렉토리
5. PATH 환경 변수에 저장된 디렉토리
참조: https://ideastreet.tistory.com/entry/DLL-로딩-시-검색-순서
만약 위의 DllTestConsole.cpp를 명시적으로 호출 한다면 다음과 같이 호출 할 수 있습니다.
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 32 33 34 35 36 37 38 |
// #include "pch.h" Uncomment for Visual Studio 2017 and earlier #include <iostream> #include <windows.h> #include "fibo.h" typedef void (*fibonacci_init_ptr)(const unsigned long long, const unsigned long long); typedef bool (*fibonacci_next_ptr)(); typedef unsigned long long (*fibonacci_current_ptr)(); typedef unsigned (*fibonacci_index_ptr)(); int main() { // Explicit way: No need to include header, library. Just dll. And you can locate it in anywhere you want. #ifdef _DEBUG HMODULE h = LoadLibrary(L"./Test/DllTest.dll"); #else HMODULE h = LoadLibrary(L"./Test/DllTest.dll"); #endif if (h != NULL) { fibonacci_init_ptr fip = (fibonacci_init_ptr)::GetProcAddress(h, "fibonacci_init"); fibonacci_next_ptr fnp = (fibonacci_next_ptr)::GetProcAddress(h, "fibonacci_next"); fibonacci_current_ptr fcp = (fibonacci_current_ptr)::GetProcAddress(h, "fibonacci_current"); fibonacci_index_ptr fidxp = (fibonacci_index_ptr)::GetProcAddress(h, "fibonacci_index"); if (fip == NULL || fnp == NULL || fcp == NULL || fidxp == NULL) return 0; // Initialize a Fibonacci relation sequence. fip(1, 1); // Write out the sequence values until overflow. do { std::cout << fidxp() << ": " << fcp() << std::endl; } while (fnp()); // Report count of values written before overflow. std::cout << fidxp() + 1 << " Fibonacci sequence values fit in an " << "unsigned 64-bit integer." << std::endl; ::FreeLibrary(h); } return 0; } |
LoadLibrary로 불러와서 GetProcAddress 함수로 API를 호출하여야 합니다. 이 때 API는 미리 선언하여야 합니다.
보통 암시적 호출이 쓰기 편하기 때문에 명시적으로 쓸 일은 많지 않을 거라 생각 됩니다.