如何編譯生成帶參數dll
㈠ VB6.0怎麼調用帶參數的DLL
vb6.0中可以創建DLL文件,也可以調用DLL文件,該怎麼調用呢?下面我們就來看看詳細的教程。
1、桌面上雙擊VB圖標,打開Visual Basic。
2、打開的VB窗口中,選擇文件----新建工程選項。
3、新建工程窗口中,選擇ActiveX DLL選項。
4、編輯窗口中,輸入dll文件的代碼,具體內容由個人決定。
5、單擊文件----保存工程,將dll工程保存。
6、單擊文件----生成dll,生成一個dll文件。
7、單擊工程----引用選項,如下圖所示。
8、引用窗口中,單擊瀏覽按鈕。
9、選擇要引用的DLL文件,單擊打開按鈕。
10、DLL文件就被引用到應用程序中了,單擊確定按鈕。
11、接下來就可以在應用程序中,插入組件,書寫代碼使用DLL文件了。
㈡ 如何編譯一個 dll文件
創建DLL工程
這里,我們為了簡要說明DLL的原理,我們決定使用最簡單的編譯環境VC6.0,如下圖,我們先建立一個新的Win32 Dynamic-Link Library工程,名稱為「MyDLL」,在Visual Studio中,你也可以通過建立Win32控制台程序,然後在「應用程序類型」中選擇「DLL」選項,
點擊確定,選擇「一個空的DLL工程」,確定,完成即可。
一個簡單的dll
在第一步我們建立的工程中建立一個源碼文件」dllmain.cpp「,在「dllmain.cpp」中,鍵入如下代碼
[cpp] view plain
#include <Windows.h>
#include <stdio.h>
BOOL APIENTRY DllMain(HMODULE hMole, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
printf("DLL_PROCESS_ATTACH\n");
break;
case DLL_THREAD_ATTACH:
printf("DLL_THREAD_ATTACH\n");
break;
case DLL_THREAD_DETACH:
printf("DLL_THREAD_DETACH\n");
break;
case DLL_PROCESS_DETACH:
printf("DLL_PROCESS_DETACH\n");
break;
}
return TRUE;
}
之後,我們直接編譯,即可以在Debug文件夾下,找到我們生成的dll文件,「MyDLL.dll」,注意,代碼裡面的printf語句,並不是必須的,只是我們用於測試程序時使用。而DllMain函數,是dll的進入/退出函數。
實際上,讓線程調用DLL的方式有兩種,分別是隱式鏈接和顯式鏈接,其目的均是將DLL的文件映像映射進線程的進程的地址空間。我們這里只大概提一下,不做深入研究,如果感興趣,可以去看《Window高級編程指南》的第12章內容。
隱式鏈接調用
隱士地鏈接是將DLL的文件影響映射到進程的地址空間中最常用的方法。當鏈接一個應用程序時,必須制定要鏈接的一組LIB文件。每個LIB文件中包含了DLL文件允許應用程序(或另一個DLL)調用的函數的列表。當鏈接器看到應用程序調用了某個DLL的LIB文件中給出的函數時,它就在生成的EXE文件映像中加入了信息,指出了包含函數的DLL文件的名稱。當操作系統載入EXE文件時,系統查看EXE文件映像的內容來看要裝入哪些DLL,而後試圖將需要的DLL文件映像映射到進程的地址空間中。當尋找DLL時,系統在系列位置查找文件映像。
1.包含EXE映像文件的目錄
2.進程的當前目錄
3.Windows系統的目錄
4.Windows目錄
5.列在PATH環境變數中的目錄
這種方法,一般都是在程序鏈接時控制,反映在鏈接器的配置上,網上大多數講的各種庫的配置,比如OPENGL或者OPENCV等,都是用的這種方法
顯式鏈接調用
這里我們只提到兩種函數,一種是載入函數
[cpp] view plain
HINSTANCE LoadLibrary(LPCTSTR lpszLibFile);
HINSTANCE LoadLibraryEx(LPCSTR lpszLibFile,HANDLE hFile,DWORD dwFlags);
返回值HINSTANCE值指出了文件映像映射的虛擬內存地址。如果DLL不能被映進程的地址空間,函數就返回NULL。你可以使用類似於
[cpp] view plain
LoadLibrary("MyDLL")
或者
[cpp] view plain
LoadLibrary("MyDLL.dll")
的方式進行調用,不帶後綴和帶後綴在搜索策略上有區別,這里不再詳解。
顯式釋放DLL
在顯式載入DLL後,在任意時刻可以調用FreeLibrary函數來顯式地從進程的地址空間中解除該文件的映像。
[cpp] view plain
BOOL FreeLibrary(HINSTANCE hinstDll);
這里,在同一個進程中調用同一個DLL時,實際上還牽涉到一個計數的問題。這里也不在詳解。
線程可以調用GetMoleHandle函數:
[cpp] view plain
GetMoleHandle(LPCTSTR lpszMoleName);
來判斷一個DLL是否被映射進進程的地址空間。例如,下面的代碼判斷MyDLL.dll是否已被映射到進程的地址空間,如果沒有,則裝入它:
[cpp] view plain
HINSTANCE hinstDll;
hinstDll = GetMoleHandle("MyDLL");
if (hinstDll == NULL){
hinstDll = LoadLibrary("MyDLL");
}
實際上,還有一些函數,比如 GetMoleFileName用來獲取DLL的全路徑名稱,FreeLibraryAndExitThread來減少DLL的使用計數並退出線程。具體內容還是參見《Window高級編程指南》的第12章內容,此文中不適合講太多的內容以至於讀者不能一下子接受。
DLL的進入與退出函數
說到這里,實際上只是講了幾個常用的函數,這一個小節才是重點。
在上面,我們看到的MyDLL的例子中,有一個DllMain函數,這就是所謂的進入/退出函數。系統在不同的時候調用此函數。這些調用主要提供信息,常常被DLL用來執行進程級或線程級的初始化和清理工作。如果你的DLL不需要這些通知,就不必再你的DLL源代碼中實現此函數,例如,如果你創建的DLL只含有資源,就不必實現該函數。但如果有,則必須像我們上面的格式。
DllMain函數中的ul_reason_for_call參數指出了為什麼調用該函數。該參數有4個可能值: DLL_PROCESS_ATTACH、DLL_THREAD_ATTACH、DLL_THREAD_DETACH、DLL_PROCESS_DETACH。
其中,DLL_PROCESS_ATTACH是在一個DLL首次被映射到進程的地址空間時,系統調用它的DllMain函數,傳遞的ul_reason_for_call參數為DLL_PROCESS_ATTACH。這只有在首次映射時發生。如果一個線程後來為已經映射進來的DLL調用LoadLibrary或LoadLibraryEx,操作系統只會增加DLL的計數,它不會再用DLL_PROCESS_ATTACH調用DLL的DllMain函數。
而DLL_PROCESS_DETACH是在DLL被從進程的地址空間解除映射時,系統調用它的DllMain函數,傳遞的ul_reason_for_call值為DLL_PROCESS_DETACH。我們需要注意的是,當用DLL_PROCESS_ATTACH調用DLL的DllMain函數時,如果返回FALSE,說明初始化不成功,系統仍會用DLL_PROCESS_DETACH調用DLL的DllMain。因此,必須確保沒有清理那些沒有成功初始化的東西。
DLL_THREAD_ATTACH:當進程中創建一個線程時,系統察看當前映射到進程的地址空間中的所有DLL文件映像,並用值DLL_THREAD_ATTACH調用所有的這些DLL的DllMain函數。該通知告訴所有的DLL去執行線程級的初始化。注意,當映射一個新的DLL時,進程中已有的幾個線程在運行,系統不會為已經運行的線程用值DLL_THREAD_ATTACH調用DLL的DllMain函數。
而DLL_THREAD_DETACH,如果線程調用ExitThread來終結(如果讓線程函數返回而不是調用ExitThread,系統會自動調用ExitThread),系統察看當前映射到進程空間的所有DLL文件映像,並用值DLL_THREAD_DETACH來調用所有的DLL的DllMain函數。該通知告訴所有的DLL去執行線程級的清理工作。
這里,我們需要注意的是,如果線程的終結是因為系統中的一個線程調用了TerminateThread,系統就不會再使用DLL_THREAD_DETACH來調用DLL和DllMain函數。這與TerminateProcess一樣,不再萬不得已時,不要使用。
下面,我們貼出《Window高級編程指南》中的兩個圖來說明上述四種參數的調用情況。
好的,介紹了以上的情況,下面,我們來繼續實踐,這次,建立一個新的空的win32控制台工程TestDLL,不再多說,代碼如下:
[cpp] view plain
#include <iostream>
#include <Windows.h>
using namespace std;
DWORD WINAPI someFunction(LPVOID lpParam)
{
cout << "enter someFunction!" << endl;
Sleep(1000);
cout << "This is someFunction!" << endl;
Sleep(1000);
cout << "exit someFunction!" << endl;
return 0;
}
int main()
{
HINSTANCE hinstance = LoadLibrary("MyDLL");
if(hinstance!=NULL)
{
cout << "Load successfully!" << endl;
}else {
cout << "Load failed" << endl;
}
HANDLE hThread;
DWORD dwThreadId;
cout << "createThread before " << endl;
hThread = CreateThread(NULL,0,someFunction,NULL,0,&dwThreadId);
cout << "createThread after " << endl;
cout << endl;
Sleep(3000);
cout << "waitForSingleObject before " << endl;
WaitForSingleObject(hThread,INFINITE);
cout << "WaitForSingleObject after " << endl;
cout << endl;
FreeLibrary(hinstance);
return 0;
}
代碼很好理解,但是前提是,你必須對線程有一定的概念。另外,注意,我們上面編譯的獲得的「MyDLL.dll"必須拷貝到能夠讓我們這個工程找到的地方,也就是上面我們提到的搜索路徑中的一個地方。
這里,我們先貼結果,當然,這只是在我機器上其中某次運行結果。
有了上面我們介紹的知識,這個就不是很難理解,主進程在調用LoadLibrary時,用DLL_PROCESS_ATTACH調用了DllMain函數,而線程創建時,用DLL_THREAD_ATTACH調用了DllMain函數,而由於主線程和子線程並行的原因,可能輸出的時候會有打斷。但是,這樣反而能讓我們更清楚的理解程序。
㈢ .dll文件是用什麼編寫的呀
DLL是一種特殊的可執行文件。說它特殊主要是因為一般它都不能直接運行,需要宿主程序比如*.EXE程序或其他DLL的動態調用才能夠使用。簡單的說,在通常情況下DLL是經過編譯的函數和過程的集合。
使用DLL技術主要有以下幾個原因:
一、減小可執行文件大小。
DLL技術的產生有很大一部分原因是為了減小可執行文件的大小。當操作系統進入Windows時代後,其大小已經達到幾十兆乃至幾百兆。試想如果還是使用DOS時代的單執行文件體系的話一個可執行文件的大小可能將達到數十兆,這是大家都不能接受的。解決的方法就是採用動態鏈接技術將一個大的可執行文件分割成許多小的可執行程序。
二、實現資源共享。
這里指的資源共享包括很多方面,最多的是內存共享、代碼共享等等。早期的程序員經常碰到這樣的事情,在不同的編程任務中編寫同樣的代碼。這種方法顯然浪費了很多時間,為了解決這個問題人們編寫了各種各樣的庫。但由於編程語言和環境的不同這些庫一般都不能通用,而且用戶在運行程序時還需要這些庫才行,極不方便。DLL的出現就像制定了一個標准一樣,使這些庫有了統一的規范。這樣一來,用不同編程語言的程序員可以方便的使用用別的編程語言編寫的DLL。另外,DLL還有一個突出的特點就是在內存中只裝載一次,這一點可以節省有限的內存,而且可以同時為多個進程服務。
三、便於維護和升級。
細心的朋友可能發現有一些DLL文件是有版本說明的。(查看DLL文件的屬性可以看到,但不是每一個DLL文件都有)這是為了便於維護和升級。舉個例子吧,早期的Win95中有一個BUG那就是在閏年不能正確顯示2月29日這一天。後來,Microsoft發布了一個補丁程序糾正了這個BUG。值得一提的是,我們並沒有重裝Win95,而是用新版本的DLL代替了舊版本的DLL。(具體是哪一個DLL文件筆者一時想不起來了。)另一個常見的例子是驅動程序的升級。例如,著名的DirectX就多次升級,現在已經發展到了6.0版了。更妙的是,當我們試圖安裝較低版本的DLL時,系統會給我們提示,避免人為的操作錯誤。例如我們升級某硬體的驅動程序時,經常碰到Windows提示我們當前安裝的驅動程序比原來的驅動程序舊。
四、比較安全。
這里說的安全也包括很多方面。比如,DLL文件遭受病毒的侵害機率要比普通的EXE文件低很多。另外,由於是動態鏈接的,這給一些從事破壞工作的「高手」們多少帶來了一些反匯編的困難。
第二章 在Delphi中編寫DLL top
注意:在這里筆者假定讀者使用的是Delphi 3或Delphi 4開場白說了那麼多,總該言歸正傳了。編寫DLL其實也不是一件十分困難的事,只是要注意一些事項就夠了。為便於說明,我們先舉一個例子。
library Delphi;
uses
SysUtils,
Classes;
function TestDll(i:integer):integer;stdcall;
begin
Result:=i;
end;
exports
TestDll;
begin
end.
上面的例子是不是很簡單?熟悉Delphi的朋友可以看出以上代碼和一般的Delphi程序的編寫基本是相同的,只是在TestDll函數後多了一個stdcall參數並且用exports語句聲明了TestDll函數。只要編譯上面的代碼,就可以得到一個名為Delphi.dll的動態鏈接庫。現在,讓我們來看看有哪些需要注意的地方。 一、在DLL中編寫的函數或過程都必須加上stdcall調用參數。在Delphi 1或Delphi 2環境下該調用參數是far。從Delphi 3以後將這個參數變為了stdcall,目的是為了使用標準的Win32參數傳遞技術來代替優化的register參數。忘記使用stdcall參數是常見的錯誤,這個錯誤不會影響DLL的編譯和生成,但當調用這個DLL時會發生很嚴重的錯誤,導致操作系統的死鎖。原因是register參數是Delphi的默認參數。
二、所寫的函數和過程應該用exports語句聲明為外部函數。
正如大家看到的,TestDll函數被聲明為一個外部函數。這樣做可以使該函數在外部就能看到,具體方法是單激滑鼠右鍵用「快速查看(Quick View)」功能查看該DLL文件。(如果沒有「快速查看」選項可以從Windows CD上安裝。)TestDll函數會出現在Export Table欄中。另一個很充分的理由是,如果不這樣聲明,我們編寫的函數將不能被調用,這是大家都不願看到的。
三、當使用了長字元串類型的參數、變數時要引用ShareMem。
Delphi中的string類型很強大,我們知道普通的字元串長度最大為256個字元,但Delphi中string類型在默認情況下長度可以達到2G。(對,您沒有看錯,確實是兩兆。)這時,如果您堅持要使用string類型的參數、變數甚至是記錄信息時,就要引用ShareMem單元,而且必須是第一個引用的。既在uses語句後是第一個引用的單元。如下例:
uses
ShareMem,
SysUtils,
Classes;
還有一點,在您的工程文件(*.dpr)中而不是單元文件(*.pas)中也要做同樣的工作,這一點Delphi自帶的幫助文件沒有說清楚,造成了很多誤會。不這樣做的話,您很有可能付出死機的代價。避免使用string類型的方法是將string類型的參數、變數等聲明為Pchar或ShortString(如:s:string[10])類型。同樣的問題會出現在當您使用了動態數組時,解決的方法同上所述。
第三章 在Delphi中靜態調用DLL top
調用一個DLL比寫一個DLL要容易一些。首先給大家介紹的是靜態調用方法,稍後將介紹動態調用方法,並就兩種方法做一個比較。同樣的,我們先舉一個靜態調用的例子。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procere Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
//本行以下代碼為我們真正動手寫的代碼
function TestDll(i:integer):integer;stdcall;
external 』Delphi.dll』;
procere TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text:=IntToStr(TestDll(1));
end;
end.
上面的例子中我們在窗體上放置了一個編輯框(Edit)和一個按鈕(Button),並且書寫了很少的代碼來測試我們剛剛編寫的Delphi.dll。大家可以看到我們唯一做的工作是將TestDll函數的說明部分放在了implementation中,並且用external語句指定了Delphi.dll的位置。(本例中調用程序和Delphi.dll在同一個目錄中。)讓人興奮的是,我們自己編寫的TestDll函數很快被Delphi認出來了。您可做這樣一個實驗:輸入「TestDll(」,很快Delphi就會用fly-by提示條提示您應該輸入的參數是什麼,就像我們使用Delphi中定義的其他函數一樣簡單。注意事項有以
下一些:
一、調用參數用stdcall。
和前面提到的一樣,當引用DLL中的函數和過程時也要使用stdcall參數,原因和前面提到的一樣。
二、用external語句指定被調用的DLL文件的路徑和名稱。
正如大家看到的,我們在external語句中指定了所要調用的DLL文件的名稱。沒有寫路徑是因為該DLL文件和調用它的主程序在同一目錄下。如果該DLL文件在C:\,則我們可將上面的引用語句寫為external 』C:\Delphi.dll』。注意文件的後綴.dll必須寫上。
三、不能從DLL中調用全局變數。
如果我們在DLL中聲明了某種全局變數,如:var s:byte 。這樣在DLL中s這個全局變數是可以正常使用的,但s不能被調用程序使用,既s不能作為全局變數傳遞給調用程序。不過在調用程序中聲明的變數可以作為參數傳遞給DLL。
四、被調用的DLL必須存在。
這一點很重要,使用靜態調用方法時要求所調用的DLL文件以及要調用的函數或過程等等必須存在。如果不存在或指定的路徑和文件名不正確的話,運行主程序時系統會提示「啟動程序時出錯」或「找不到*.dll文件」等運行錯誤。
第四章 在Delphi中動態調用DLL top
動態調用DLL相對復雜很多,但非常靈活。為了全面的說明該問題,這次我們舉一個調用由C++編寫的DLL的例子。首先在C++中編譯下面的DLL源程序。
#include
extern 」C」 _declspec(dllexport)
int WINAPI TestC(int i)
{
return i;
}
編譯後生成一個DLL文件,在這里我們稱該文件為Cpp.dll,該DLL中只有一個返回整數類型的函數TestC。為了方便說明,我們仍然引用上面的調用程序,只是將原來的Button1Click過程中的語句用下面的代碼替換掉了。
procere TForm1.Button1Click(Sender: TObject);
type
TIntFunc=function(i:integer):integer;stdcall;
var
Th:Thandle;
Tf:TIntFunc;
Tp:TFarProc;
begin
Th:=LoadLibrary(』Cpp.dll』); {裝載DLL}
if Th>0 then
try
Tp:=GetProcAddress(Th,PChar(』TestC』));
if Tp<>nil
then begin
Tf:=TIntFunc(Tp);
Edit1.Text:=IntToStr(Tf(1)); {調用TestC函數}
end
else
ShowMessage(』TestC函數沒有找到』);
finally
FreeLibrary(Th); {釋放DLL}
end
else
ShowMessage(』Cpp.dll沒有找到』);
end;
大家已經看到了,這種動態調用技術很復雜,但只要修改參數,如修改LoadLibrary(』Cpp.dll』)中的DLL名稱為』Delphi.dll』就可動態更改所調用的DLL。
一、定義所要調用的函數或過程的類型。
在上面的代碼中我們定義了一個TIntFunc類型,這是對應我們將要調用的函數TestC的。在其他調用情況下也要做同樣的定義工作。並且也要加上stdcall調用參數。
二、釋放所調用的DLL。
我們用LoadLibrary動態的調用了一個DLL,但要記住必須在使用完後手動地用FreeLibrary將該DLL釋放掉,否則該DLL將一直佔用內存直到您退出Windows或關機為止。
現在我們來評價一下兩種調用DLL的方法的優缺點。靜態方法實現簡單,易於掌握並且一般來說稍微快一點,也更加安全可靠一些;但是靜態方法不能靈活地在運行時裝卸所需的DLL,而是在主程序開始運行時就裝載指定的DLL直到程序結束時才釋放該DLL,另外只有基於編譯器和鏈接器的系統(如Delphi)才可以使用該方法。動態方法較好地解決了靜態方法中存在的不足,可以方便地訪問DLL中的函數和過程,甚至一些老版本DLL中新添加的函數或過程;但動態方法難以完全掌握,使用時因為不同的函數或過程要定義很多很復雜的類型和調用方法。對於初學者,筆者建議您使用靜態方法,待熟練後再使用動態調用方法。
第五章 使用DLL的實用技巧 top
一、編寫技巧。
1 、為了保證DLL的正確性,可先編寫成普通的應用程序的一部分,調試無誤後再從主程序中分離出來,編譯成DLL。
2 、為了保證DLL的通用性,應該在自己編寫的DLL中杜絕出現可視化控制項的名稱,如:Edit1.Text中的Edit1名稱;或者自定義非Windows定義的類型,如某種記錄。
3 、為便於調試,每個函數和過程應該盡可能短小精悍,並配合具體詳細的注釋。
4 、應多利用try-finally來處理可能出現的錯誤和異常,注意這時要引用SysUtils單元。
5 、盡可能少引用單元以減小DLL的大小,特別是不要引用可視化單元,如Dialogs單元。例如一般情況下,我們可以不引用Classes單元,這樣可使編譯後的DLL減小大約16Kb。
二、調用技巧。
1 、在用靜態方法時,可以給被調用的函數或過程更名。在前面提到的C++編寫的DLL例子中,如果去掉extern 」C」語句,C++會編譯出一些奇怪的函數名,原來的TestC函數會被命名為@TestC$s等等可笑的怪名字,這是由於C++採用了C++ name mangling技術。這個函數名在Delphi中是非法的,我們可以這樣解決這個問題:
改寫引用函數為
function TestC(i:integer):integer;stdcall;
external 』Cpp.dll』;name 』@TestC$s』;
其中name的作用就是重命名。
2 、可把我們編寫的DLL放到Windows目錄下或者Windows\system目錄下。這樣做可以在external語句中或LoadLibrary語句中不寫路徑而只寫DLL的名稱。但這樣做有些不妥,這兩個目錄下有大量重要的系統DLL,如果您編的DLL與它們重名的話其後果簡直不堪設想,況且您的編程技術還不至於達到將自己編寫的DLL放到系統目錄中的地步吧!
三、調試技巧。
1 、我們知道DLL在編寫時是不能運行和單步調試的。有一個辦法可以,那就是在Run|parameters菜單中設置一個宿主程序。在Local頁的Host Application欄中添上宿主程序的名字就可進行單步調試、斷點觀察和運行了。
2 、添加DLL的版本信息。開場白中提到了版本信息對於DLL是很重要的,如果包含了版本信息,DLL的大小會增加2Kb。增加這么一點空間是值得的。很不幸我們如果直接使用Project|options菜單中Version選項是不行的,這一點Delphi的幫助文件中沒有提到,經筆者研究發現,只要加一行代碼就可以了。如下例:
library Delphi;
uses
SysUtils,
Classes;
{$R *.RES}
//注意,上面這行代碼必須加在這個位置
function TestDll(i:integer):integer;stdcall;
begin
Result:=i;
end;
exports
TestDll;
begin
end.
3 、為了避免與別的DLL重名,在給自己編寫的DLL起名字的時候最好採用字元數字和下劃線混合的方式。如:jl_try16.dll。
4 、如果您原來在Delphi 1或Delphi 2中已經編譯了某些DLL的話,您原來編譯的DLL是16位的。只要將源代碼在新的Delphi 3或Delphi 4環境下重新編譯,就可以得到32位的DLL了。
[後記]:除了上面介紹的DLL最常用的使用方法外,DLL還可以用於做資源的載體。例如,在Windows中更改圖標就是使用的DLL中的資源。另外,熟練掌握了DLL的設計技術,對使用更為高級的OLE、COM以及ActiveX編程都有很多益處。