一、編寫一個簡單的DLLc#
設置爲導出函數,並採用C風格。函數前加extern "C" __declspec(dllexport)。定義函數在退出前本身清空堆棧,在函數前加__stdcall。api
新建一個頭文件,在頭文件中:函數
/* 加入任意你想加入的函數定義*/工具
extern "C" _declspec(dllexport) int _stdcall add(int *x,int *y); // 聲明爲C編譯、連接方式的外部函數
extern "C" _declspec(dllexport) int _stdcall sub(int x,int y); // 聲明爲C編譯、連接方式的外部函數學習
新建一個.cpp文件ui
#include "mydll.h"//貌似這兩個頭文件的順序不能顛倒。我試了不少次,可是不能肯定。spa
int add(int *x,int *y)//是否能夠理解爲,VS2010已經默認是 _stdcall,因此函數不用添加該修飾符
{
return *x+*y;
}指針
int sub(int x,int y)
{
return x-y;
}接口
把導出函數名稱變爲標準名稱,需加模塊定義文件,就是.def文件。字符串
內容以下:(須要註釋,前面加分號就能夠了,註釋須要單獨行)
LIBRARY "TEST"
EXPORTS
;add函數
add
;sub函數
sub
LIBRARY 庫名稱
EXPORTS 須要導出的各個函數名稱
從新編譯以後,再用Depends工具看一下,函數已經變成標準add。這個在動態加載時頗有用,特別是在GetProcAddress函數尋找入庫函數的時候。
二、靜態調用
新建一個C#的控制檯項目,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;//添加
namespace mytest
{
class Program
{
//----------------------------------------------------------------------------------------------
[DllImport("mydll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
extern static int add(ref int a, ref int b);
[DllImport("mydll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
extern static int sub(int a, int b);
//--------------------------------------------------------------------------------------------------
static void Main(string[] args)
{
int a, b,c,d;
a = 1;
b = 2;
c=d= 0;
Console.WriteLine(add(ref a,ref b).ToString());
Console.WriteLine(sub(b,a).ToString());
Console.Read();
}
}
}
三、動態調用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;//添加
namespace mytest
{
class Program
{
[DllImport("kernel32.dll")]
private extern static IntPtr LoadLibrary(String path);//path 就是dll路徑 返回結果爲0表示失敗。
[DllImport("kernel32.dll")]
private extern static IntPtr GetProcAddress(IntPtr lib, String funcName);//lib是LoadLibrary返回的句柄,funcName 是函數名稱 返回結果爲0標識失敗。
[DllImport("kernel32.dll")]
private extern static bool FreeLibrary(IntPtr lib);
//聲明委託
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
delegate int ADD(ref int x, ref int y);
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
delegate int SUB(int x, int y);
static void Main(string[] args)
{
string dllPath = "G:\\VS2010軟件學習\\c#調用C_dll\\mytest\\mytest\\mydll.dll";
string apiName1 = "add";
string apiName2 = "sub";
//使用動態加載
IntPtr hLib = LoadLibrary(dllPath);//加載函數
IntPtr apiFunction1 = GetProcAddress(hLib, apiName1);//獲取函數地址
IntPtr apiFunction2 = GetProcAddress(hLib, apiName2);//獲取函數地址
int i = Marshal.GetLastWin32Error();
if (apiFunction1.ToInt32() == 0)//0表示函數沒找到
return;
if (apiFunction2.ToInt32() == 0)//0表示函數沒找到
return;
//獲取函數接口,至關於函數指針
ADD add1 = (Delegate)Marshal.GetDelegateForFunctionPointer(apiFunction1, typeof(ADD)) as ADD;
SUB sub1 = (SUB)Marshal.GetDelegateForFunctionPointer(apiFunction2, typeof(SUB));
// //調用函數
int a, b,c;
a = 1;
b = 2;
c= 0;
//add1(ref a,ref b);
c=sub1(b,a);
// //釋放句柄
FreeLibrary(hLib );
Console.WriteLine(c.ToString());
//Console.WriteLine(add(ref a,ref b).ToString());
//Console.WriteLine(sub(10,2).ToString());
Console.Read();
}
}
}
注意:
C#時常須要調用C/C++DLL,當傳遞參數時時常遇到問題,尤爲是傳遞和返回字符串時。VC++中主要字符串類型爲:LPSTR,LPCSTR, LPCTSTR, string, CString, LPCWSTR, LPWSTR等,但轉爲C#類型卻不徹底相同。
類型對照:
C/C++----------C#
BSTR --------- StringBuilder
LPCTSTR --------- StringBuilder
LPCWSTR --------- IntPtr
handle---------IntPtr
hwnd-----------IntPtr
char *----------string
int * -----------ref int
int &-----------ref int
void *----------IntPtr
unsigned char *-----ref byte
Struct須要在C#裏從新定義一個Struct
CallBack回調函數須要封裝在一個委託裏,delegate static extern int FunCallBack(string str);
注意在每一個函數的前面加上public static extern +返回的數據類型,若是不加public ,函數默認爲私有函數,調用就會出錯。
在C#調用C++ DLL封裝庫時會出現兩個問題:
1. 數據類型轉換問題 2. 指針或地址參數傳送問題