1、編寫一個簡單的DLL
設置為導出函數,並采用C風格。函數前加extern "C" __declspec(dllexport)。定義函數在退出前自己清空堆棧,在函數前加__stdcall。
新建一個頭文件,在頭文件中:
/* 加入任意你想加入的函數定義*/
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文件
#include "mydll.h"//貌似這兩個頭文件的順序不能顛倒。我試了很多次,但是不能確定。
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函數尋找入庫函數的時候。
2、靜態調用
新建一個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();
}
}
}
3、動態調用
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. 指針或地址參數傳送問題
