1. 程式人生 > 其它 >在VS2010上使用C#呼叫非託管C++生成的DLL檔案(圖文講解) 背景

在VS2010上使用C#呼叫非託管C++生成的DLL檔案(圖文講解) 背景

背景

     在專案過程中,有時候你需要呼叫非C#編寫的DLL檔案,尤其在使用一些第三方通訊元件的時候,通過C#來開發應用軟體時,就需要利用DllImport特性進行方法呼叫。本篇文章將引導你快速理解這個呼叫的過程。

步驟

1. 建立一個CSharpInvokeCPP的解決方案:

2. 建立一個C++的動態庫專案:

3. 在應用程式設定中,選擇“DLL”,其他按照預設選項:

最後點選完成,得到如圖所示專案:

      我們可以看到這裡有一些檔案,其中dllmain.cpp作為定義DLL應用程式的入口點,它的作用跟exe檔案有個main或者WinMain入口函式是一樣的,它就是作為DLL的一個入口函式,實際上它是個可選的檔案。它是在靜態連結時或動態連結時呼叫LoadLibrary和FreeLibrary時都會被呼叫。詳細內容可以參考(

http://blog.csdn.net/benkaoya/archive/2008/06/02/2504781.aspx)。

4. 現在我們開啟CSharpInvokeCPP.CPPDemo.cpp檔案:

現在我們加入以下內容:

// CSharpInvokeCPP.CPPDemo.cpp : 定義 DLL 應用程式的匯出函式。
//
 
#include "stdafx.h"
 
extern "C" __declspec(dllexport) int Add(int x, int y)
{
    return x + y;
}
extern "C" __declspec(dllexport) int Sub(int x, int y)
{
    return x - y;
}
extern "C" __declspec(dllexport) int Multiply(int x, int y)
{
    return x * y;
}
extern "C" __declspec(dllexport) int Divide(int x, int y)
{
    return x / y;
}

      extern "C" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標是“extern”的;其次,被它修飾的目標是“C”的。而被extern "C"修飾的變數和函式是按照C語言方式編譯和連線的。

      __declspec(dllexport)的目的是為了將對應的函式放入到DLL動態庫中。

      extern "C" __declspec(dllexport)加起來的目的是為了使用DllImport呼叫非託管C++的DLL檔案。因為使用DllImport只能呼叫由C語言函式做成的DLL。

5. 編譯專案程式,最後在Debug目錄生成CSharpInvokeCPP.CPPDemo.dll和CSharpInvokeCPP.CPPDemo.lib

我們用反編譯工具PE Explorer檢視下該DLL裡面的方法:

可以發現對外的公共函式上包含這四種“加減乘除”方法。

6. 現在來演示下如何利用C#專案來呼叫非託管C++的DLL,首先建立C#控制檯應用程式:

7. 在CSharpInvokeCSharp.CSharpDemo專案上新建一個CPPDLL類,編寫以下程式碼:

public class CPPDLL
{
    [DllImport("CSharpInvokeCPP.CPPDemo.dll")]
    public static extern int Add(int x, int y);
 
    [DllImport("CSharpInvokeCPP.CPPDemo.dll")]
    public static extern int Sub(int x, int y);
 
    [DllImport("CSharpInvokeCPP.CPPDemo.dll")]
    public static extern int Multiply(int x, int y);
 
    [DllImport("CSharpInvokeCPP.CPPDemo.dll")]
    public static extern int Divide(int x, int y);
}

DllImport作為C#中對C++的DLL類的匯入入口特徵,並通過static extern對extern “C”進行對應。

8. 另外,記得把CPPDemo中生成的DLL檔案拷貝到CSharpDemo的bin目錄下,你也可以通過設定【專案屬性】->【配置屬性】->【常規】中的輸出目錄:

這樣編譯專案後,生成的檔案就自動輸出到CSharpDemo中了。

9. 然後在Main入口編寫測試程式碼:

static void Main(string[] args)
{
    int result = CPPDLL.Add(10, 20);
    Console.WriteLine("10 + 20 = {0}", result);
 
    result = CPPDLL.Sub(30, 12);
    Console.WriteLine("30 - 12 = {0}", result);
 
    result = CPPDLL.Multiply(5, 4);
    Console.WriteLine("5 * 4 = {0}", result);
 
    result = CPPDLL.Divide(30, 5);
    Console.WriteLine("30 / 5 = {0}", result);
 
    Console.ReadLine();
}

執行結果:

方法得到呼叫。 

10. 以上的方法只能通過靜態方法對於C++中的函式進行呼叫。那麼怎樣通過靜態方法去呼叫C++中一個類物件中的方法呢?現在我在CPPDemo專案中新增一個頭檔案userinfo.h: 

class UserInfo {
private:
    char* m_Name;
    int m_Age;
public:
    UserInfo(char* name, int age)
    {
        m_Name = name;
        m_Age = age;
    }
    virtual ~UserInfo(){ }
    int GetAge() { return m_Age; }
    char* GetName() { return m_Name; }
};

在CSharpInvokeCPP.CPPDemo.cpp中,新增一些程式碼:

#include "malloc.h"
#include "userinfo.h"
 
typedef struct {
    char name[32];
    int age;
} User; 
 
UserInfo* userInfo;
 
extern "C" __declspec(dllexport) User* Create(char* name, int age)   
{  
    User* user = (User*)malloc(sizeof(User));
 
    userInfo = new UserInfo(name, age);
    strcpy(user->name, userInfo->GetName()); 
    user->age = userInfo->GetAge();
 
    return user;
}

這裡宣告一個結構,包括name和age,這個結構是用於和C#方面的結構作個對映。

注意:程式碼中的User*是個指標,返回也是一個物件指標,這樣做為了防止方法作用域結束後的區域性變數的釋放。

strcpy是個複製char陣列的函式。

11. 在CSharpDemo專案中CPPDLL類中補充程式碼:

[DllImport("CSharpInvokeCPP.CPPDemo.dll")]
public static extern IntPtr Create(string name, int age);
 
[StructLayout(LayoutKind.Sequential)]
public struct User
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string Name;
 
    public int Age;
}

其中這裡的結構User就和C++中的User對應。

12. 在Program.cs中補充程式碼:

IntPtr ptr = CPPDLL.Create("李平", 27);
<strong><font color="#ff0000">CPPDLL.User user = (CPPDLL.User)Marshal.PtrToStructure(ptr, typeof(CPPDLL.User));</font></strong>
Console.WriteLine("Name: {0}, Age: {1}", user.Name, user.Age);

注意:紅色字型部分,這裡結構指標首先轉換成IntPtr控制代碼,然後通過Marshal.PtrToStructrue轉換成你所需要的結構。

執行結果:

最後附上我的原始碼:CSharpInvokeCPP.rar,希望對大家有所幫助:)