問題:
1.寫一段C++程序,編譯成動態鏈接庫后,C程序怎么訪問?
2.寫一段C程序,編譯成動態鏈接庫后,C++程序怎么訪問?
3.寫一個類,編譯成動態鏈接庫后,里面的public變量能否訪問?
對於以上問題,我在Visual C++ 6.0上進行了實驗。以下是實驗的過程和初步結論。
1. 建立動態鏈接庫是在新建工程中選擇Win32 Dynamic-Link Library,建立空的工程,在里面添加頭文件和實現文件(C程序用.c后綴,C++程序用.cpp后綴),以下給出C程序寫的動態鏈接庫代碼和C++寫的動態鏈接庫代碼(只包含一個Add函數):
Case 1:C程序的動態鏈接庫代碼:
//CLib.h #ifndef C_LIB_H #define C_LIB_H extern int __declspec(dllexport) add(int x,int y); #endif //CLib.c #include "Clib.h" int add( int x, int y ) { return x + y; }
Case 2:C++程序的動態鏈接庫代碼:
//CplusplusLib.h #ifndef LIB_H #define LIB_H extern "C" int __declspec(dllexport) add(int x, int y); #endif //CplusplusLib.cpp #include "CplusplusLib.h" int add( int x, int y ) { return x + y; }
由此可發現,其代碼唯一不同的地方在於extern int __declspec(dllexport) add(int x,int y) 這一導出語句,在C程序中沒有”C” ,而在C++程序多了一個標識”C”。
發生這個區別的原因是由於C++編譯器與C編譯器在對程序進行編譯的時候,對函數聲明的編譯會有區別,加上”C”,是為了告訴C++編譯器,使用C編譯器的方式對這一個函數聲明進行編譯。這樣,在C程序調用C++寫的動態鏈接庫的時候,才不會發生因為尋找不到對應的函數名(編譯后的)而發生Link錯誤。
再來看調用,調用方式有兩種:靜態和動態。這的確是印證了老師說的萬事皆有兩面的道理。
使用動態的方式調用,不管在C還是在C++中,代碼都是一樣的,下面給出一個例子:
typedef int(*lpAddFun)(int, int); int main(int argc, char* argv[]) { HINSTANCE hDll; lpAddFun addFun; hDll = LoadLibrary("..//..//Debug//CplusplusDLL.dll"); if (hDll != NULL) { addFun = (lpAddFun)GetProcAddress(hDll, "add"); if (addFun != NULL) { int result = addFun(2, 3); printf("%d", result); } FreeLibrary(hDll); } return 0; }
思路是定義一個函數指針,在主程序中使用LoadLibrary這個庫函數載入DLL,然后再用GetProcAddress這個庫函數獲得DLL中函數所在的位置。調用完函數后使用FreeLibrary這個庫函數釋放DLL資源。
然而在靜態的方式中,C和C++的調用就略有不同,下面給出C++調用C庫的程序:
// CplusplusApplicationStaticInvoke.cpp : Defines the entry point for the console application. #include "stdafx.h" #pragma comment(lib,"CDLL.lib") extern "C" __declspec(dllimport) add(int x,int y); int main(int argc, char* argv[]) { int result = add(2,3); printf("%d",result); return 0; }
#pragma comment(lib,"CDLL.lib")這一句是表示在鏈接的時候將CDLL.dll這個動態鏈接庫鏈接到這個程序當中。
而在C調用C++庫的例子中:
// CApplicationStaticInvoke.cpp : Defines the entry point for the console application. #include "stdafx.h" #pragma comment(lib,"CplusplusDLL.lib") extern __declspec(dllimport) add(int x,int y); int main(int argc, char* argv[]) { int result = add(2,3); printf("%d",result); return 0; }
明顯看到extern __declspec(dllimport) add(int x,int y); 與上一例子不同,少了一個”C”
這個原因很明顯,就是為了動態庫中的函數名進行對應。在CDLL中函數當然是使用C編譯器的方式進行編譯的,所以在調用程序中,在聲明外部函數的時候,必須加上”C”,以使的這個C++程序,在編譯的時候使用C編譯的方法對這個外部函數聲明進行編譯,否則在編譯運行的時候就會報鏈接錯誤。在C++調用C的例子中我們將”C”去掉,結果:
LNK2001: unresolved external symbol "__declspec(dllimport) int __cdecl add(int,int)" (__imp_?add@@YAHHH@Z)
上面add@@YAHHH@Z就是add函數用C++的編譯方法編譯后的得到的函數名,而C編譯得到的應該是_add,鏈接的時候匹配不成功,因此就報錯了。
2. 關於類的public變量能夠被訪問,能否被直接訪問,這里我仍沒有找出答案,等待老師解答,但是有一個間接的方法能訪問到類里面的變量。就是使用類的全局變量,將其導出即可,例子如下:
#ifdef CLASSDLL_EXPORTS #define CLASSDLL_API __declspec(dllexport) #else #define CLASSDLL_API __declspec(dllimport) #endif class CLASSDLL_API CClassDLL { public: CClassDLL(void); //Added public member int testNumber; // TODO: add your methods here. }; extern CLASSDLL_API int nClassDLL; CLASSDLL_API int fnClassDLL(void);
上面的標准宏是為了對導入導出進行匹配,這里先略過。
注意extern CLASSDLL_API int nClassDLL; 這句代碼定義了這個DLL中的一個全局變量。注意這個時候宏CLASSDLL_API應該是__declspec(dllexport)表示導出該變量。
在調用的時候:
#pragma comment(lib,"ClassDLL.lib") extern int _declspec(dllimport) nClassDLL; int main() { … }
這樣就可以直接在程序中訪問nClassDLL了。