C#時常須要調用C++DLL

在合做開發時,C#時常須要調用C++DLL,當傳遞參數時時常遇到問題,尤爲是傳遞和返回字符串是,現總結一下,分享給你們:html

VC++中主要字符串類型爲:LPSTR,LPCSTR, LPCTSTR, string, CString, LPCWSTR, LPWSTR等
但轉爲C#類型卻不徹底相同。c++

主要有以下幾種轉換:數組


將string轉爲IntPtr:IntPtr System.Runtime.InteropServices.Marshal.StringToCoTaskMemAuto(string)安全

將IntPtr轉爲string:string System.Runtime.InteropServices.MarshalPtrToStringAuto(IntPtr)函數

類型對照:post

BSTR ---------  StringBuilderui

LPCTSTR --------- StringBuilderthis

LPCWSTR ---------  IntPtr.net

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. 指針或地址參數傳送問題

    首先是數據類型轉換問題。由於C#是.NET語言,利用的是.NET的基本數據類型,因此其實是將C++的數據類型與.NET的基本數據類型進行對應。

    例如C++的原有函數是:

int __stdcall FunctionName(unsigned char param1, unsigned short param2)

    其中的參數數據類型在C#中,必須轉爲對應的數據類型。如:

[DllImport(「 COM DLL path/file 」)]
extern static int FunctionName(byte param1, ushort param2)

    由於調用的是__stdcall函數,因此使用了P/Invoke的調用方法。其中的方法FunctionName必須聲明爲靜態外部函數,即加上 extern static聲明頭。咱們能夠看到,在調用的過程當中,unsigned char變爲了byte,unsigned short變爲了ushort。變換後,參數的數據類型不變,只是聲明方式必須改成.NET語言的規範。

    咱們能夠經過下表來進行這種轉換:

Win32 Types
CLR Type

char, INT8, SBYTE, CHAR
System.SByte

short, short int, INT16, SHORT
System.Int16

int, long, long int, INT32, LONG32, BOOL , INT
System.Int32

__int64, INT64, LONGLONG
System.Int64

unsigned char, UINT8, UCHAR , BYTE
System.Byte

unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t
System.UInt16

unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT
System.UInt32

unsigned __int64, UINT64, DWORDLONG, ULONGLONG
System.UInt64

float, FLOAT
System.Single

double, long double, DOUBLE
System.Double


    以後再將CLR的數據類型表示方式轉換爲C#的表示方式。這樣一來,函數的參數類型問題就能夠解決了。

    如今,咱們再來考慮下一個問題,若是要調用的函數參數是指針或是地址變量,怎麼辦?

    對於這種狀況可使用C#提供的非安全代碼來進行解決,可是,畢竟是非託管代碼,垃圾資源處理很差的話對應用程序是很不利的。因此仍是使用C#提供的ref以及out修飾字比較好。

    同上面同樣,咱們也舉一個例子:

int __stdcall FunctionName(unsigned char &param1, unsigned char *param2)

    在C#中對其進行調用的方法是:

[DllImport(「 file 」)]
extern static int FunctionName(ref byte param1, ref byte param2)

    看到這,可能有人會問,&是取地址,*是傳送指針,爲什麼都只用ref就能夠了呢?一種可能的解釋是ref是一個具備重載特性的修飾符,會自動識別是取地址仍是傳送指針。

    在實際的狀況中,咱們利用參數傳遞地址更多仍是用在傳送數組首地址上。
如:byte[] param1 = new param1(6);

    在這裏咱們聲明瞭一個數組,如今要將其的首地址傳送過去,只要將param1數組的第一個元素用ref修飾。具體以下:

[DllImport(「 file 」)]
extern static int FunctionName(ref byte param1[1], ref byte param2)
 
文章出處:DIY部落(http://www.diybl.com/course/3_program/c++/cppjs/200886/134816.html)

C# 中調用DLL

爲了能用上原來的C++代碼,只好研究下從C# 中調用DLL
首先必需要有一個聲明,使用的是DllImport關鍵字:
包含DllImport所在的名字空間
using System.Runtime.InteropServices;
public class XXXX{

[DllImport(「MyDLL.dll")]
public static extern int mySum (int a,int b);
}


[DllImport(「MyDLL.dll")]
public static extern int mySum (int a,int b);
代碼中DllImport關鍵字做用是告訴編譯器入口點在哪裏,並將打包函數捆綁在這個類中
在調用的時候
在類中的時候 直接   mySum(a,b);就能夠了
在其餘類中調用: XXXX. mySum(a,b);

[DllImport(「MyDLL.dll」)]在申明的時候還能夠添加幾個屬性
[DllImport(「MyDLL.dll", EntryPoint=" mySum ",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)
]
EntryPoint: 指定要調用的 DLL 入口點。默認入口點名稱是託管方法的名稱 。
CharSet: 控制名稱重整和封送 String 參數的方式 (默認是UNICODE)
CallingConvention指示入口點的函數調用約定(默認WINAPI)(上次報告講過的)
SetLastError 指示被調用方在從屬性化方法返回以前是否調用 SetLastError Win32 API 函數 (C#中默認false )


int 類型
[DllImport(「MyDLL.dll")]
//返回個int 類型
public static extern int mySum (int a1,int b1);
//DLL中申明
extern 「C」 __declspec(dllexport)  int WINAPI mySum(int a2,int b2)
{
//a2 b2不能改變a1 b1
//a2=..
//b2=...
return a+b;
}

//參數傳遞int 類型
public static extern int mySum (ref int a1,ref int b1);
//DLL中申明
extern 「C」 __declspec(dllexport)  int WINAPI mySum(int *a2,int *b2)
{
//能夠改變 a1, b1
*a2=...
*b2=...
return a+b;
}


DLL 需傳入char *類型
[DllImport(「MyDLL.dll")]
//傳入值
public static extern int mySum (string  astr1,string bstr1);
//DLL中申明
extern 「C」 __declspec(dllexport)  int WINAPI mySum(char * astr2,char * bstr2)
{
//改變astr2 bstr 2  ,astr1 bstr1不會被改變
return a+b;
}


DLL 需傳出char *類型
[DllImport(「MyDLL.dll")]
// 傳出值
public static extern int mySum (StringBuilder abuf, StringBuilder bbuf );
//DLL中申明
extern 「C」 __declspec(dllexport)  int WINAPI mySum(char * astr,char * bstr)
{
//傳出char * 改變astr bstr -->abuf, bbuf能夠被改變
return a+b;
}

DLL 回調函數

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)



using System;
using System.Runtime.InteropServices;
public delegate bool CallBack(int hwnd, int lParam); //定義委託函數類型
public class EnumReportApp
{
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
public static void Main() {
CallBack myCallBack = new CallBack(EnumReportApp.Report); EnumWindows(myCallBack, 0);
}
public static bool Report(int hwnd, int lParam)
{
Console.Write("Window handle is ");
Console.WriteLine(hwnd); return true;
}
}

DLL  傳遞結構 
BOOL PtInRect(const RECT *lprc, POINT pt);

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit)]
public struct Rect
{
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}
Class XXXX {
[DllImport("User32.dll")]
public static extern bool PtInRect(ref  Rect r, Point p);
}

DLL 回調函數,傳遞結構 想看的msdn裏面都有專題介紹,看的我都是暈暈的:)

其餘參考請搜索:

在C#程序設計中使用Win32類庫
C#中調用C++託管Dll
如何在C#中加載本身編寫的動態連接庫

相關文章:Creating a P/Invoke Library


能用上DLL之後感受仍是很好的,原來的C++代碼只要修改編譯經過就能夠了,
高興沒多久,發現.net2005竟然能夠用VB,VC開發智能設備項目,能夠建立MFC智能設備項目
暈暈,難道能夠直接用MFC來開發smartphone的程序了,趕忙看看,,,

Visual C++ 使用 __declspec(dllexport) 從 DLL 導出 (到C#)

因爲各類的緣由, 如何把unmanaged 的 c++ DLL 轉換成 managed C# 是一個問題。

方法有3個.

Ø 使用.def文件

Ø 能夠不用.def文件, 使用__declspec(dllexport)關鍵字, 特別是針對Visual C++編譯器的時候

Ø 直接用MC++寫

何時用.def文件?

.def的意思是module-definition, 這個純文本的文件定義了模塊的信息。 對於編譯器來講, 一個方法在編譯以後的DLL文件裏, 存在的形式名字可能不是做者當時起的那個,例如好好的函數名字function() 變成了?function2@@YAXXZ; 能夠用undname查看這個被編譯器修飾掉的名字, 原型是"void __cdecl function2(void)". 大概使用的時候就會遇到相似」連接錯誤,未決的外部符號…」 的錯誤.

.def文件主要的做用, 就是」標註」 出這個函數原來的樣子, 這樣編譯器在編譯的時候, 規則上就會以C編譯器的規則來處理, 修飾被去掉了, 另外同時能夠把導出函數的序號值手動的改高一點; 還有一個優勢(也是缺點) 就是能夠用NONAME來修飾函數, 這樣導出函數的序號值就變成了1~N, 即第N個函數. 因此調用GetProcAddress() 的時候, 能夠直接用定義的序號值, 而不用寫函數的名字(可是名字就徹底不可用了), 更好的是, 導出函數的這個DLL會變得比較小, 固然, MSDN強調了一點: 僅你能夠並有權更改這個.def文件內容的時候, 你才能夠用這個辦法.

那麼, 何時考慮用.def文件呢? 由於編譯器不一樣, 而產生的修飾名不一樣的話, 這個文件就是必須的.

注意若是文件沒有導出函數的話, 這個文件可能下降運行效率。

.def文件的格式

LIBRARY FileNameWithoutExtension

EXPORTS

Function1 @1

Function3 @2

Function2 @3 NONAME

啓用 Enable it: Property pages-> Configuration Properties->C/C++ -> Linker -> input -> Module Definition File

那不用.def呢? __declspec(dllexport)的做用

這個東西, 能夠給函數用, 也能夠給類用. 聲明大概這樣子:

 

1

__declspec(dllexport)int__stdcall GetMid(vector<type> ve);

2

class__declspec(dllexport) TestClass{

3

public:

4

TestClass();

5

}

這牽扯到了一個東西就是__stdcall和__cdecl (還有__fastcall, 不過不多用), 其中__cdecl通常是C或者C++的缺省調用規範, 可是最大的一個區別就是__stdcall在返回前自身清除堆棧, 而__cdecl是調用方來作這個事情(可參考COM中的某些機制), 另外一個區別就是__stdcall對於可變參數的函數, 玩不轉.

反正今時今日, 你們都在用__stdcall, 因此這麼寫也沒什麼問題, 但不是沒有. VB裏調用標記着__cdecl的方法, 可能會獲得一個異常Bad DLL Calling Convention. 解決方法也很簡單:

原來的函數

1

long_cdecl PassStr(LPSTRp Str)

2

{       return1;      }

新的函數

1

long_stdcall PassStrStdCall(LPSTRpStr)

2

{       returnPassStr(pStr);      }

問題是, 若是這個函數原型, 參數是可變的, 那又怎麼弄呢?

調用的時候, C#都是這麼寫的:

1

[DllImport("", EntryPoint =@" ?GetMid@@YAXXZ", CharSet = CharSet.Auto)]

2

privatestaticexternvoidGetMid(...);

這個入口點名字還真彆扭, 看來去掉這個修飾仍是蠻須要的, 除了用.def文件, 另外一個辦法就是用 extern 「C」.

Extern 「C」

一句話總結:這個東西能夠去掉修飾名。在不用.def文件的前提下, 這個能夠保證你的函數function() 仍是這個名字.

可是,這個東西對類不太起做用!

這個東西是這麼用的: 放到函數聲明的最前面。 就相似這樣 extern 「C」 void __declspec(dllexport) function(void);

對於類, 通常的作法是, 把它的內部方法(特別是實例方法,或變量),wrap出一個方法來。 見下面的實例.

還要作什麼?

當一個DLL被初始化的時候, 它須要一個入口點, 通常對於非MFC DLL來講, 這樣寫就好了:

01

BOOLAPIENTRY DllMain(HANDLEhModule, 

02

                      DWORD ul_reason_for_call, 

03

                      LPVOID lpReserved

04

                      )

05

{

06

    switch( ul_reason_for_call )

07

    {

08

    caseDLL_PROCESS_ATTACH:

09

    caseDLL_THREAD_ATTACH:

10

    caseDLL_THREAD_DETACH:

11

    caseDLL_PROCESS_DETACH:

12

        break;

13

    }

14

    returnTRUE;

15

}

要注意的是這個入口點的名字必須是DllMain, 若是不是須要修改linker的/entry 選項. 不然對於C的話可能會初始化失敗.

************************************************************************

開始用導出函數 PInvoke

爲了好看一點, 先約定一下:

1

#define EXT_C extern "C" 

2

#define DLLEXPORT __declspec(dllexport)

3

#define EXT_C_DLLEXPORT EXT_C DLLEXPORT

4

#define CALLBACK    __stdcall

5

#define WINAPI      __stdcall

6

#define APIENTRY    WINAPI

1. 普通的函數

1

EXT_C_DLLEXPORT void WINAPI Function();

2

 

3

[DllImport("filename.dll", EntryPoint =" Function")]

4

privatestaticexternvoidFunc();

2. ref或者out

1

EXT_C_DLLEXPORT void WINAPI Function(Type** ty);

2

 

3

[DllImport("filename.dll", EntryPoint =" Function")]

4

privatestaticexternvoidFunc(outType ty);

3. 指針函數和委託

1

Typedef void(CALLBACK *pFunc)(int);

2

EXT_C_DLLEXPORT void WINAPI Compare(inta, intb, pFunc p);

3

privatedelegateintCompareCallback(inta, intb);

4

[DllImport("filename.dll",EntryPoint=」Compare」)]

5

privatestaticexternintCompare(inta, intb, CompareCallback call);

4. 類的處理. 其實不是說不能夠把類標記爲 DLLEXPORT, 若是能夠的話, 固然是wrap比較好

C++裏的原型

1

classDLLEXPORT Test

2

{

3

public:

4

    Test();

5

    ~Test();

6

    BOOLfunction(intpar)

7

};

類被export, 函數調用時候注意用CallingConvention.ThisCall.

1

[DllImport("filename.dll", EntryPoint =@"??4Test@@QEAAAEAV0@AEBV0@@Z", CallingConvention = CallingConvention.ThisCall)]

2

  privatestaticexternintTestFunc(IntPtr hwnd, intpar);

採用了」迂迴」策略, C++裏先這樣定義,同理, 添加構造函數等, 函數就變成了這個樣子:

01

EXT_C_DLLEXPORT BOOLWINAPI function_wrap(Test* t, int par)

02

{

03

    returnt->function(par);

04

}

05

EXT_C_DLLEXPORT Test* Test_ctor()

06

{

07

    Test* t =newTest();

08

    returnt;

09

}

10

EXT_C_DLLEXPORT voidTest_dector(Test* t)

11

{

12

    if(NULL == t)

13

    {

14

        deletet;

15

        t = NULL;

16

    }

17

}

在C#裏這樣寫, 那麼就和平時用沒什麼區別了.

01

publicclassTest : IDisposable

02

        {

03

            privateIntPtr instance;

04

            publicTest()

05

            {

06

                instance = CreateInstance();

07

            }

08

 

09

             ~Test()

10

            {

11

                Dispose(false);

12

            }

13

 

14

            #region pinvoke

15

            [DllImport("filename.dll", EntryPoint =@"Test_ctor")]

16

            privatestaticexternIntPtr CreateInstance();

17

            [DllImport("filename.dll", EntryPoint =@"Test_dector")]

18

            privatestaticexternvoidDestroyInstance(IntPtr hwnd);

19

            [return: MarshalAs(UnmanagedType.Bool)]

20

            [DllImport("filename.dll", EntryPoint =@"function_wrap")]

21

            privatestaticexternboolfunction_wrap(intpar);

22

            #endregion

23

 

24

            #region IDisposable Members

25

 

26

            publicvoidDispose()

27

            {

28

                Dispose(true);

29

            }

30

 

31

            privatevoidDispose(boolbDisposing)

32

            {

33

                if(instance != IntPtr.Zero)

34

                {

35

                    DestroyInstance(instance);

36

                }

37

 

38

                if(bDisposing)

39

                {

40

                    GC.SuppressFinalize(this);

41

                }

42

            }

43

 

44

            #endregion

45

        }

5. Struct操做.

1

typedefstructObject_HANDLE {

2

  unsignedlong     dbch_size;

3

  HANDLE    dbch_handle;

4

  GUID       dbch_eventguid;

5

  BOOLres_flag;

6

} Object_Native_HANDLE ;

首先在C#裏嚴格定義這個,LayoutKind.Sequential 用來保證內存分配的正常。

1

[StructLayout(LayoutKind.Sequential)]

2

publicstructObject_Native_HANDLE

3

{

4

    publiculongdbch_size;

5

    publicIntPtr dbch_handle;

6

    publicGuid dbch_eventGuid;

7

    publicboolres_flag;

8

}

Marshal的使用以下:

1

Object_Native_HANDLE obj =newObject_Native_HANDLE(); //初始化

2

intamountToAllocate = Marshal.SizeOf(obj);//獲取大小

3

IntPtr objPtr = Marshal.AllocHGlobal(amountToAllocate);//分配並獲取空的空間地址

4

Marshal.StructureToPtr(obj, objPtr,false);// 值寫入分配的空間

5

//操做...

6

Marshal.FreeHGlobal(objPtr);//釋放空間

最後提一句, unmaged code中的錯誤, 到managed 之後, 極大多是捕捉不到的。 因此錯誤須要分別處理。

花了很多時間, MC++平時用的很少, 不寫了。

相關文章
相關標籤/搜索