本篇文章為了熟悉在C#環境下如何調用C(C++)寫的dll。
【轉帖註明出處】
一、首選建立一個C的DLL工程,生產dll檔案用來備用,步驟如下:
1、VS2010建立C++項目內選擇Win32的應用程式,名稱為CreateCDll,在應用程式設定介面內選擇DLL,如下圖:
2、在上圖中在附件選項內選擇【匯出符號】點擊完成,便產生了C++的dll工程,如下圖
可以看到選擇了匯出符號的好處是工程自動幫我們建立了CREATECDLL_API的宏,這個宏是什麼呢,就是我們要匯出到DLL用的關鍵字可以按F12看CREATECDLL_API的原定義:
// 下列 ifdef 塊是建立使從 DLL 匯出更簡單的// 宏的標準方法。此 DLL 中的所有檔案都是用命令列上定義的 CREATECDLL_EXPORTS// 符號編譯的。在使用此 DLL 的// 任何其他項目上不應定義此符號。這樣,源檔案中包含此檔案的任何其他項目都會將// CREATECDLL_API 函數視為是從 DLL 匯入的,而此 DLL 則將用此宏定義的// 符號視為是被匯出的。#ifdef CREATECDLL_EXPORTS#define CREATECDLL_API __declspec(dllexport)#else#define CREATECDLL_API __declspec(dllimport)#endif
上圖中源檔案內有三個檔案,第一個CreateCDll.cpp就是我們要寫的DLL介面函數源檔案(工程自動幫我們建立了三個範例一個變數一個函數一個類,為了簡單我們可以刪除這三段代碼),dllmain.cpp類似於dll的main入口,stdafx.cpp就不做介紹了,好了我們開始在CreaeCDll.cpp內添加我們的介面函數,為了測試我們簡單的寫兩個函數,一個是求兩個數的和函數,第二個是比較兩個數的大小,並輸出最大值,好到此源碼如下:
// CreateCDll.cpp : 定義 DLL 應用程式的匯出函數。//#include "stdafx.h"#include "CreateCDll.h"// 求和CREATECDLL_API int fnAdd(int num1,int num2){return num1 + num2;}// 求最大值CREATECDLL_API int fnMax(int num1,int num2){return (num1 > num2)?num1:num2;}
3、OK到此我們兩個介面已經寫好,我們把工程編譯一下,可以看到在工程的Debug目錄下已經生產了DLL和LIB檔案,如下圖:
4、好了第三步我們已經生產了DLL,下面我們要建立C#的工程來調用剛產生的DLL,我們建立一個C#視窗應用程式名稱為ImportCDll,儲存,添加如下介面:
5、把剛產生的dll檔案拷貝到C#工程的DEBUG目錄下,然後在C#內完成如下代碼:
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Runtime.InteropServices;namespace ImportCDll{ public partial class Form1 : Form { // 匯入求和dll內函數介面 [DllImport("CreateCDll.dll",EntryPoint="fnAdd")] private static extern int fnAdd(int num1, int num2); // 匯入求最大值dll內函數介面 [DllImport("CreateCDll.dll", EntryPoint = "fnMax")] private static extern int fnMax(int num1, int num2); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } // 求和按鈕事件 private void button1_Click(object sender, EventArgs e) { textBox3.Text = string.Format("{0}",fnAdd(Convert.ToInt32(textBox1.Text),Convert.ToInt32(textBox2.Text))); } // 求最大值按鈕事件 private void button2_Click(object sender, EventArgs e) { textBox4.Text = string.Format("{0}", fnMax(Convert.ToInt32(textBox1.Text), Convert.ToInt32(textBox2.Text))); } }}
6、如上代碼已經完成相應操作參見代碼內注釋,需要注意的是需要添加:using System.Runtime.InteropServices;引用才能使用dll,好了編譯運行,當我們點擊求和或者最大值的時候發現報錯了如下圖:
提示找不到函數入口。。。怎麼回事很納悶。。。為了確認是否dll內真的沒有我們兩個函數的入口,我們使用PE Explorer來開啟我們的CreateCDll.dll檔案查看裡面的函數入口名稱,發現如下圖:
可以發現我們函數名字被改變了分別變成了:?fnAdd@@YAHHH@Z(對應fnAdd)和?fnMax@@YAHHH@Z(對應fnMax),怎麼回事,原來編譯器在編譯的時候會自動的在我們的函數上加上標誌字元,因此我們需要在做dll檔案的時候使用def檔案來告訴編譯器不要改變我們的介面名稱,怎麼做呢。如下。
7、如上的錯誤需要在剛才的C的dll工程內添加def檔案來告訴編譯器不要修改我們的函數介面名稱,在工程上右擊【添加】---->【建立項目】,選擇【模組定義檔案(.def)】,如下圖:
輸入mydef,點擊添加,在mydef檔案下添加如下代碼:
LIBRARY "CreateCDll"EXPORTSfnAddfnMax
LIBRARY 後面添加你的dll名稱,注意不需要加.dll;在 EXPORTS下面直接添加你的函數名稱,不需要引號見上面程式碼範例。
好了到此我們再把dll的工程編譯一下,然後用PE Explorer看一下編譯好的dll的函數名稱,發現如下:
名稱已經正確好,OK再次拷貝到我們C#工程的debug目錄下,再次編譯,運行。
8、運行重新編譯,再次點擊求和或者最大值按鈕,又報錯了…………,報錯介面如下:
經過百度-google了一下,發現在dll調用的時候需要把【CallingConvention】的調用方式值修改為【CallingConvention.Cdecl】,預設好像是採用【StdCall】的方式;經修改代碼如下:
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Runtime.InteropServices;namespace ImportCDll{ public partial class Form1 : Form { // 匯入求和dll內函數介面 [DllImport("CreateCDll.dll", EntryPoint = "fnAdd", CallingConvention = CallingConvention.Cdecl)] private static extern int fnAdd(int num1, int num2); // 匯入求最大值dll內函數介面 [DllImport("CreateCDll.dll", EntryPoint = "fnMax", CallingConvention = CallingConvention.Cdecl)] private static extern int fnMax(int num1, int num2); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } // 求和按鈕事件 private void button1_Click(object sender, EventArgs e) { textBox3.Text = string.Format("{0}", fnAdd(Convert.ToInt32(textBox1.Text), Convert.ToInt32(textBox2.Text))); } // 求最大值按鈕事件 private void button2_Click(object sender, EventArgs e) { textBox4.Text = string.Format("{0}", fnMax(Convert.ToInt32(textBox1.Text), Convert.ToInt32(textBox2.Text))); } }}
9、再次編譯運行,OK了效果如下:
OK,到此結束,希望對新手有協助。