|
靜態載入 DLL
首先在 VC 選擇 New 一個 Project ,選擇用 MFC 方式建立,但是此方式所建立的 DLL 會看不見 DllMain,取而代之的是
繼承 CWinApp 的類別,這跟一般的 MFC 很像,原本的 DLL_PROCESS_ATTACH被 InitInstance()
取而代之。建立步驟如下
[圖 1] 我們選擇新增一個專案
[圖 2] 我們選擇建立 MFC 格式的 DLL 專案
[圖 3] 我們選擇共用 MFC DLL 的形式
[圖 4] 完成後我們可以看見,VC幫我們產生好的檔案
[圖 5] 完成後我們可以看見,VC幫我們產生好的類別
|
|
|
//
// 注意!
//
// 如果這個 DLL 是動態地對 MFC DLL 連結,那麼從這個 DLL 匯出的任何會呼叫
// MFC 內部的函式,都必須在函式一開頭加上 AFX_MANAGE_STATE 巨集。
//
// 例如:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // 此處為正常函式主體
// }
//
// 這個巨集一定要出現在每一個函式中,才能夠呼叫 MFC 的內部。
// 這意味著它必須顯示為函式內的第一個陳述式,甚至必須在
// 任何物件變數宣告的前面,因為它們的建構函式可能會產生對
// MFC DLL 內部的呼叫。
//
// 如需詳細資訊,請參閱 MFC 技術提示 33 和 58。
// |
|
|
|
建立完成後,我們可以在我們主要的 .cpp 檔案裡面發現,有這一段敘述,這一個巨集是用來推送模組的全域資料。因此在每一個輸出函式的開始,你必須設定目前模組狀態為
DLL,別擔心重設模組狀態,因為 AFX_MANAGE_STATE 巨集實際上會在當物件過完生命週期時,重設為前一個狀態。 |
因為 DLL 是給別人用的,所以我們最好另外建立一個 .h 的宣告檔案,將一些共用的東西存在那裡,至於要做哪些宣告,這也是很重要的,不要將別人用不到的也宣告進來,只需將要匯出的部分宣告出來即可。
我們增加一個匯出檔案定義宣告(假設為 DllCommon.h),定義內容如下:
|
|
|
// 下列 ifdef 區塊是建立巨集以協助從 DLL 匯出的標準方式。
// 這個 DLL 中的所有檔案都是使用命令列中所定義 DLLFUNC_EXPORTS 符號編譯的。
// 任何使用這個 DLL 的專案都不應定義這個符號。這樣的話,原始程式檔中包含這檔案的任何其他專案
// 會將 DLLFUNC_API 函式視為從 DLL 匯入的,而這個 DLL 則會將這個巨集定義的符號視為匯出的。
#ifdef DLLFUNC_EXPORTS
#define DLLFUNC_API __declspec(dllexport)
#else
#define DLLFUNC_API __declspec(dllimport)
#endif |
|
|
然後在我們 DLL 的欲匯出函式的宣告處加入下面的宣告,用來宣告成匯出模式,並載入匯出函式定義宣告,我們什麼時候要使用 dllexport
的敘述,什麼時候又要使用 dllimport 的敘述呢?在 DLL 裡面,我們要將我們的函式匯出去,給其他應用程式使用,所以我們必須在
DLL 內部宣告 DLLFUNC_EXPORTS ,如此編譯器才知道我們要使用 dllexport 的敘述。
|
|
|
#define DLLFUNC_EXPORTS
#include "DllCommon.h" |
|
|
使用 "#define DLLFUNC_EXPORTS" 是一種方法,但是如果我們定義一新的 Class
我們希望這一個 Class 能夠匯出,並且在 DLL 內部宣告視為 __declspec(dllexport) ,而應用程式端則會視為 __declspec(dllimport),我們有比較好的方法,我們改用前置處理器的宣告方式如下圖,我們把
DLL 的屬性頁叫出來,在前置處理器定義裡宣告 DLLFUNC_EXPORTS,我想這樣比較方便,因為我們可能有許多地方要匯出我們的
Class 或 Function。
[圖 6] 設定我們 DLL 的屬性頁
|
匯出類別的宣告(.h) |
|
#pragma once
#include "DllCommon.h"
class DLLFUNC_API SampleClass
{
public:
SampleClass(void);
~SampleClass(void);
int GetVersion(void);
}; |
|
|
|
匯出類別的程式碼(.cpp) |
|
#include "StdAfx.h"
#include "sampleclass.h"
SampleClass::SampleClass(void)
{
}
SampleClass::~SampleClass(void)
{
}
int SampleClass::GetVersion(void)
{
return 0x5678;
} |
同理,我們要匯出我們的變數或者一個函是我們可以這樣做,這樣做的另一個好處就是不必為每一個 .cpp 檔案去定義 DLLFUNC_EXPORTS
,而且標頭檔又可以共用而不會有問題出現。下面就是以函式與變數的範例。
|
匯出檔案定義宣告(假設為 DllFunc.h),定義內容如下: |
|
// 下列 ifdef 區塊是建立巨集以協助從 DLL 匯出的標準方式。
// 這個 DLL 中的所有檔案都是使用命令列中所定義 DLLFUNC_EXPORTS 符號編譯的。
// 任何使用這個 DLL 的專案都不應定義這個符號。這樣的話,原始程式檔中包含這檔案的任何其他專案
// 會將 DLLFUNC_API 函式視為從 DLL 匯入的,而這個 DLL 則會將這個巨集定義的符號視為匯出的。
#ifdef DLLFUNC_EXPORTS
#define DLLFUNC_API __declspec(dllexport)
#else
#define DLLFUNC_API __declspec(dllimport)
#endif
extern DLLFUNC_API int iDllVersion; //宣告匯出變數
DLLFUNC_API int fnDllFunction(void); //宣告匯出函數 |
|
|
|
然後我們撰寫我們的程式(.cpp) |
|
DLLFUNC_API int iDllVersion
= 0;
// 這是匯出之函式的範例。
DLLFUNC_API int fnDllFunction(void)
{
return 0x1234;
} |
現在我們使用靜態連接方式來使用我們的 DLL ,首先我們建立一個應用程式,並將我們的產生出來的 .lib 加入,並在我們應用處載入我們
DLL 的匯出定義檔(.h) ,應用範例如下:
|
|
|
#include "../include/SampleClass.h"
#include <DllCommon.h>
...
void CDllTestApp::OnTestDLL02()
{
// TODO: 在此加入您的命令處理常式程式碼
SampleClass Sample;
int abc=0;
abc = Sample.GetVersion(); //
使用 DLL 裡的 Class
abc = iDllVersion; // 使用
DLL 裡的變數
abc = fnDllFunction(); // 使用 DLL
裡的函數
} |
|
程式參考 StaticLink.rar |
如果我們要混和使用 C 與 C++ 來撰寫模組時,我們可以使用 extern "C" 修飾字來修飾要輸出的
C++ 符號,而且如果我們輸出 C++ 類別的話,我們的應用程式端也要使用相同的編譯器。縱使我們使用純粹的 C 語言來撰寫程式碼,也會因為不同的
C 編譯器會產生不同的符號,Microsoft 的 C 編譯器也一樣會修飾 C 函式的符號。當我們使用 __stdcall
(WINAPI) 的呼叫協定來修飾我們的 C 函式時,就會發生這一個問題,不幸的是這一個是我們最常使用的呼叫協定型式。(詳見Windows
程式設計開發指南 P820)
如果我的的程式要給 C 和 C++ 使用的話,我們宣告方式可以加上 extern "C" 語法來修飾,片段程式如下:
|
|
|
#ifdef __cplusplud
extern "C"
{
#endif // __cplusplud
其它定義...
extern DLLFUNC_API int iDllVersion;
DLLFUNC_API int fnDllFunction(void);
#ifdef __cplusplud
}
#endif // __cplusplud
|
|
|
|
接下來看看我們DLL的程式碼(.cpp) |
|
...
DLLFUNC_API int fnDllFunction(void)
{
return 0x1234;
}
... |
動態載入 DLL
當我們的 DLL 寫好以後,我們要怎麼載入我們的 DLL 呢?使用動態載入的方式,我們可以不必管我們匯入什麼函式,也就是說我們可以不必理會
DLL 端宣告的匯入匯出動作,當然 DLL 端必須有匯出的動作,只是我們應用端改變載入方式而已,他的應用大致如下:
1. |
LoadLibrary |
|
HINSTANCE hinstDll = LoadLibrary("DllTemplate.dll");
將指定的行程載入到行程空間,如果成功的話,將會傳回被映射的可執行模組之 Handle |
2. |
GetProcAddress |
|
FARPROC pfn = GetProcAddress(hDll,"fnDllFunction");
GetProcAddress 可接受字串與序號,但是不建議使用序號方式,至於字串有一個要注意的就是,他只支援
ANSI 字串,不能傳送 Unicode 字串給他,如果成功的話,將會傳回所指定的匯出函式的位置 |
3. |
利用傳回的位置做我們的應用 |
|
int a;
if (pfn != NULL)
a = pfn(); |
4. |
FreeLibrary |
|
FreeLibrary(hDll);
當我們不再使用 DLL 的時候,記得使用 FreeLibrary 來釋放 DLL。 |
|
呼叫端片段程式如下: |
|
void CDllTestApp::OnTestDLL01()
{
// TODO: 在此加入您的命令處理常式程式碼
HINSTANCE hDll = NULL;
hDll = LoadLibrary("MFC_DLL.dll");
if (hDll != NULL)
{
int a=0;
FARPROC pfn = GetProcAddress(hDll,"fnMFC_DLL");
if (pfn != NULL)
a = pfn();
FreeLibrary(hDll);
}
} |
|
|
如果我們直接使用的話,我們可以發現,我們無法找到我們的 function entery ,這一個問題主要就是前面所說的修飾問題,要解決這一個問題有兩種方法,一種是更改
.def 來將函式匯出,另一種則是在宣告時我們使用 extern "C" 修飾字來修飾我們的匯出宣告,片段程式如下:
1. |
1.使用修改匯出定義檔(XXX.def),若使用了 DEF 檔案的話,就不需要使用到 extern "C"
了。另外,除了可以藉此控制輸出名稱外,也可以控制序數,不過相對的也要對 DEF 檔案進行維護。匯出的函式,只要單純指定
__stdcall 就可,_declspec(dllexport) 與 extern "C"
語法都不需要。 |
|
內容如下: |
|
; MFC_DLL.def : 宣告 DLL 的模組參數。
LIBRARY "DllTemplate"
EXPORTS
fnDllFunction
; ...
|
|
|
2. |
使用修飾字來修飾我們的匯出宣告的標頭檔(.h) |
|
#ifdef DLLFUNC_EXPORTS
#define DLLFUNC_API __declspec(dllexport)
#else
#define DLLFUNC_API __declspec(dllimport)
#endif
//==================================
#ifdef __cplusplud
extern "C"
{
#endif // __cplusplud
extern DLLFUNC_API int
iDllVersion;
extern "C" DLLFUNC_API
int fnDllTemplate(void); // extern
"C" 可直接應用在單行敘述上
// 其實在此可以不必宣告,因為我們頭尾早已
// 包含在 extern "C" {
... } 敘述裡
#ifdef __cplusplud
}
#endif // __cplusplud
|
|
此種方式好像還是會有修飾問題,無法解決 |
重新編譯我們的 DLL ,我們就完成了我們的 DLL 動態載入的應用了,但是如果我們的函式有參數要傳遞,那麼我們要怎麼樣去應用呢,這不再只是將我們抓到的指標直接拿來應用了,我們還必須透過一個轉型的動作,將指標轉成我們函數宣告型態,這樣我們才可以正確的將參數帶入,呼叫端範例如下:
|
DLL 的標頭檔(.h) |
|
// 下列 ifdef 區塊是建立巨集以協助從 DLL 匯出的標準方式。
// 這個 DLL 中的所有檔案都是使用命令列中所定義 DLLFUNC_EXPORTS 符號編譯的。
// 任何使用這個 DLL 的專案都不應定義這個符號。這樣的話,原始程式檔中包含這檔案的任何其他專案
// 會將 DLLFUNC_API 函式視為從 DLL 匯入的,而這個 DLL 則會將這個巨集定義的符號視為匯出的。
#ifdef DLLFUNC_EXPORTS
#define DLLFUNC_API __declspec(dllexport)
#else
#define DLLFUNC_API __declspec(dllimport)
#endif
//==================================
#ifdef __cplusplud
extern "C"
{
#endif //
__cplusplud
extern DLLFUNC_API int iDllVersion;
extern "C" DLLFUNC_API int
fnDllFunction(int iValue);
#ifdef __cplusplud
}
#endif //
__cplusplud |
|
|
|
呼叫端的片段應用程式(.cpp) |
|
void CDllTestApp::OnTestDLL01()
{
// TODO: 在此加入您的命令處理常式程式碼
HINSTANCE hDll = NULL;
hDll = LoadLibrary("MFC_DLL.dll");
int ( _cdecl *fnDllTemplate) (int iValue);
if (hDll != NULL)
{
int a=0;
FARPROC pfn = GetProcAddress(hDll,"fnDllFunction");
if (pfn != NULL)
{
fnMFC_DLL = (int ( _cdecl *)(int)) pfn ;
a = (fnDllFunction(123));
}
FreeLibrary(hDll);
}
} |
從範例程式我們可以看見,我們先宣告一個我們的函數指標變數,然後等我們擷取到函式位址時,我們利用強迫轉型,將函式位址轉給我們的函數指標,接著我們就可以直接使用我們的函式了,並且可以傳入參數,當然我們呼叫端的定義名稱可以不必與
DLL 端的定義取相同的名稱,不過在這使用相同名稱,避免混淆。
|
extern "C" DLLFUNC_API int fnDllFunction(int
iValue); |
我們在 DLL 宣告的函數 |
|
int ( _cdecl *fnDllFunction) (int
iValue); |
我們在呼叫端的函數指標宣告,在這我們可以發現,我們有使用 _cdecl ,在實際應用上,我們未必宣告成這樣,也可以是 _stdcall 、_fastcall 這完全根據我們 DLL 的宣告而定,但是在上面我們明明沒有宣告,那到底在哪裡宣告了呢?我們打開我們 DLL
的屬性頁,如下圖,我們可以發現我們使用 __cdecl 為我們的預設值,在這的函是可以被我們的外面函式所覆寫
__stdcall 是使用在呼叫 Win32 API 與 DLL 行程的時候,Stack 會由被呼叫端來清除。若不使用
__stdcall 的話,在混合使用多種語言時程式將容易發生問題。 |
|
fnDllFunction = (int ( _cdecl *)(int)) pfn ; |
將我們抓到的位置強制轉型給我們的函數指標 |
|
|
|
當我們改用 __stdcall 方式時,由於 C++ 會修飾外部 Symbol ,因此取名的慣例不太一樣,這個慣例也會因為開發環境版本不一樣兒有所不同。而引起一些麻煩的問題。因此我們不採行
extern "C" 語法,而採用 C++ 修飾名詞的方法。
|
|
|
#ifdef __cplusplud
extern "C" {
#endif // __cplusplud
extern DLLFUNC_API int iDllVersion;
int DLLFUNC_API __stdcall fnDllFunction(int
iValue);
#ifdef __cplusplud
}
#endif // __cplusplud |
|
|
我們觀看其它程式的時候,我們也常看見非使用 __cdecl or __stdcall 等宣告的方式,如 WINAPI 、CALLBACK
...的宣告方式,我們可以從下面的定義檔發現,其實他是透過巨集宣告成不一樣的名子, 也就是說我們常使用 __stdcall
方式,只是換成我們比較習慣或熟悉的語法而已,詳細參考 windef.h,片段參考如下。
|
windef.h |
|
#ifdef _MAC
...
#elif (_MSC_VER >= 800)
|| defined(_STDCALL_SUPPORTED)
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#else
#define CALLBACK
#define WINAPI
#define WINAPIV
#define APIENTRY WINAPI
#define APIPRIVATE
#define PASCAL pascal
#endif |
|
|
[圖 7] VC 的慣例呼叫預設值
[圖 8] VC 的慣例呼叫的選項
但是我們所寫的 DLL 每次要自己去寫載入的 DLL 檔案與程式入口以及宣告這些,還要轉記住要怎麼轉型似乎有些麻煩,所以我們可以使用巨集宣告在標頭檔裡面,這樣介面比較統一與方便我們使用。
|
DLL Define.h |
|
// 下列 ifdef 區塊是建立巨集以協助從 DLL 匯出的標準方式。
// 這個 DLL 中的所有檔案都是使用命令列中所定義 DLLFUNC_EXPORTS 符號編譯的。
// 任何使用這個 DLL 的專案都不應定義這個符號。這樣的話,原始程式檔中包含這檔案的任何其他專案
// 會將 DLLFUNC_API 函式視為從 DLL 匯入的,而這個 DLL 則會將這個巨集定義的符號視為匯出的。
// 記得 DLL 要定義 DLLFUNC_EXPORTS
#pragma once
#ifdef DLLFUNC_EXPORTS
#define DLLFUNC_API __declspec(dllexport)
#else
#define DLLFUNC_API __declspec(dllimport)
#endif
//============================================================================
//--- For function ---
#define DLLFUNC(type) DLLFUNC_API type WINAPI //#define
DLLFUNC(type) DLLFUNC_API type __stdcall
//
Samele :
// DLLFUNC(int) fnTestFunc(int iValue);
//============================================================================
//--- For Variable ---
#define DLLVAR(type) DLLFUNC_API type
//
Sample :
// Define(.h)
// extern DLLVAR(DWORD) dwTestValue;
// extern DLLFUNC_API DWORD dwTestValue;
// (.cpp)
// DLLVAR(DWORD) dwTestValue;
// DLLFUNC_API DWORD dwTestValue; |
|
|
|
Output Function define.h |
|
#pragma once
#include "DllCommon.h"
#ifdef __cplusplud
extern "C"
{
#endif //
__cplusplud
DLLFUNC(int) fnTestFunc(int iValue);
extern DLLFUNC_API DWORD
g_dwTestValue; //extern DLLVAR(DWORD)
dwTestValue;
#ifdef __cplusplud
}
#endif //
__cplusplud |
|
我們可以發現我們變數有額外使用 extern 的定義,這是外部宣告,避免重複定義,因為我們的標頭檔是共用的,所以我們必須稍微注意。
|
|
Function.cpp |
|
DLLFUNC(int) fnTestFunc(int iValue)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialog dlgBuild;
dlgBuild.DoModal();
return 0;
}
DLLFUNC_API DWORD g_dwTestValue = 10; //
DLLVAR(DWORD) g_dwTestValue = 10;
|
|
在剛開始的時候我們有提過 AFX_MANAGE_STATE 這一個巨集,這一個巨集是用來推送模組的全域資料。因此在每一個輸出函式的開始,你必須設定目前模組狀態為
DLL,別擔心重設模組狀態,因為 AFX_MANAGE_STATE 巨集實際上會在當物件過完生命週期時,重設為前一個狀態。在這一個匯出函式我們有使用到
MFC 的類別,所以我們在程式一開始的地方我們就加上這一個敘述。 |
我們真正的應用,我們必須定義 DEF 檔,避免找不到匯出的函式,但是如果要為我們每一個匯出函式定義一個名稱,這可是一件累人的苦差事,除了將來維護是一個問題,呼叫端也要注意其關係性,並且為每一個要使用到的函式一一建立指標變數,是不是有別的方法可以簡化這些呢?其實還有一個不錯的方法,適用於大量的輸出函數,那就是建立一個
Function Table 的方法,我們替我們的函式建立一個結構( struct ) ,然後我們一次將這一個結構匯出,使用時只要宣告一個結構指標,就可以使用,就如同我們常使用的
Class 一樣,底下就是一個完整的範例,除了匯出一般函式,也會出結構方式的範例,並將 DLL 要用的一些基本機制加入並說明。
|
DllCommon.h |
|
#if !defined(_ORIGINALITY_DLL_COMMON_H_)
#define _ORIGINALITY_DLL_COMMON_H_
//============================================================================
// Originality Export/Import Define
//============================================================================
// 下列 ifdef 區塊是建立巨集以協助從 DLL 匯出的標準方式。
// 這個 DLL 中的所有檔案都是使用命令列中所定義 DLLFUNC_EXPORTS 符號編譯的。
// 任何使用這個 DLL 的專案都不宣告 DLLFUNC_EXPORTS 這個符號。這樣的話,只要
// 程式檔中包含這標頭檔案的任何其他專案會將 DLLFUNC_API 函式視為要從 DLL 匯
// 入,而只有宣告 DLLFUNC_EXPORTS 這個符號的 DLL 專案程式,會將這個巨集定義
// 的符號視為匯出的。
// ※記得 DLL 要定義 DLLFUNC_EXPORTS
#ifdef DLLFUNC_EXPORTS
#define DLLFUNC_API __declspec(dllexport)
#else
#define DLLFUNC_API __declspec(dllimport)
#endif
//============================================================================
//--- For function ---
#define DLLFUNC(type) DLLFUNC_API type WINAPI //#define DLLFUNC(type) DLLFUNC_API type __stdcall
// Samele :
// DLLFUNC(int) fnTestFunc(int iValue);
//============================================================================
//--- For Variable ---
#define DLLVAR(type) DLLFUNC_API type
// Sample :
// Define(.h)
// extern DLLVAR(DWORD) dwTestValue;
// extern DLLFUNC_API DWORD dwTestValue;
// (.cpp)
// DLLVAR(DWORD) dwTestValue;
// DLLFUNC_API DWORD dwTestValue;
#endif //_ORIGINALITY_DLL_COMMON_H_ |
|
|
|
#if !defined(_ORIGINALITY_COMMON_MESSAGE_H_)
#define _ORIGINALITY_COMMON_MESSAGE_H_
//============================================================================
// Module Register Message Define
// Samele :
// UINT g_uiOriginalityMessage = RegisterOriginalityMessage(); // For All Application
// UINT g_uiModuleMessage = RegisterWindowMessage((LPTSTR)_T("Originality Module Message"));
//============================================================================
#define RegisterOriginalityMessageName "OriginalityMessage" // Originality Message For All Application
#define RegisterOriginalityMessage() RegisterWindowMessage (RegisterOriginalityMessageName)
#pragma pack(4)
typedef struct _VERSION_FOR_ALL_
{
union
{
struct _VER
{
BYTE bMajor;
BYTE bMinor;
BYTE bRelease;
BYTE bBuild;
} Version;
DWORD dwVersion;
};
} VERSION_FOR_ALL, *PVERSION_FOR_ALL;
#define VERSION2DWORD(Major,Minor,Release,Build) MAKELONG(MAKEWORD(Major,Minor),MAKEWORD(Release,Build))
// return < 0, caller version is less than DLL
// = 0, caller version is the same as DLL
// > 0, caller version is great than DLL
__inline int CheckVersion (PVERSION_FOR_ALL pCaller, PVERSION_FOR_ALL pDll)
{
if (pCaller->Version.bMajor == pDll->Version.bMajor &&
pCaller->Version.bMinor == pDll->Version.bMinor &&
pCaller->Version.bRelease == pDll->Version.bRelease &&
pCaller->Version.bBuild == pDll->Version.bBuild)
return 0;
if (pCaller->Version.bMajor > pDll->Version.bMajor ||
(pCaller->Version.bMajor == pDll->Version.bMajor && pCaller->Version.bMinor > pDll->Version.bMinor) ||
(pCaller->Version.bMajor == pDll->Version.bMajor && pCaller->Version.bMinor == pDll->Version.bMinor &&
pCaller->Version.bRelease > pDll->Version.bRelease) ||
(pCaller->Version.bMajor == pDll->Version.bMajor && pCaller->Version.bMinor == pDll->Version.bMinor &&
pCaller->Version.bRelease > pDll->Version.bRelease && pCaller->Version.bBuild > pDll->Version.bBuild))
return 1;
else
return -1;
}
#pragma pack()
#endif //_ORIGINALITY_COMMON_MESSAGE_H_ |
|
|
|
#if !defined(_ORIGINALITY_TEMPLATE_LIBRARY_H_)
#define _ORIGINALITY_TEMPLATE_LIBRARY_H_
#include <windows.h>
#include <windowsx.h>
#include <OriginalityCommon.h>
#ifdef __cplusplud
extern "C" {
#endif // __cplusplud
#pragma pack(4)
//------------------------------------------------------------------------------------------------------
// ------------------------------- Search Library Version Info. -------------------------------
//------------------------------------------------------------------------------------------------------
#define TEMPLATE_LIBRARY_MAJOR 0 // Major
#define TEMPLATE_LIBRARY_MINOR 9 // Minor
#define TEMPLATE_LIBRARY_RELEASE 0 // Release
#define TEMPLATE_LIBRARY_BUILD 0 // Build
#define TemplateLibraryVersion VERSION2DWORD(DEVICE_SEARCH_MAJOR,DEVICE_SEARCH_MINOR,DEVICE_SEARCH_RELEASE,DEVICE_SEARCH_BUILD)
//========================================================================
//--- Struct ---
//========================================================================
typedef enum
{
DLL_RET_STATUS_SUCCESS = 1, // Okay
DLL_RET_STATUS_CANCELLED = 0, //
DLL_RET_STATUS_NO_MORE_DATA = 2,
DLL_RET_ERROR_FAIL = -1, // Invalid call
DLL_RET_ERROR_INCOMPATIBLE_VERSION = -2, // Version is incorrect (Incompatible Version)
DLL_RET_ERROR_BAD_PARAMETTER = -3, //
DLL_RET_ERROR_BAD_UI_PARAMETTER = -4, // UIparameter error
DLL_RET_ERROR_RESOURCE_NOT_ENOUGH = -5, //
DLL_RET_ERROR_INSUFFICIENT_MEMORY = -6, // Memory problem(Insufficient Memory)
DLL_RET_ERROR_INSUFFICIENT_RESOURCE = -7, // Resource problem(Insufficient Resource)
DLL_RET_ERROR_IO_ERROR = -8, // Read/Write error
DLL_RET_ERROR = -32767,
} DLL_TEMPLATE_LIBRARY_RETCODE;
typedef struct _TEMPLATE_LIBRARY_FUNCTS
{
DLL_TEMPLATE_LIBRARY_RETCODE (CALLBACK* pfnRegister)(PVERSION_FOR_ALL pVersion);// Regsiter
void (CALLBACK* pfnUnregister)();
} TEMPLATE_LIBRARY_FUNCS, *PTEMPLATE_LIBRARY_FUNCS;
//========================================================================
//--- Share for DLL ---
//========================================================================
#pragma data_seg(".DShare")
// add share data
#pragma data_seg()
//-------------------------------------------------------------------------------------------------------
// ---------- Functions ------------ //
//-------------------------------------------------------------------------------------------------------
//========================================================================
//--- Load DLL's Function Macro
//========================================================================
// Name of DLL
//#define DLL_TEMPLATE_LIBRARY_MODULE "DllTemplate.dll"
#if (defined _DEBUG)
#if (defined _UNICODE)
#define DLL_TEMPLATE_LIBRARY_MODULE _T("DllTemplateD.dll")
#define LIB_TEMPLATE_LIBRARY_MODULE "DllTemplateD.lib"
#elif (defined _MBCS)
#define DLL_TEMPLATE_LIBRARY_MODULE _T("DllTemplateMD.dll")
#define LIB_TEMPLATE_LIBRARY_MODULE "DllTemplateMD.lib"
#else
#define DLL_TEMPLATE_LIBRARY_MODULE _T("DllTemplateND.dll")
#define LIB_TEMPLATE_LIBRARY_MODULE "DllTemplateND.lib"
#endif // _UNICODE
#else
#if (defined _UNICODE)
#define DLL_DEVICE_SEARCH_MODULE _T("DllTemplate.dll")
#define LIB_DEVICE_SEARCH_MODULE "DllTemplate.lib"
#elif (defined _MBCS)
#define DLL_DEVICE_SEARCH_MODULE _T("DllTemplateM.dll")
#define LIB_DEVICE_SEARCH_MODULE "DllTemplateM.lib"
#else
#define DLL_DEVICE_SEARCH_MODULE _T("DllTemplateN.dll")
#define LIB_DEVICE_SEARCH_MODULE "DllTemplateN.lib"
#endif // _UNICODE
#endif // _DEBUG
// Used to get procedure address
#define DLL_TEMPLATE_LIBRARY_ENTRY "ServiceEntry"
// Calling convention and return parameter
typedef PTEMPLATE_LIBRARY_FUNCS (CALLBACK* PFNDLLENTRY)();
#pragma pack()
#ifdef __cplusplud
} /* Assume C declarations for C++ */
#endif // __cplusplud
#endif
|
|
|
|
|
|
// DllTemplate.cpp : 定義 DLL 的初始化常式。
//
#include "stdafx.h"
#include "DllTemplate.h"
#include "OriginalityCommon.h"
#include "DllCommon.h"
#include "TemplateLibrary.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
//
//TODO: 如果這個 DLL 是動態地對 MFC DLL 連結,
// 那麼從這個 DLL 匯出的任何會呼叫
// MFC 內部的函式,都必須在函式一開頭加上 AFX_MANAGE_STATE
// 巨集。
//
// 例如:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // 此處為正常函式主體
// }
//
// 這個巨集一定要出現在每一個
// 函式中,才能夠呼叫 MFC 的內部。這意味著
// 它必須是函式內的第一個陳述式
// ,甚至必須在任何物件變數宣告前面
// ,因為它們的建構函式可能會產生對 MFC
// DLL 內部的呼叫。
//
// 請參閱 MFC 技術提示 33 和 58 中的
// 詳細資料。
//
// CDllTemplateApp
BEGIN_MESSAGE_MAP(CDllTemplateApp, CWinApp)
END_MESSAGE_MAP()
// CDllTemplateApp 建構
CDllTemplateApp::CDllTemplateApp()
{
// TODO: 在此加入建構程式碼,
// 將所有重要的初始設定加入 InitInstance 中
}
// 僅有的一個 CDllTemplateApp 物件
CDllTemplateApp theApp;
// CDllTemplateApp 初始設定
BOOL CDllTemplateApp::InitInstance()
{
CWinApp::InitInstance();
//(+)2007-12-31 Richard Chung
SetupDllObjects(); // Setup Function Table
//(-)2007-12-31 Richard Chung
return TRUE;
}
//==============================================================================
// Global Variables
//==============================================================================
TEMPLATE_LIBRARY_FUNCS g_DllObjects;
DLLVAR(DWORD) g_dwTestValue = 10;
DLLFUNC(int) fnTestFunc(int iValue)
{
//AFX_MANAGE_STATE(AfxGetStaticModuleState());
CString strTest=_T("1234");
return iValue;
}
//==============================================================================
// Function name : Register
// Description :
// Parameter : None
// Return :
// Note :
// Update : 2007-12-28 Richard Chung
//==============================================================================
DLLFUNC(DLL_TEMPLATE_LIBRARY_RETCODE) Register (PVERSION_FOR_ALL pVersion)
{
VERSION_FOR_ALL DllVersion;
DllVersion.dwVersion=VERSION2DWORD( TEMPLATE_LIBRARY_MAJOR, TEMPLATE_LIBRARY_MINOR,
TEMPLATE_LIBRARY_RELEASE, TEMPLATE_LIBRARY_BUILD);
if (CheckVersion(pVersion,&DllVersion) < 0)
return DLL_RET_ERROR_INCOMPATIBLE_VERSION;
return DLL_RET_STATUS_SUCCESS;
}
//==============================================================================
// Function name : Unregister
// Description :
// Parameter : None
// Return :
// Note :
// Update : 2007-12-28 Richard Chung
//==============================================================================
DLLFUNC(void) Unregister ()
{
}
//==============================================================================
// Function name : ServiceEntry
// Description : Export function table
// Parameter : None
// Return :
// Note :
// Update : 2007-12-28 Richard Chung
//==============================================================================
PTEMPLATE_LIBRARY_FUNCS WINAPI ServiceEntry ()
{
return &g_DllObjects;
}
//==============================================================================
// Function name :
// Description : Setup function table
// Parameter : None
// Return :
// Note :
// Update : 2007-12-28 Richard Chung
//==============================================================================
BOOL SetupDllObjects()
{
g_DllObjects.pfnRegister = Register;
g_DllObjects.pfnUnregister = Unregister;
return TRUE;
}
|
|
|
|
DEF 定義檔 |
|
; DllTemplate.def : 宣告 DLL 的模組參數。
LIBRARY "DllTemplate"
EXPORTS
; 明確匯出可置於此
ServiceEntry
fnTestFunc
|
|
|
|
呼叫端範例程式 |
|
void CMainFrame::OnCmdFunctest()
{
// TODO: 在此加入您的命令處理常式程式碼
//--- Use Function Table ---
HINSTANCE hDllInstance; // DLL instance handle
VERSION_FOR_ALL DllVersion; // DLL Version info
PFNTEMPLATELIBRARYENTRY pfnEntry; // Entry Pointer
PTEMPLATE_LIBRARY_FUNCS pfnFunc; // Function Table
//--- DLL Register ---
DllVersion.dwVersion=VERSION2DWORD( TEMPLATE_LIBRARY_MAJOR, TEMPLATE_LIBRARY_MINOR,
TEMPLATE_LIBRARY_RELEASE, TEMPLATE_LIBRARY_BUILD);
hDllInstance = NULL;
pfnEntry = NULL;
pfnFunc = NULL;
VERIFY(hDllInstance = ::LoadLibrary(DLL_TEMPLATE_LIBRARY_MODULE) ); //發出 LoadLibrary 的函式將程式庫載入
if (hDllInstance != NULL)
{
pfnEntry = (PFNTEMPLATELIBRARYENTRY) GetProcAddress (hDllInstance, DLL_TEMPLATE_LIBRARY_ENTRY); // 取得程式庫入口
if (pfnEntry != NULL)
{
pfnFunc = pfnEntry();
pfnFunc->pfnRegister(&DllVersion);
}
else
return;
}
//--- Test Function ---
if (hDllInstance != NULL)
{
typedef int (CALLBACK* PFNTEST)(int);
PFNTEST pfnTest;
FARPROC pfn = GetProcAddress (hDllInstance, "fnTestFunc"); // 取得程式庫入口
if (pfn != NULL)
{
int a= 0;
pfnTest = (PFNTEST)pfn;
a = pfnTest(123);
}
}
//--- Test Function 2 ---
int (CALLBACK *pfnTestDLL) (int iValue);
if (hDllInstance != NULL)
{
int a = 0;
FARPROC pfn = 0;
pfn = GetProcAddress(hDllInstance,"fnTestFunc");
if (pfn != NULL)
{
pfnTestDLL = (int (CALLBACK *)(int)) pfn;
a = pfnTestDLL(246);
}
}
if (hDllInstance != NULL)
FreeLibrary(hDllInstance);
} |
|
|