动态链接库中函数的参数传递问题!!! 在动态链接库中有一个函数,返回的是字符串,但是这个字符串中有“\0”字符,用什么方法可以将字符串全部传递过来? 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 动态库中的函数原型:void func(char *buffer);// 可以返回'\0'字符 直接的char *,一点都不考虑Unicode字符?至少你也来个TCHAR啊。建议去看看Win32 API都是怎么定义的。至于lz的问题,我贴上自己的一篇文章中的一段,希望能有所帮助。字符串在.NET中只有一个字符串类型——String,而在非托管程序中,事情可就复杂多了。字符指针和含有字符数组的结构都需要被恰当的封送(marshaled)。在Win32中有两种字符串的表示方法: ANSI Unicode最开始的Windows系统使用单字节字符,这样可以有效的节约存储空间,不过为了处理多语言的问题使用了复杂的多字节编码方式。Windows NT出现之后,它使用了双字节Unicode编码。为了处理这两者之间的不同,Win32 API使用了一个小技巧,它定义了一个TCHAR类型,该类型在Win9X平台上为单字节字符,而在Win NT平台上则是双字节Unicode字符。对了所有使用字符数据的字符串或者结构作为参数的函数,Win32 API定义了两个版本,A后缀说明为Ansi版本,W说明为宽字节字符版本(也就是Unicode版本)。你可根据目标平台的不同选择适当的编译方式。因为P/Invoke的设计者并不想让设用者陷于指出诸如使用何种平台这样的细节当中,所以他们为自动选择使用A或者W版本提供了内建的支持。如果你调用的函数不存在,interop层将会自动搜索对应的A或W版本的函数来代替。因为字符串的存在有多种形式,下面我们将对这些形式作一一讲解:简单的字符串这是一个简单的使用字符串的例子: BOOL GetDiskFreeSpace( LPCTSTR lpRootPathName, // 根路径 LPDWORD lpSectorsPerCluster, // 每个簇的扇区数 LPDWORD lpBytesPerSector, // 每个扇区的字节数 LPDWORD lpNumberOfFreeClusters, // 空闲的簇 LPDWORD lpTotalNumberOfClusters // 总的簇 );注意根路径被定义为LPCTSTR,这是一种与平台相关的字符串指针形式。因为没有函数的名字叫GetDiskFreeSpace(),封送器(marshaler)将会自动查找A或者W变量,调用对应的函数,而我们需要通过属性来告诉封送器API到底需要什么样的字符串类型。 [DllImport("kernel32.dll")] static extern bool GetDiskFreeSpace( [MarshalAs(UnmanagedType.LPTStr)] string rootPathName, ref int sectorsPerCluster, ref int bytesPerSector, ref int numberOfFreeClusters, ref int totalNumberOfClusters);不过很不幸,这样不行。问题在于在默认情况下封送器总是试图找到API的Ansi版本,而不管我们当前所处的平台是什么,想想看,封送器在Windows NT平台下想找到使用Unicode字符串参数的Ansi函数还真是不容易啊。要让封送器不再干这么愚蠢的事情,需要我们告诉它自己去确定到底应该使用A版本或者W版本的函数: [DllImport("kernel32.dll", CharSet = CharSet.Auto)]使用上面这个小技巧已经可以处理大多数Win32 API中的字符串类型了,不过总有一小部分API函数不是那么普通,下面让我们继续看看一些不使用上述机制的家伙,我们必须另辟蹊径来解决它们:字符串缓冲区.NET中的字符串属于不可变类型,这意味着字符串对象的值是不能修改的。对于那些需要拷贝字符串值到字符串缓冲区的函数来说,使用String类型就不能奏效了。如果你非要这么做,好的情况可能是破坏封送器创建的临时缓冲区;坏的情况就可能会破坏.NET的托管堆(managed heap),这下可就会有无法预料的情况发生了。不过.NET早就为我们准备好了应对之策,StringBuilder类型可以象一个缓冲区一样容纳必要的内容: [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern int GetShortPathName( [MarshalAs(UnmanagedType.LPTStr)] string path, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath, int shortPathLength);用法也很简单: StringBuilder shortPath = new StringBuilder(80); int result = GetShortPathName( @"d:\test.jpg", shortPath, shortPath.Capacity); string s = shortPath.ToString();需要注意的地方是StringBuilder的容量就是缓冲区的大小。内嵌字符数组的结构除了上面说的两种情况,还有一些函数需要内嵌字符数组的结构作为参数。比如GetTimeZoneInformation()函数就需要一个指针来指向下面这个结构: typedef struct _TIME_ZONE_INFORMATION { LONG Bias; WCHAR StandardName[ 32 ]; SYSTEMTIME StandardDate; LONG StandardBias; WCHAR DaylightName[ 32 ]; SYSTEMTIME DaylightDate; LONG DaylightBias; } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION;要在C#语言中使用这个函数需要两个结构,第一个非常简单,没什么好说的: struct SystemTime { public short wYear; public short wMonth; public short wDayOfWeek; public short wDay; public short wHour; public short wMinute; public short wSecond; public short wMilliseconds; }不过TimeZoneInformation的定义就有些复杂了: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct TimeZoneInformation { public int bias; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string standardName; SystemTime standardDate; public int standardBias; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string daylightName; SystemTime daylightDate; public int daylightBias; }在以上的定义有两个需要我们重点注意的地方。第一个是MarshalAs属性的定义方式: [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]MSDN中的说明是:“ByValTStr用于在结构中出现的内联定长字符数组。与 ByValTStr 一起使用的字符类型由应用于包含结构的 System.Runtime.InteropServices.StructLayoutAttribute 的 System.Runtime.InteropServices.CharSet 参数确定。应始终使用 MarshalAsAttribute.SizeConst 字段来指示数组的大小。”有了这段说明,我想不必再多说什么了吧。当我第一次编写这段代码时出现过执行引擎错误,这往往意味着你在调用过程中传递了错误的结构大小,导致了一些不属于你的内存被覆盖了。我使用Marshal.SizeOf()方法来获取使用的封送器的大小为108个字节。我查找了一些资料,很快想起来在默认情况下调用默认的字符类型是Ansi类型,而结构中定义的却是WCHAR,标准的双字节类型,是它导致了这个问题。我为了修改这个问题,添加了StructLayout属性,Sequential布局方式是结构的默认方式,说明对象的成员按照它们在被导出到非托管内存时出现的顺序依次布局。那为什么CharSet值需要设为Unicode,而不是我们一开始所说得那样使用Auto呢?这是因为这个函数和前面讨论的函数不同,它没有A和W版本的变量之分,总是使用Unicode字符串,所以我做出了设定Unicode属性的决定,让它能够总是能够使用正确的字符类型。 注意,vc ++ 的 char 在 c# 里应当用 byte 来代替。 C#关于多线程与进度条 C# sqlite 数据插入数据失败 C# 怎样开发数据库离线应用?当可以连接服务端DB时,将本地DB同步到服务端DB? 公共类实例问题。。。 求助高手c#断点调试问题 求教,网络上看电影,看的人越多,速度越块的原理 2005 的asp.net为什么不会自动生成dll了呀?求答案~~~ 帮我看看是什么问题造成的错误! 在Web上如何实现水晶报表的打印!!如何实现将导出文件保存到本地!!! DataList控件的问题 发布c#开发的休闲游戏-偷心贼(也就是贪吃蛇) 如何读取关联表?
// 可以返回'\0'字符
建议去看看Win32 API都是怎么定义的。至于lz的问题,我贴上自己的一篇文章中的一段,希望能有所帮助。字符串
在.NET中只有一个字符串类型——String,而在非托管程序中,事情可就复杂多了。字符指针和含有字符数组的结构都需要被恰当的封送(marshaled)。
在Win32中有两种字符串的表示方法:
 ANSI
 Unicode
最开始的Windows系统使用单字节字符,这样可以有效的节约存储空间,不过为了处理多语言的问题使用了复杂的多字节编码方式。Windows NT出现之后,它使用了双字节Unicode编码。为了处理这两者之间的不同,Win32 API使用了一个小技巧,它定义了一个TCHAR类型,该类型在Win9X平台上为单字节字符,而在Win NT平台上则是双字节Unicode字符。对了所有使用字符数据的字符串或者结构作为参数的函数,Win32 API定义了两个版本,A后缀说明为Ansi版本,W说明为宽字节字符版本(也就是Unicode版本)。你可根据目标平台的不同选择适当的编译方式。
因为P/Invoke的设计者并不想让设用者陷于指出诸如使用何种平台这样的细节当中,所以他们为自动选择使用A或者W版本提供了内建的支持。如果你调用的函数不存在,interop层将会自动搜索对应的A或W版本的函数来代替。
因为字符串的存在有多种形式,下面我们将对这些形式作一一讲解:
简单的字符串
这是一个简单的使用字符串的例子:
BOOL GetDiskFreeSpace(
LPCTSTR lpRootPathName, // 根路径
LPDWORD lpSectorsPerCluster, // 每个簇的扇区数
LPDWORD lpBytesPerSector, // 每个扇区的字节数
LPDWORD lpNumberOfFreeClusters, // 空闲的簇
LPDWORD lpTotalNumberOfClusters // 总的簇
);
注意根路径被定义为LPCTSTR,这是一种与平台相关的字符串指针形式。因为没有函数的名字叫GetDiskFreeSpace(),封送器(marshaler)将会自动查找A或者W变量,调用对应的函数,而我们需要通过属性来告诉封送器API到底需要什么样的字符串类型。
[DllImport("kernel32.dll")]
static extern bool GetDiskFreeSpace(
[MarshalAs(UnmanagedType.LPTStr)]
string rootPathName,
ref int sectorsPerCluster,
ref int bytesPerSector,
ref int numberOfFreeClusters,
ref int totalNumberOfClusters);
不过很不幸,这样不行。问题在于在默认情况下封送器总是试图找到API的Ansi版本,而不管我们当前所处的平台是什么,想想看,封送器在Windows NT平台下想找到使用Unicode字符串参数的Ansi函数还真是不容易啊。
要让封送器不再干这么愚蠢的事情,需要我们告诉它自己去确定到底应该使用A版本或者W版本的函数:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
使用上面这个小技巧已经可以处理大多数Win32 API中的字符串类型了,不过总有一小部分API函数不是那么普通,下面让我们继续看看一些不使用上述机制的家伙,我们必须另辟蹊径来解决它们:
字符串缓冲区
.NET中的字符串属于不可变类型,这意味着字符串对象的值是不能修改的。对于那些需要拷贝字符串值到字符串缓冲区的函数来说,使用String类型就不能奏效了。如果你非要这么做,好的情况可能是破坏封送器创建的临时缓冲区;坏的情况就可能会破坏.NET的托管堆(managed heap),这下可就会有无法预料的情况发生了。
不过.NET早就为我们准备好了应对之策,StringBuilder类型可以象一个缓冲区一样容纳必要的内容:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetShortPathName(
[MarshalAs(UnmanagedType.LPTStr)]
string path,
[MarshalAs(UnmanagedType.LPTStr)]
StringBuilder shortPath,
int shortPathLength);
用法也很简单:
StringBuilder shortPath = new StringBuilder(80);
int result = GetShortPathName(
@"d:\test.jpg", shortPath, shortPath.Capacity);
string s = shortPath.ToString();
需要注意的地方是StringBuilder的容量就是缓冲区的大小。
内嵌字符数组的结构
除了上面说的两种情况,还有一些函数需要内嵌字符数组的结构作为参数。比如GetTimeZoneInformation()函数就需要一个指针来指向下面这个结构:
typedef struct _TIME_ZONE_INFORMATION
{
LONG Bias;
WCHAR StandardName[ 32 ];
SYSTEMTIME StandardDate;
LONG StandardBias;
WCHAR DaylightName[ 32 ];
SYSTEMTIME DaylightDate;
LONG DaylightBias;
} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION;
要在C#语言中使用这个函数需要两个结构,第一个非常简单,没什么好说的:
struct SystemTime
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
不过TimeZoneInformation的定义就有些复杂了:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TimeZoneInformation
{
public int bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string standardName;
SystemTime standardDate;
public int standardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string daylightName;
SystemTime daylightDate;
public int daylightBias;
}
在以上的定义有两个需要我们重点注意的地方。第一个是MarshalAs属性的定义方式:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
MSDN中的说明是:“ByValTStr用于在结构中出现的内联定长字符数组。与 ByValTStr 一起使用的字符类型由应用于包含结构的 System.Runtime.InteropServices.StructLayoutAttribute 的 System.Runtime.InteropServices.CharSet 参数确定。应始终使用 MarshalAsAttribute.SizeConst 字段来指示数组的大小。”有了这段说明,我想不必再多说什么了吧。
当我第一次编写这段代码时出现过执行引擎错误,这往往意味着你在调用过程中传递了错误的结构大小,导致了一些不属于你的内存被覆盖了。我使用Marshal.SizeOf()方法来获取使用的封送器的大小为108个字节。我查找了一些资料,很快想起来在默认情况下调用默认的字符类型是Ansi类型,而结构中定义的却是WCHAR,标准的双字节类型,是它导致了这个问题。我为了修改这个问题,添加了StructLayout属性,Sequential布局方式是结构的默认方式,说明对象的成员按照它们在被导出到非托管内存时出现的顺序依次布局。那为什么CharSet值需要设为Unicode,而不是我们一开始所说得那样使用Auto呢?这是因为这个函数和前面讨论的函数不同,它没有A和W版本的变量之分,总是使用Unicode字符串,所以我做出了设定Unicode属性的决定,让它能够总是能够使用正确的字符类型。