可以说新手使用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,StringBuilder outStr,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属性控制字符串封送行为:
需要注意的是:此表只适用于string类型,对于 StringBuilder而言,能够允许的选项只有:LPStr、LPTStr、LPWStr。
枚举类型
非托管格式说明
UnmanagedType.AnsiBStr
长度前缀为双字节的 Unicode字符的COM样式的BSTR。。
UnmanagedType.LPStr
单字节、null空终止的 ANSI 字符数组的指针。(默认值)
UnmanagedType.LPTStr
null空终止与平台相关的字符数组的指针。
UnmanagedType.LPWStr
null空终止与Unicode的字符数组的指针。
UnmanagedType.TBStr
一个有长度前缀的与平台相关的 COM样式的BSTR。
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 extern
string
GetStringReturn1
();[DllImport("test.dll", EntryPoint = "
GetStringReturn2
", CharSet = CharSet.Unicode)]public static extern
string
GetStringReturn2
();(2)、用IntPtr指针对应:
[DllImport("test.dll", EntryPoint = "
GetStringReturn1
", CharSet = CharSet.Ansi)]public static extern
IntPtr
GetStringReturn1
();[DllImport("test.dll", EntryPoint = "
GetStringReturn2
", CharSet = CharSet.Unicode)]public static extern
();
IntPtr
GetStringReturn2
以
为例,给出C#如何使用:
GetStringReturn2
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 );