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

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