平台调用P-INVOKE(二)--(封送字符串)

时间:2023-01-08 19:00:17

 

  可以说新手使用P-INVOKE最开始的头疼就是C#和C++的字符串传递,由于不同编程语言对字符串处理的机制不同,因此导致托管代码的平台调用必须对字符串进行特殊的封送处理。本节将阐述以下几个问题:

(1)、C#的string和C++的字符串首指针如何对应

(2)、字符串还有ANSI和UNICODE(宽字符串)之分

(3)、封送字符串数组

1、通过CharSet字段控制字符串封送行为:

 C++:

void  __cdecl TestString1(char* hello);

void  __cdecl TestString2(const wchar_t* str,wchar_t* outStr,int size);


MSDN上给出C/C++字符串类型与C#字符串类型的对应关系

Wtypes.h 中的非托管类型

非托管C/C++

语言类型

托管类名

说明

CHAR

char

System.Char

用 ANSI 修饰。

LPSTR

char*

System.String 或 System.Text.StringBuilder

用 ANSI 修饰。

LPCSTR

Const char*

System.String 或 System.Text.StringBuilder

用 ANSI 修饰。

LPWSTR

wchar_t*

System.String 或 System.Text.StringBuilder

用 Unicode 修饰。

LPCWSTR

Const wchar_t*

System.String 或 System.Text.StringBuilder

用 Unicode 修饰。


C#

[DllImport("test.dll", EntryPoint = "TestString1", CharSet =CharSet.Ansi)]

public static extern voidTestString1(string hello);

[DllImport("test.dll", EntryPoint = "TestString1", CharSet =CharSet.Unicode)]

public static extern voidTestString2(stringstr,StringBuilderoutStr,int size)

2、使用MarshalAs属性控制字符串封送行为:

       CharSet字段影响的是整个函数过程的字符串封送行为,MarshalAs属性只影响其作用的字符串参数。因此,当一个非托管函数的参数即由ANSI字符串,又有Unicode字符串时,就只能用MarshalAs属性来控制封送行为。

C++:

void  __cdecl TestString3(const char* str1,const wchar_t* str2,wchar_t* outStr,int size);

MSDN给出MarshalAs属性控制字符串封送行为:

枚举类型

非托管格式说明

UnmanagedType.AnsiBStr

长度前缀为双字节的 Unicode字符的COM样式的BSTR。。 

UnmanagedType.LPStr

单字节、null空终止的 ANSI 字符数组的指针。(默认值)

UnmanagedType.LPTStr

null空终止与平台相关的字符数组的指针。 

UnmanagedType.LPWStr

null空终止与Unicode的字符数组的指针。 

UnmanagedType.TBStr

一个有长度前缀的与平台相关的 COM样式的BSTR。

需要注意的是:此表只适用于string类型,对于 StringBuilder而言,能够允许的选项只有:LPStrLPTStr、LPWStr


C#:

[DllImport("test.dll", EntryPoint = "TestString3")]

public static extern voidTestString3(

[MarshalAs(UnmanagedType.LPStr)]string str1,

[MarshalAs(UnmanagedType.LPWStr)]string str2,

[MarshalAs(UnmanagedType.LPWStr)]stringoutStr,

int size);


3、封送作为返回值的字符串:

C++:

char*   __cdecl GetStringReturn1()

wchar_t*__cdecl GetStringReturn2();

这里,有两种声明方法:

(1)、直接用string类型对应:

[DllImport("test.dll", EntryPoint = "GetStringReturn1", CharSet = CharSet.Ansi)]

public static externstringGetStringReturn1();

[DllImport("test.dll", EntryPoint = "GetStringReturn2", CharSet = CharSet.Unicode)]

public static externstring GetStringReturn2();

(2)、用IntPtr指针对应:

[DllImport("test.dll", EntryPoint = "GetStringReturn1", CharSet = CharSet.Ansi)]

public static externIntPtrGetStringReturn1();

[DllImport("test.dll", EntryPoint = "GetStringReturn2", CharSet = CharSet.Unicode)]

public static externIntPtrGetStringReturn2();


GetStringReturn2为例,给出C#如何使用:

string ret="";

IntPtr strPtr=GetStringReturn2();

ret=Marshal.PtrToStringUni(strPtr);

//对于IntPtr传递的变量,需要手工释放非托管内存

Marshal.FreeCoTaskMem(strPtr);  //释放非托管内存是互操作的一个难题,将在后面的章节做专门的介绍

4、封送字符串数组

C++:

int TestArrayOfStrings(char* ppStrArray[], int size);
C#:
[ DllImport( "test.dll" )]
public static extern int TestArrayOfStrings( [In, Out] String[] ppStrArray, int size );
使用:
String[] strArray = { "one", "two", "three", "four", "five" };
int lenSum = LibWrap.TestArrayOfStrings( strArray, strArray.Length );