DLL
什麼是DLL,Dynamic Link Library 文件爲動態鏈接庫文件,又稱“應用程序擴展”。在Windows中,許多應用程序並不是一個完整的可執行文件,它們被分割成一些相對獨立的動態鏈接庫,即DLL文件。當我們執行某一個程序時,相應的DLL文件會被調用。一個應用程序可使用多個DLL文件,一個DLL文件也可能被不同的應用程序使用,這樣的DLL文件被稱爲共享DLL文件。
DLL文件中存放的是各類程序的函數實現過程,當程序需要調用函數時,需要先載入DLL(添加引用),然後取得函數的地址,最後進行調用。好處在於程序不需要再運行指出加載所有的代碼,只有在程序需要某個函數的時候從DLL中去除。另外,使用DLL文件還可以減少程序的體積。
但是各種DLL有區別
VC、Delphi或者VB等編程語言編寫的非託管DLL文件,在編譯完成後,產生的DLL文件已經是一個可以直接供計算機使用的二進制文件,而C#生成的DLL不是獨立運行的程序,是託管的。
問題
主要有這麼幾個問題,C++怎麼製作DLL,C#怎麼製作DLL。然後怎麼調用,相互怎麼調用?有哪些方式引用。
C++製作dll
C++創建dll有兩種方法:一種使用_declspec(dllexport)創建dll.二是使用模塊定義文件(.def)文件創建dll.
舉例說明:
(1)用_declspec(dllexport)
創建Win32 Project 一個空的動態鏈接庫工程。
然後添加代碼如下:
extern "C" _declspec (dllexport) int add(int a, int b)//extren "C"是爲了解決C++和C語言之間相互調用函數命名的問題,而用模塊定義文件(.def)可以避免。
{
return a+b;
}
編譯後在Debug下面會生成.dll和.lib文件
.dll是動態鏈接庫,在程序運行時鏈接(run-time linked),爲PE(portable executable)格式。像exe、dll、fon、mod等等都是動態鏈接庫。
.lib靜態鏈接庫,在編譯時與程序鏈接(link-time linked),會嵌入到陳旭中,在應用時需要在源代碼中引用lib對應的頭文件.h這些頭文件會告訴編譯器.lib中有什麼。
一般在生成.dll時會伴生一個.lib,這個.lib被編譯到程序文件中,在程序運行時告訴操作系統將要加載的dll.其中還包括文件名,順序表。
(2)用模塊定義文件(.def)文件創建dll.
函數寫成原始的樣子比如
int add(int a,int b)
{
return a+b;
}
然後爲工程創建一個後綴名爲.def的文件,並添加進工程,編輯其內容爲:
LIBRARY DLLname
EXPORTS
add//函數名
該模塊定義文件需要連接到工程中,方法爲工程屬性頁面>鏈接器>輸入>模塊定義文件中寫入.def文件。編譯。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
C++生成dll還有一種方法
ATL技術
在COM技術的創建給開發帶來了諸多的便利,但是卻有點難。爲了簡化COM編程,提高開發效率,人們想了很多辦法。1995年的時候,微軟推出了一種全新的COM開發工具ATL。ATL是ActiveX Template Library 的縮寫,它是一套C++模板。
個人在工作中也用了ATL技術。他會自動生成DLL文件,需要你 注意幾個文件。
、、、、、、、、、、、、、
“MyATL_i.h”、“MyATL_i.c”(這個文件主要用來查看CLSID_MyATLClass和IID_IMyATLClass的值)
、、、、、、、、、、、、、
還有一個.idl文件。
IDL(接口描述語言)_
interface IMyATLClass : IDispatch{
[id(1)] HRESULT Sum([in] LONG para1, [in] LONG para2, [out] LONG* sum);
[id(2)] HRESULT PopupDialog([in] CHAR* text);
library MyATLLib中類定義的順序決定了GetTypeInfo中index參數的值
、、、、、、、、、、
應有的文件如下圖:
(網上down來的)
例子如下:
//在CPP裏面寫
STDMETHODIMP MyClass::Cal()
{
...
return S_OK;
}
//在.h文件裏面寫
STDMETHOD Cal();
//在idl裏面寫
[id(5)] HRESULT Cal( [in] int i,[out,retval] int* pVal);//in代表輸入 ,out代表 輸出,retval代表返回值。
然後就會 生成一個接口指針,我們在C#裏面就可以通過指針讀了。
比如
#region 程序集 Interop.WaxCal.dll,v1.1.0.0;
//D:\...\Interop.WaxCal.dll
#endregion
using System;
using System.Runtime.InteropServices;
namespace Interop.WaxCal
{
[Guid("7CECEF52-A386-47CD-A8FA-AB9A463A23D1")]//是不是跟COM組件很像啊。
[TypeLibType(TypeLibTypeFlags.FDual | TypeLibTypeFlags.FNonExtensible | TypeLibTypeFlags.FDispatchable)]
public interface ICal
{
//可以是類,也可以是函數
[DispId(2)]
WaxCar waxCar{get;set;}
[Displd(3)]
void WaxAdd{get;set;}
//按順序會自動添加 。
}
}
我們要用 的話就定義一個指針對象。
IWaxCal wax;
wax. 裏面註冊好了的東西就會出來 。
C#製作DLL
文件–新建–項目–類庫。
建立了之後生成就可以了,在bin目錄下的Debug下面找生成這個類庫的DLL文件,接下來就可以拷貝到其他項目中引用了。
//////////////////////////////////////////////////////////////////////////////////////////////////////////
還有一種方法,利用COM組件。生成DLL.
1.新建一個類庫項目,命名爲ComTestDLL。進去後將默認的類Class1改成規範的名字如ComTest.cs,系統會提醒是否給類改名,選確定;
2.修改Properties目錄下面的AssemblyInfo.cs,將ComVisible屬性設置爲true;然後點擊項目-屬性在生成的選項卡的底部位置勾選“爲COM互操作註冊”;如果想要加密的話,在簽名選項卡上勾選爲程序集簽名,創建一個強名稱密鑰,舉例代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ComTestDLL
{
[Guid("EBD4A297-3BEE-4F29-B6BC-85D9DED1FFD8")]
public interface IComTest
{
[DispId(1)]//指定屬性,字段,函數的COM調度標識符
int Add(int x, int y);
[DispId(2)]
string AddString(string a, string b);
}
[Guid("E580CBE7-A8E2-4375-A949-C85D6315A326")]
[ProgId("ComTestDLL.IComTest")]//允許用戶指定類的ProgId
[ClassInterface(ClassInterfaceType.None)]//設置com接口類的類型
public class ComTest : IComTest
{
public int Add(int x, int y)
{
return x + y;
}
public string AddString (string a,string b)
{
return a+b;
}
}
}
在代碼中,需要加入DispId特性和GUID特性。DispId按順序編號即可,但是GUID需要自己生成,步驟爲工具-創建GUID-選擇第5項,複製就可以。
生成解決方案後,會生成dll和tlb兩個文件,到此則已經完成C#端的工作了。
一般會報錯,因爲你不是用管理員身份來運行的。所以現在知道用COM組件時最好用管理員註冊,不然會報錯否則生成解決方案時會出現對註冊表項XXX的訪問被拒絕的錯誤。
C++怎麼引用C++生成的DLL
應用程序如果想要訪問某個dll中的函數,那麼這個函數必須是已經被導出函數。
如果想要查那些函數被導出了,可以用VS裏面提供的一個命令行工具:Dumpbin.
怎麼用Dumpbin呢?首先需要運行一個批處理文件,一般位於VC\bin目錄下,該文件的作用更是用來創建VC++使用的環境信息。
然後輸入dumpbin命令,即可列出該命令的方法。
如果想查看dll提供的導出函數,在DLL文件所在的目錄下,在命令行輸入 dumpbin -exports DLL.dll
1.隱式的加載時鏈接
這個需要伴生的LIB文件,運行時系統會尋找這個DLL,尋找路徑如下:
- 可執行文件所在的目錄
- 當前程序的工程目錄
- 系統目錄(GetSystemDirectory列表內容函數可以得到)
- windows目錄
- 列在PATH環境變量中的所有目錄
那麼LIB文件怎麼加載呢?
- 直接加入到工程文件列表中,在VC中打開FileView一頁,選中工程名,單擊鼠標右鍵,然後選中“Add Files to Project”菜單,然後選擇LIB文件。
- 打開工程Projectsetting菜單,選中Link,然後再Object/library modules 下的文本框輸入DLL的LIB文件。
- 通過代碼,加入預編譯指令#pragma comment(lib,"*.lib"),這種方法的優點是可以利用條件預編譯指令鏈接不同的版本的LIB文件,如Debug方式產生的Debug版本,如Debug方式產生的Debug版本(如Waxd.lib),或者Release版本(Waxr.lib)。
//一般第三種方法比較多
//Dlltest.h
#pragma comment(lib,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);//隱式鏈接需要就加上去
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include
#include"Dlltest.h"
void main()
{
int a;
a=min(8,10)
printf("比較的結果爲%d\n",a);
}
2.顯式的運行時鏈接
顯式鏈接需要頭文件和LIB文件,有點多,如果只提供DLL文件的話,就只能用顯式鏈接,調用API函數來對DLL文件進行加載與卸載。
-
使用Windows API函數Load
Library或者MFC提供的AfxLoadLibrary將DLL模塊映像到進程的內存空間,對DLL模塊進行動態加載。 -
使用GetProcAddress函數得到要調用DLL中的函數的指針。
-
不用DLL時,用FreeLibrary函數或者AfxFreeLibrary函數從進程的地址空間顯式卸載DLL。
-
使用顯式鏈接應用程序編譯時不需要使用相應的Lib文件。另外,使用GetProcAddress()函數時,可以利用MAKEINTRESOURCE()函數直接使用DLL中函數出現的順序號,如將GetProcAddress(hDLL,”Min”)改爲GetProcAddress(hDLL,MAKEINTRESOURCE(2))(函數Min()在DLL中的順序號是2),這樣調用DLL中的函數速度很快,但是要記住函數的使用序號,否則會發生錯誤。
那麼DLL怎麼調用呢?兩種方法(一般用動態調用)
(1).靜態調用其步驟如下:
- 把你的youApp.dll拷貝到你的目標工程(youApp.dll的工程)的Debug目錄下;
- 把你的youApp.lib拷貝到你的目標工程目錄下;
- 把你的youApp.h(包含輸出函數的定義)拷貝到目標工程目錄下;
- 打開目標工程選中的工程,選擇VC++的Project主菜單的Setting菜單;
- 彈出對話框,在對話框的多頁顯示控件中選擇Link頁,在Object/library modules 輸入框中輸入:youApp.lib
- 選擇目標工程的頭文件加入:youApp.h文件。
- 最後在你的目標工程(*.cpp,需要調用DLL中的函數)中包含你的#include “youApp.h”
注:youApp是你dll的工程名。
(2)動態調用其步驟如下:
把youApp.dll拷貝到目標工程的Debug目錄下。
{
HINSTANCE hDllInst = LoadLibrary("youApp.DLL");
if(hDllInst)
{ typedef DWORD (WINAPI*MYFUNC)(DWORD,DWORD);
MYFUNCyouFuntionNameAlias=NULL;
// youFuntionNameAlias 函數別名
youFuntionNameAlias= (MYFUNC)GetProcAddress(hDllInst,"youFuntionName");
// youFuntionName 在DLL中聲明的函數名
if(youFuntionNameAlias)
{
youFuntionNameAlias(param1,param2);
}
FreeLibrary(hDllInst);
}
}
//再貼一段代碼參考
void CXXXDlg::OnBtnSubtract()
{
// TODO: Add your control notification handler code here
HINSTANCE hInst;
hInst = LoadLibrary(L"Dll1.dll");
typedef int(*SUBPROC)(int a, int b);
SUBPROC Sub = (SUBPROC)GetProcAddress(hInst, "subtract");
CString str;
str.Format(_T("5-3=%d"), Sub(5, 3));
FreeLibrary(hInst); //LoadLibrary後要記得FreeLibrary
MessageBox(str);
}
顯式(靜態)調用:LIB+DLL+.H ,注意.H中的dllexport改爲dllimport
隱式(動態)調用:DLL+函數原型聲明,先LoadLibrary,再GetProcAddress(即找到DLL中的函數的地址),不用後FreeLibrary;
__declspec(dllexport) 聲明一個導入函數,從本DLL文件導出去,給別人用。一般在DEF文件中定義導出哪些函數的方法,但是如果DLL裏面全部是C++的類的話,就無法從DEF裏指定導出的函數,只能用這個函數。
__declspec(dllimport) 聲明一個導出函數,給我自己用,從別的DLL導入進來的。
C#怎麼引用C#生成的DLL
直接在解決方案中【引用】-【添加引用】-【瀏覽】-【尋找路徑,但是最好都拷貝到bin下面的Debug下面】-【確認】。
添加引用了之後需要添加:
using 類庫名;
C++怎麼調用C#生成的DLL
可以參考我的另外一篇博文。用C++調用C#生成的dll
首先先簡介一下:
C++編寫的程序爲非託管代碼,C#編寫的程序爲託管代碼。託管代碼雖然提供了其他
方法一:clr的方法調用
//C#創建的DLL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace 命名空間
{
public class 測試類
{
public int 測試函數(int x,int y)
{
return x + y;
}
}
}
有幾點是要注意的:
- 使用#using 引用C#dll,而不是#include
- 別忘了using namespace 命名空間
- 使用C++/clr語法,採用正確的訪問託管對象,即:使用“^”,而不是用“*”。
- C++編譯設置一定設置爲:支持公共語言運行時支持(/clr)
- 從C#dll獲取的字符串爲System::String^,在C++中需要轉化爲string .
//然後創建C++項目
#include "stdafx.h"//項目自帶
#using "../debug/你dll的名稱.dll" //注意不要用#include
using namespace 命名空間
int _tmain(int argc ,_TCHAR* argv[])
{
int x,y,testResults;
x = 10;
y = 20;
測試類 ^a = gcnew 測試類(); //創建了一個託管對象,放在gc堆裏用^不用*是因爲C++/clr語法的原因
testResults=a->測試函數(x,y);
printf("計算結果爲:%d",testResults);
return 0;
}
方法二:借用COM組件來調用
分兩種情況,一個是在本機開發的,且勾選了“爲COM互操作註冊”選項,在生成解決方案時已經在本機將該dll註冊爲COM組件,所以運行時不需要再註冊。
但是如果是在其他機器上運行的,需要註冊dll爲COM組件後纔可以使用。我們可以用regasm.exe生成註冊表文件供使用者將dll註冊爲COM組件(其實就是把GUID導入註冊表)
腳本文件如下:
regasm E:\\...\CalcClass\bin\Debug\CalcClass.dll
regasm E:\\...\CalcClass\bin\Debug\CalcClass.dll /tlb: CalcClass.tlb
regasm E:\\...\CalcClass\bin\Debug\CalcClass.dll /regfile: CalcClass.reg
注意使用的regasm.exe版本與開發dll所使用的.NET Framework版本最好保持一致。
運行該腳本生成CalcClass.reg文件,在其他機器上運行該文件,即可註冊該COM組件,才能正常使用。
、、、、、、、、
下面是將dll封裝爲COM組件。
新建工作空間,選擇Win32 Dynamic - Link Library ,類型爲簡單DLL工程。
將生成的dll和tlb兩個文件拷貝至工作空間目錄下。
在StdAfx.h頭文件下增加以下兩行代碼導入dll.(也可以嘗試在通用屬性-框架和引用中添加新的引用)
#import "WaxClass.tlb"
using namespace WaxCal;
在cpp文件中添加以下方法聲明,也可以創建頭文件後包含進來。
extern “C” _declspec(dllexport) BOOL Add (char *a,char *b,long* c);
extren "C" _declspec(dllexport)void Join(char * a,char *b,char *c);
實現聲明的兩個方法:
BOOL Add(char * a,char * b,long *c)
{
CoInitialize(NULL);
CalcClass::ICalcPtr CalcPtr(_uuidof(Waxc));
VARIANT_BOOL ret = CalcPtr->Add(_bstr_t(a),_bstr_t(b),c);//VARIANT_BOOL中,-1表示true,0表示false.
CalcPtr->Release();
CoUninitialize();
if (ret == -1)
return 1;return 1;
else
return ret;return ret;
}
void Join (char * a,char *b ,char *c)
{
CoInitialize(NULL);
CalcClass::ICalcPtr CalcPtr(__uuidof(Calc));//獲取Calc所關聯的GUID
BSTR temp;
CalcPtr->Join(_bstr_t(a),_bstr_t(b),&temp);
strcpy(c , _com_util::ConvertBSTRToString(temp));
CalcPtr->Release();
CoUninitialize();
}
編譯成功後則完成了dll封裝爲COM組件的任務。(相當於用C++的COM封裝了C#,然後C++就可以用了)
HINSTANCE calc;
calc = LoadLibrary(TEXT("CalcCom.dll"));
if (NULL == calc)
{
MessageBox("cant't find dll");
return;
}
Add _Add=(Add)::GetProcAddress(calc,"Add");
if (NULL == _Add)
{
MessageBox("cant't find function");
return;
}
else
{
ret = _Add(A,B,&result);
CString boxMsg;
boxMsg.Format("Reslut: %d\nMessage:%ld\n",ret,result);
MessageBox(boxMsg);
}
微軟官方竟然也有說明還有例子,,,可以直接看。
完整的官方說明
C#怎麼調用C++生成的DLL
用buildrcw.bat.裏面的代碼是這樣的。
@echo off
regsvr32 /s MyDLL.dll
“C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\TlbImp.exe” MyDLL.dll /out: Interop.MyDLL.dll
pause
//////////是不是很簡單
當然還可以結合我的另一篇,希望我可以理解的更加透徹。
傳送門