1. 程式人生 > >C#和C++ 有關DLL的

C#和C++ 有關DLL的

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檔案怎麼載入呢?

  1. 直接加入到工程檔案列表中,在VC中開啟FileView一頁,選中工程名,單擊滑鼠右鍵,然後選中“Add Files to Project”選單,然後選擇LIB檔案。
  2. 開啟工程Projectsetting選單,選中Link,然後再Object/library modules 下的文字框輸入DLL的LIB檔案。
  3. 通過程式碼,加入預編譯指令#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).靜態呼叫其步驟如下:

  1. 把你的youApp.dll拷貝到你的目標工程(youApp.dll的工程)的Debug目錄下;
  2. 把你的youApp.lib拷貝到你的目標工程目錄下;
  3. 把你的youApp.h(包含輸出函式的定義)拷貝到目標工程目錄下;
  4. 開啟目標工程選中的工程,選擇VC++的Project主選單的Setting選單;
  5. 彈出對話方塊,在對話方塊的多頁顯示控制元件中選擇Link頁,在Object/library modules 輸入框中輸入:youApp.lib
  6. 選擇目標工程的標頭檔案加入:youApp.h檔案。
  7. 最後在你的目標工程(*.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 //////////是不是很簡單

當然還可以結合我的另一篇,希望我可以理解的更加透徹。 傳送門

參考1 參考2 參考3