一步一步教你用VC和VB調用C++ DLL

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"這個消息.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章