一、引言
在Windows开发中,总免不了载入一些资源(resource)。例如,在rc文件中,声明图标资源:
///////////////////////////////////////////////////////////////////////////// // // 图标 // IDI_SMALL ICON "small.ico"
加载图标可以用下面的方式:
HICON hIcon = (HICON)LoadImage(hInstance, L"small.ico", IMAGE_ICON, 0, 0, 0);
或者:
HICON hIcon = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDI_SMALL), IMAGE_ICON, 0, 0, 0); // 标记为A
这两种方式是等价的,第一种方式中,为LoadImage的第二个参数传入资源的名称,第二种方式中,传入资源的ID,不过要加上一个MAKEINTRESOURCE。
那么MAKEINTRESOURCE是个什么东西呢?
二、疑惑
在Unicode环境下,
#define MAKEINTRESOURCE MAKEINTRESOURCEW
而
#define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
又因为
typedef wchar_t WCHAR; typedef WCHAR *LPWSTR, *PWSTR;
所以,在Unicode环境下,
MAKEINTRESOURCE(i)
就是
((wchar_t*)((ULONG_PTR)((WORD)(i))))
因此,我们可以将MAKEINTRESOURCE看作一个函数:
wchar_t *MakeIntResource(WORD i) { return (wchar_t*)i; }
这样一来就很清楚了,就是把一个WORD解释成wchar_t*。如果IDI_SMALL为40001,前文那行标记为A的代码就等价于:
HICON hIcon = (HICON)LoadImage(hInstance, (wchar_t*)40001, IMAGE_ICON, 0, 0, 0);
这就让人感到奇怪。以字符串的形式传递名称(L"small.ico")好理解,把ID(40001)以wchar_t*的方式传进去算怎么回事?LoadImage在其内部如何知道传进来的是资源的ID还是一个字符串的首地址呢?
三、原理
原来,Windows有这么一个性质:任何一个变量,它的地址一定大于等于65536。例如,定义一个变量:
int n = 0;
我们可以作出一个断言:
ASSERT(reinterpret_cast<UINT_PTR>(&n) >= 65536);
这个断言在Windows上是永远成立的。
这时,所有的疑惑都可以解开了。
由于MAKEINTRESOURCE宏会先把宏参数转换成WORD,所以通过MAKEINTRESOURCE得到的wchar_t*一定不超过65535。
如此一来,在使用MAKEINTRESOURCE向LoadImage传递资源ID的时候,LoadImage可以通过第二个参数值决定应该把传入的参数作为ID还是一个字符串的首地址。
猜测LoadImage的实现是这样的:
HANDLE _LoadImage( _In_opt_ HINSTANCE hInst, _In_ const wchar_t *name, _In_ UINT type, _In_ int cx, _In_ int cy, _In_ UINT fuLoad ) { if ((ULONG_PTR)name >> 16 == 0) { // 传入的是ID } else { // 传入的字符串 } // 其他代码 }