Step by Step: Calling C++ DLLs from VC++ and VB
一步一步教你用VC和VB調用C++ DLL.
作者 Hans Dietrich 翻譯煙灰
介紹
本系列教程討論了普通情況下4種使用DLL的方法
Part 1
從VC++應用程式調用C++ DLL的函數
從VC++應用程式調用C++ DLL的類
Part 2
從VB應用程式調用C++ DLL的函數
Part 3
從VB應用程式調用C++ DLL的類
Part 4
從VC++應用程式動態調用C++ DLL的函數
從VC++應用程式調用C++ DLL的函數
Visual Studio 6 使建立包含函數或類的動態串連庫(DLL) 變得非常容易.
第一步
開啟 Visual Studio 然後選擇 File | New功能表項目:
選擇 Win32 Dynamic Link Library, 輸入工程名, 敲 OK.
選擇 A DLL that exports some symbols 並單擊Finish.在File View裡你會看到如下的工程檔案:
第二步
在Test.cpp裡,你將看到如下代碼:
// Test.cpp : Defines the entry point for the DLL application.//#include "stdafx.h"#include "Test.h"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: break; } return TRUE;}// This is an example of an exported variableTEST_API int nTest=0;// This is an example of an exported function.TEST_API int fnTest(void){ return 42;}// This is the constructor of a class that has been exported.// see Test.h for the class definitionCTest::CTest(){ return; }
Test.cpp 包含了 fnTest 和 CTest::CTest.如果你現在編譯Test.dll, 你將會得到一個可以被其他VC++應用程式直接調用的DLL. 允許其他VC++程式調用的關鍵機制?( key mechanism)就包含在 Test.h中:
// The following ifdef block is the standard way of creating macros// which make exporting from a DLL simpler. All files within this DLL// are compiled with the TEST_EXPORTS symbol defined on the command line.// This symbol should not be defined on any project that uses this DLL.// This way any other project whose source files include this file see // TEST_API functions as being imported from a DLL, whereas this DLL// sees symbols defined with this macro as being exported.#ifdef TEST_EXPORTS#define TEST_API __declspec(dllexport)#else#define TEST_API __declspec(dllimport)#endif// This class is exported from the Test.dllclass TEST_API CTest {public: CTest(void); // TODO: add your methods here.};extern TEST_API int nTest;TEST_API int fnTest(void);
這裡面發生了什麼? #ifdef TEST_EXPORTS是什麼意思? TEST_EXPORTS又是在哪定義的?
TEST_EXPORTS如果被定義, 那麼TEST_API將會被定義為 __declspec(dllexport) (DLL匯出), 否則,將會被定義為__declspec(dllimport)(DLL匯入). 這將影響到後邊定義的Ctest類是匯出類還是匯入類. 這意味著如果我們需要匯出的時候,我們就得定義TEST_EXPORTS. 當一個VC++應用程式要訪問這個DLL的時候,可以將Test.lib連結進去,它包含了DLL的匯出符號.
第三步
TEST_EXPORTS 在哪裡被定義了呢? DLL wizard幹了一件我討厭的事,它把TEST_EXPORTS放到了命令列裡. 選擇 Project | Settings | C/C++ | General, 你將看到工程選項:
當然了,這個辦法是可行的. 但是它卻容易讓人忽計,並且可能導致維護上的麻煩. 我比較喜歡清楚明白的定義TEST_EXPORTS : 從項目選項裡邊去掉/D "TEST_EXPORTS",然後在Test.cpp 裡來定義它:
// Test.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#define TEST_EXPORTS // <=== ADD THIS LINE
#include "Test.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
.
.
.
注意 #define TEST_EXPORTS 在 #include "Test.h"前邊. 所以,它定義要在標頭檔裡.現在,可以像先前那樣重新編譯我們的Test.dll, 我們將得到一個可以被其他VC應用程式所調用的DLL.
第四步
我們如何調用DLL裡的函數呢? 舉個例子吧, 我用VS建立一個樣本. 選MFC AppWizard (exe),輸入項目名字,然後點OK. 選擇基於對話方塊. 然後點Finish. 開啟 XXXDlg.cpp(XXX是你的工程名字.) 找到OnInitDialog()成員函數, 敲進去如下的代碼:
. . . // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // code to test Test.dll function: int n = fnTest(); // <=== ADD THIS LINE // code to test the CTest class: CTest test; // <=== ADD THIS LINE return TRUE; // return TRUE unless you set the focus to a control}
第五步
我們還沒寫完代碼呢, 現在要把 Test.h這個DLL的標頭檔包含進去:
// TestExeDlg.cpp : implementation file//#include "stdafx.h"#include "TestExe.h"#include "TestExeDlg.h"#include "Test.h" // <=== ADD THIS LINE...
第六步
如果你想趕時間做一個示範的話, 你可能會嘗試只是拷貝DLL的 test.h 到你的項目目錄裡去,那麼編譯器會找到它. 但是當項目很大的時候,這可不是個好主意, 因為當你更新你的DLL檔案時,可能會遇上危險.比如忘了拷貝它到你的exe的目錄中去. 這裡有個簡單的方法來解決這個問題 : 選擇Project | Settings | C/C++ | Settings | Preprocessor, 並且添加Additional include directories: (DLL工程的目錄)
提示 這樣做實際上是假設DLL項目和EXE項目擁有同一個項目目錄.
現在當我編譯的時候, 我得到了一個 linker errors!!
Deleting intermediate files and output files for project 'TestExe - Win32 Debug'.
--------------------Configuration: TestExe - Win32 Debug--------------------
Compiling resources...
Compiling...
StdAfx.cpp
Compiling...
TestExe.cpp
TestExeDlg.cpp
Generating Code...
Linking...
TestExeDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport)
public: __thiscall CTest::CTest(void)" (__imp_??0CTest@@QAE@XZ)
TestExeDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport)
int __cdecl fnTest(void)" (__imp_?fnTest@@YAHXZ)
Debug/TestExe.exe : fatal error LNK1120: 2 unresolved externals
Error executing link.exe.
TestExe.exe - 3 error(s), 0 warning(s)
第七步
雖然我已經告訴編譯器DLL符號啦,但是連結器還不知道. 所以我們必須告訴連結器.. 選擇Project | Settings | Link,把DLL的lib檔案加到Object/library modules裡邊去:
----------------------------------------------
第八步
好啦,現在編譯通過. 在我們運行程式前,別忘了一件事: 把Test.dll 拷貝到EXE的目錄裡去.
第九步
接下來,可以放一個 斷點到OnInitDialog()函數裡去, 點 GO(F5)調試運行:
可以看到, fnTest 返回了42, 和我們預測的一樣. CTest 類也可以用類似的方法來測試.
要點.
VS的工程嚮導為我們建立VC++DLL提供了很好的開始.
函數,類, 和變數 可以從DLL中匯出.
使用 #define 前置處理器定義, 一個標頭檔將可以被DLL 和應用程式共同使用.
DLL匯出它的符號,並且應用程式匯入這個DLL符號. 當編譯應用程式時,編譯器通過標頭檔看到的DLL符號, 當連結應用程式時, 連結器通過匯入庫(Test.lib)看到DLL符號.
當執行應用程式時,DLL必須放到和EXE相同的目錄中去. DLL也可以放到 windows或者system目錄中,這樣也是可行的, 但是它經常引起某些問題, 所以應該避免這樣使用
注釋:
再實際工作中,我很少用到 第七步中的方法. 這樣做的話,在大的工程中,DLL和Lib檔案將經常變得很難管理.我們會想到要建立一個lib目錄和一個 bin目錄,在這裡邊放進去所有我們要使用的lib檔案 , dll檔案 和exe檔案. 如果這樣做的話,我們怎麼告訴連結器找到lib檔案呢? 有兩種方法來做:
1. 選擇Tools | Options | Directories and set Show directories for 為"Library files". 在下邊添加上我們工程所使用的Lib檔案的路徑.
2. 另一種辦法是,選擇 Project | Settings | Link, 選category為 Input ,在下邊的 Additional library path 筐裡輸入工程使用的lib檔案的所在路徑.
哪種方法更好一點呢?這取決於你的開發環境. 第一種方法要求整個工程要共用的設定目錄路徑, 並且所有要求所有的開發人員的VS都必須設定到這些路徑.
第二種方法允許每個工程定製自己的路徑,並且在工程中被儲存,如果開發人員的電腦上存放了同樣的目錄,那麼每個開發人員都可以簽出工程並且設計. ,這樣可以避免在每台機器上都去設定同樣的路徑.
到現在,我還沒有說怎樣指定要使用的LIB檔案, 我的辦法是在DLL的Test.h中添加兩行,現在它看起來像下邊的樣子:
#ifdef TEST_EXPORTS #define TEST_API __declspec(dllexport)#else #define TEST_API __declspec(dllimport) #pragma message("automatic link to Test.lib") // <== add this line #pragma comment(lib, "Test.lib") // <== add this line#endif// This class is exported from the Test.dllclass TEST_API CTest {public: CTest(void);};extern TEST_API int nTest;TEST_API int fnTest(void);
這樣做,保證了當工程包含DLL的標頭檔時, 它會自動的把DLL 的lib連結進去,我們可以從VS的OUTPUT視窗看到#pragma message給我們的傳達的"automatic link to Test.lib"這個訊息.