给.Net程序员的PInvoke Tips [1]: String is Sometimes an Integer
Posted on 2006-06-13 17:30 smalldust 阅读(836) 评论(1) 编辑 收藏 举报
习惯了.Net编程,尤其是以前几乎没有用过Basic,Pascal,C/C++等“较古老”的语言的程序员,遇到PInvoke,尤其是COM interoperation的时候,往往是一头雾水不得要领。相信在在将来,一方面是从C#,.Net以及Java开始学习编程的人会越来越多,一方面整个Windows也逐渐往Managed平台迁移,懂得如何直接同Win32API打交道的程序员就会越来越少(当然绝对数量肯定还是很多的,至少比用DDK写驱动程序的多吧……)
但是,对于.Net程序员来说,虽然不用直接使用C++调用API,但是PInvoke的知识还是必不可少的,对于Handle,Unmanaged Thread,COM等等最基本的东西仍然是必修课。因此,我才想通过Tips的方式,对我所了解的最基础的内容做一些整理。
说了很多废话,首先让我们看一个PInvoke时遇到的实际例子:
话说某个C#写的WindowsForm中,要对资源DLL进行读取(Resource DLL,Win32格式的只含有资源的DLL)。其中要用到这么一个API函数:
其中,ENUMRESTYPEPROC,其定义为:
其中的LPTSTR,在C#里应该怎么写呢?我想,想也不想就写出下面代码的朋友不会太少吧:
很遗憾,这段代码在执行的时候会出错——用Marshal.GetLastWin32Error()查看,发现是ERROR_NOACCESS(998)。
说明,程序访问了禁止访问的地址空间。经测试,hModule是正确无误的DLL库的Handle,最后的参数也被设置为NULL,那么原因只能是这个strType了。
该参数的类型是LPTSTR,在Unicode环境下也即LPWSTR,也即WCHAR * ——没错啊,不就是字符串么?
但是我们仔细看一下MSDN的说明,就会发现,Windows提供了一些标准的资源类型;例如RT_ICON,RT_STRING,RT_VERSION,……仔细看这些类型的定义就会发现它们都是数字。
也就是说,lpszType这个参数,即可以被当作数字来使用,也可以把其数值当作指针,指向内存中的某个地址。在这种时候,显然就不能用.Net的String类型来表示。正确的做法是,使用IntPtr,如果判断其值不是系统与定义的类型,再将其Marshal为指向字符串的指针,获取字符串。
结论:在.Net中,数据类型是“强类型”的,你不能把一只猫作为一匹马来使用,因此保证了类型安全;但是在C/C++等语言为基础的Win32中,类型却是可以任意变换的,一只猫你可以当作一匹马,甚至一头猪……没有人来保证这种变换的安全性,但是也正因此带来了灵活性。Windows的各种数据类型当中,很多类型都是名称不同,但是实际上的数据结构是相同的(例如关于Handle,就有HWND,HMENU,HINSTANCE,HHOOK等等许多种)。相反,同一个数据类型,时而代表数值,时而代表指针的例子也不少见。
但是,对于.Net程序员来说,虽然不用直接使用C++调用API,但是PInvoke的知识还是必不可少的,对于Handle,Unmanaged Thread,COM等等最基本的东西仍然是必修课。因此,我才想通过Tips的方式,对我所了解的最基础的内容做一些整理。
说了很多废话,首先让我们看一个PInvoke时遇到的实际例子:
话说某个C#写的WindowsForm中,要对资源DLL进行读取(Resource DLL,Win32格式的只含有资源的DLL)。其中要用到这么一个API函数:
BOOL EnumResourceTypes(
HMODULE hModule,
ENUMRESTYPEPROC lpEnumFunc,
LONG_PTR lParam
);
HMODULE hModule,
ENUMRESTYPEPROC lpEnumFunc,
LONG_PTR lParam
);
其中,ENUMRESTYPEPROC,其定义为:
BOOL CALLBACK EnumResTypeProc(
HMODULE hModule,
LPTSTR lpszType,
LONG_PTR lParam
);
HMODULE hModule,
LPTSTR lpszType,
LONG_PTR lParam
);
其中的LPTSTR,在C#里应该怎么写呢?我想,想也不想就写出下面代码的朋友不会太少吧:
public delegate bool EnumResTypeProc(IntPtr hModule, string strType, IntPtr param);
很遗憾,这段代码在执行的时候会出错——用Marshal.GetLastWin32Error()查看,发现是ERROR_NOACCESS(998)。
说明,程序访问了禁止访问的地址空间。经测试,hModule是正确无误的DLL库的Handle,最后的参数也被设置为NULL,那么原因只能是这个strType了。
该参数的类型是LPTSTR,在Unicode环境下也即LPWSTR,也即WCHAR * ——没错啊,不就是字符串么?
但是我们仔细看一下MSDN的说明,就会发现,Windows提供了一些标准的资源类型;例如RT_ICON,RT_STRING,RT_VERSION,……仔细看这些类型的定义就会发现它们都是数字。
也就是说,lpszType这个参数,即可以被当作数字来使用,也可以把其数值当作指针,指向内存中的某个地址。在这种时候,显然就不能用.Net的String类型来表示。正确的做法是,使用IntPtr,如果判断其值不是系统与定义的类型,再将其Marshal为指向字符串的指针,获取字符串。
结论:在.Net中,数据类型是“强类型”的,你不能把一只猫作为一匹马来使用,因此保证了类型安全;但是在C/C++等语言为基础的Win32中,类型却是可以任意变换的,一只猫你可以当作一匹马,甚至一头猪……没有人来保证这种变换的安全性,但是也正因此带来了灵活性。Windows的各种数据类型当中,很多类型都是名称不同,但是实际上的数据结构是相同的(例如关于Handle,就有HWND,HMENU,HINSTANCE,HHOOK等等许多种)。相反,同一个数据类型,时而代表数值,时而代表指针的例子也不少见。