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的類(lèi)
Part 2
從VB應用程序調用C++ DLL的函數
Part 3
從VB應用程序調用C++ DLL的類(lèi)
Part 4
從VC++應用程序動(dòng)態(tài)的調用C++ DLL的函數
從VC++應用程序調用C++ DLL的函數
Visual Studio 6 使創(chuàng )建包含函數或類(lèi)的動(dòng)態(tài)連接庫(DLL) 變得非常容易.
第一步
打開(kāi) Visual Studio 然后選擇 File | New菜單項:
選擇 Win32 Dynamic Link Library, 輸入工程名, 敲 OK.
選擇 A DLL that exports some symbols 并單擊Finish.在File View里你會(huì )看到如下的工程文件:
第二步
在Test.cpp里,你將看到如下代碼:
// Test.cpp : Defines the entry point for the DLLapplication.//#include "stdafx.h"#include "Test.h"BOOL APIENTRYDllMain( 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: caseDLL_PROCESS_DETACH: break; } return TRUE;}// This isan example of an exported variableTEST_API int nTest=0;// This is anexample of an exported function.TEST_API int fnTest(void){ return42;}// This is the constructor of a class that has been exported.// seeTest.h for the class definitionCTest::CTest(){ return; }
Test.cpp包含了 fnTest 和 CTest::CTest.如果你現在編譯Test.dll,你將會(huì )得到一個(gè)可以被其他VC++應用程序直接調用的DLL. 允許其他VC++程序調用的關(guān)鍵機制?( key mechanism)就包含在Test.h中:
// The following ifdef block is the standard way ofcreating macros// which make exporting from a DLL simpler. All fileswithin this DLL// are compiled with the TEST_EXPORTS symbol defined onthe command line.// This symbol should not be defined on any projectthat uses this DLL.// This way any other project whose source filesinclude this file see // TEST_API functions as being imported from aDLL, whereas this DLL// sees symbols defined with this macro as beingexported.#ifdef TEST_EXPORTS#define TEST_API__declspec(dllexport)#else#define TEST_API__declspec(dllimport)#endif// This class is exported from theTest.dllclass TEST_API CTest {public: CTest(void); // TODO: addyour methods here.};extern TEST_API int nTest;TEST_API int fnTest(void);
這里面發(fā)生了什么? #ifdef TEST_EXPORTS是什么意思? TEST_EXPORTS又是在哪定義的?
TEST_EXPORTS如果被定義, 那么TEST_API將會(huì )被定義為 __declspec(dllexport) (DLL導出),否則,將會(huì )被定義為_(kāi)_declspec(dllimport)(DLL導入). 這將影響到后邊定義的Ctest類(lèi)是導出類(lèi)還是導入類(lèi).這意味著(zhù)如果我們需要導出的時(shí)候,我們就得定義TEST_EXPORTS.當一個(gè)VC++應用程序要訪(fǎng)問(wèn)這個(gè)DLL的時(shí)候,可以將Test.lib鏈接進(jìn)去,它包含了DLL的導出符號.
第三步
TEST_EXPORTS 在哪里被定義了呢? DLL wizard干了一件我討厭的事,它把TEST_EXPORTS放到了命令行里. 選擇 Project | Settings | C/C++ | General, 你將看到工程選項:
當然了,這個(gè)辦法是可行的. 但是它卻容易讓人忽計,并且可能導致維護上的麻煩. 我比較喜歡清楚明白的定義TEST_EXPORTS : 從項目選項里邊去掉/D "TEST_EXPORTS",然后在Test.cpp 里來(lái)定義它:
// 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, 我們將得到一個(gè)可以被其他VC應用程序所調用的DLL.
第四步
我們如何調用DLL里的函數呢? 舉個(gè)例子吧, 我用VS創(chuàng )建一個(gè)示例. 選MFC AppWizard(exe),輸入項目名字,然后點(diǎn)OK. 選擇基于對話(huà)框. 然后點(diǎn)Finish. 打開(kāi) XXXDlg.cpp(XXX是你的工程名字.)找到OnInitDialog()成員函數, 敲進(jìn)去如下的代碼:
. . . // Set the icon for this dialog. The frameworkdoes this automatically // when the application‘s main window isnot a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // code totest Test.dll function: int n = fnTest(); //<=== ADD THIS LINE // code to test the CTest class: CTesttest; // <=== ADD THIS LINE returnTRUE; // return TRUE unless you set the focus to a control}
第五步
我們還沒(méi)寫(xiě)完代碼呢, 現在要把 Test.h這個(gè)DLL的頭文件包含進(jìn)去:
//TestExeDlg.cpp : implementation file//#include "stdafx.h"#include"TestExe.h"#include "TestExeDlg.h"#include"Test.h" // <=== ADD THIS LINE...
第六步
如果你想趕時(shí)間做一個(gè)演示的話(huà), 你可能會(huì )嘗試只是拷貝DLL的 test.h 到你的項目目錄里去,那么編譯器會(huì )找到它. 但是當項目很大的時(shí)候,這可不是個(gè)好主意, 因為當你更新你的DLL文件時(shí),可能會(huì )遇上危險.比如忘了拷貝它到你的exe的目錄中去. 這里有個(gè)簡(jiǎn)單的方法來(lái)解決這個(gè)問(wèn)題 : 選擇Project | Settings | C/C++ | Settings |Preprocessor, 并且添加Additional include directories: (DLL工程的目錄)
提示 這樣做實(shí)際上是假設DLL項目和EXE項目擁有同一個(gè)項目目錄.
現在當我編譯的時(shí)候, 我得到了一個(gè) 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)
第七步
雖然我已經(jīng)告訴編譯器DLL符號啦,但是鏈接器還不知道. 所以我們必須告訴鏈接器.. 選擇Project | Settings | Link,把DLL的lib文件加到Object/library modules里邊去:
----------------------------------------------
第八步
好啦,現在編譯通過(guò). 在我們運行程序前,別忘了一件事: 把Test.dll 拷貝到EXE的目錄里去.
第九步
接下來(lái),可以放一個(gè) 斷點(diǎn)到OnInitDialog()函數里去, 點(diǎn) GO(F5)調試運行:
可以看到, fnTest 返回了42, 和我們預測的一樣. CTest 類(lèi)也可以用類(lèi)似的方法來(lái)測試.
要點(diǎn).
VS的工程向導為我們創(chuàng )建VC++DLL提供了很好的開(kāi)始.
函數,類(lèi), 和變量 可以從DLL中導出.
使用 #define 預處理器定義, 一個(gè)頭文件將可以被DLL 和應用程序共同使用.
DLL導出它的符號,并且應用程序導入這個(gè)DLL符號. 當編譯應用程序時(shí),編譯器通過(guò)頭文件看到的DLL符號, 當鏈接應用程序時(shí), 鏈接器通過(guò)導入庫(Test.lib)看到DLL符號.
當執行應用程序時(shí),DLL必須放到和EXE相同的目錄中去. DLL也可以放到 windows或者system目錄中,這樣也是可行的, 但是它經(jīng)常引起某些問(wèn)題, 所以應該避免這樣使用
注釋:
再實(shí)際工作中,我很少用到 第七步中的方法. 這樣做的話(huà),在大的工程中,DLL和Lib文件將經(jīng)常變得很難管理.我們會(huì )想到要建立一個(gè)lib目錄和一個(gè)bin目錄,在這里邊放進(jìn)去所有我們要使用的lib文件 , dll文件 和exe文件. 如果這樣做的話(huà),我們怎么告訴鏈接器找到lib文件呢?有兩種方法來(lái)做:
1. 選擇Tools | Options | Directories and set Show directories for 為"Library files". 在下邊添加上我們工程所使用的Lib文件的路徑.
2. 另一種辦法是,選擇 Project | Settings | Link, 選category為 Input ,在下邊的 Additional library path 筐里輸入工程使用的lib文件的所在路徑.
哪種方法更好一點(diǎn)呢?這取決于你的開(kāi)發(fā)環(huán)境. 第一種方法要求整個(gè)工程要共享的設置目錄路徑, 并且所有要求所有的開(kāi)發(fā)者的VS都必須設置到這些路徑.
第二種方法允許每個(gè)工程定制自己的路徑,并且在工程中被儲存,如果開(kāi)發(fā)者的計算機上存放了同樣的目錄,那么每個(gè)開(kāi)發(fā)者都可以簽出工程并且設計. ,這樣可以避免在每臺機器上都去設置同樣的路徑.
到現在,我還沒(méi)有說(shuō)怎樣指定要使用的LIB文件, 我的辦法是在DLL的Test.h中添加兩行,現在它看起來(lái)像下邊的樣子:
#ifdef TEST_EXPORTS #define TEST_API__declspec(dllexport)#else #define TEST_API __declspec(dllimport) #pragma message("automatic link to Test.lib") // <== add thisline #pragma comment(lib, "Test.lib") // <== addthis line#endif// This class is exported from the Test.dllclassTEST_API CTest {public: CTest(void);};extern TEST_API intnTest;TEST_API int fnTest(void);
這樣做,保證了當工程包含DLL的頭文件時(shí), 它會(huì )自動(dòng)的把DLL 的lib鏈接進(jìn)去,我們可以從VS的OUTPUT窗口看到#pragma message給我們的傳達的"automatic link to Test.lib"這個(gè)消息.
聯(lián)系客服