最近用到了需要实时更新exe和dll中的资源文件,网上查了很多资料,做一些总结,方便下次使用。
UpdateResource这个函数主要用于添加、删除或者替换PE文件中的资源,它的原型为
BOOL WINAPI UpdateResource( _In_ HANDLE hUpdate, _In_ LPCTSTR lpType, _In_ LPCTSTR lpName, _In_ WORD wLanguage, _In_opt_ LPVOID lpData, _In_ DWORD cbData );
更新自定义资源文件,首先是对资源文件的读取,然后就是写入。读取的时候先通过LoadLibrary得到文件的HMODULE,然后通过FindResource获取到文件中的资源信息,FindResource需要提供HMODULE和资源的类型以及名字,这个一般在资源的头文件中有定义,通过exeScope也可查到,比如ABC PNG “abc.png”,这一行中,ABC就是名字,而PNG就是类型,而”abc.png“是你实际的文件名。FindResource得到一个HRSRC的句柄后,就可以通过LoadResource来载入这个资源文件,接着LockResource可以提供上一步的结果缓存指针,然后大家就可以将这段缓存保存下来,进行自己的处理,这样就得到了PE文件中的自定义资源的内容。当然,在保存的时候如果用memcpy的话,需要提供资源缓存的大小,可以通过SizeofResource这个函数来获取大小。其实,系统定义的文件也可以这样做,只是将类型名改为系统的就行了,比如BMP,STRING等等。
下面是我写的读取文件的函数,请指正:
BOOL GetResourceFromFile(LPCTSTR lpszFile, LPCTSTR lpName, LPCTSTR lpType, LPBYTE lpOut) { if( NULL == lpszFile || !PathFileExists(lpszFile) || NULL == lpName || NULL == lpType ) return FALSE; BOOL bRet = FALSE; HMODULE hFile = LoadLibrary( lpszFile ); if( NULL == hFile ) return bRet; HRSRC hRsrc = FindResource( hFile, lpName, lpType ); if( NULL == hRsrc ) return bRet; HGLOBAL hGlobal = LoadResource( hFile, hRsrc ); if( NULL == hGlobal ) return bRet; LPBYTE lpBuffer = (LPBYTE)LockResource( hGlobal ); if( NULL == lpBuffer) return bRet; DWORD dwSize = SizeofResource( hFile, hRsrc ); memcpy( lpOut, lpBuffer, dwSize ); bRet = TRUE; FreeLibrary( hFile ); return bRet; }
写入资源文件也比较简单。先通过GetFileVersionInfoSize得到资源文件的大小,然后根据大小开辟一段缓存,通过GetFileVersionInfo来填充这段缓存,接着调用BeginUpdateResource来得到资源的句柄,这样就可以开始更新了。但是更新之前还需要获取当前的语言版本,这步不能少,所以用VerQueryValue来传入“\\VarFileInfo\\Translation”就可以得到语言版本。VerQueryValue可以查询三种信息,我们一会就会讲到。得到语言版本后,就可以UpdateResource来更新资源了,最后需要再调用EndUpdateResource来结束更新。还是附上源码参考:
BOOL UpdateDllFile(LPCTSTR lpszFile, LPBYTE pIniBuf, LPCTSTR lpType, LPCTSTR lpName) { if( NULL == lpszFile || !PathFileExists(lpszFile) || NULL == pIniBuf || NULL == lpType || NULL == lpName ) return FALSE; BOOL bRet = FALSE; DWORD dwBufSize = strlen( (char*)pIniBuf ); DWORD dwSize = 0; DWORD dwHandle = 0; dwSize = GetFileVersionInfoSize(lpszFile, &dwHandle); if( 0 >= dwSize) return bRet; LPBYTE lpBuffer = new BYTE[dwSize]; memset( lpBuffer, 0, dwSize ); if (GetFileVersionInfo(lpszFile, dwHandle, dwSize, lpBuffer) != FALSE) { HANDLE hResource = BeginUpdateResource(lpszFile, FALSE); if( NULL != hResource ) { UINT uTemp = 0; if (VerQueryValue(lpBuffer, _T("\\VarFileInfo\\Translation"), (LPVOID *)&lpTranslate, &uTemp) != FALSE) { if( FALSE != UpdateResource( hResource, lpType, lpName, lpTranslate->wLanguage, (LPVOID)pIniBuf, dwBufSize) ) if (EndUpdateResource(hResource, FALSE) != FALSE) bRet = TRUE; } } } return bRet; }
struct { WORD wLanguage; WORD wCodePage; } *lpTranslate;
下面讲下更新PE文件中版本信息。版本信息主要有两个,一个是数字形式的,一个是字符串形式的。以版本号为例,最开始我更新的时候只注意到了数字形式,修改了之后发现有些信息还是原来的,后来才查到还需要修改字符串形式,否则显示的仍然是原来的版本号。我下面也只讲下版本号的修改,其他都是一样的。
首先获取数字形式的版本号。之前还是需要先GetFileVersionInfoSize和GetFileVersionInfoSize来得到资源,并BeginUpdateResource获取资源句柄,这些可以参考上面的代码。然后得到语言版本,也是上面有的代码,VerQueryValue并传入“\\VarFileInfo\\Translation”。对于数字的版本号,我们可以通过VerQueryValue并传入“\\”来获取,这个时候我们将得到的缓存转换为VS_FIXEDFILEINFO* 形式的指针,就可以得到版本信息了。这个VS_FIXEDFILEINFO结构体里面dwFileVersionMS和dwFileVersionLS信息是文件版本号,dwProductVersionLS和dwProductVersionMS是产品版本号,MS是主版本号,LS是副版本号。更改之后再调用UpdateResource传入缓存指针就可以了。
另外一个就是字符串形式的版本号。字符串的获取是通过VerQueryValue传入“\\StringFileInfo\\语言版本\\需要的信息”来获取的,里面的“语言版本“需要已16进制格式化上面的lpTranslate结构体后得到。"需要的信息"是你想要查询的,这个在VerQueryValue的介绍里面也有,用exeScope打开一个PE文件,就可以查到,附图:
这里面可以看到语言版本是080404b0,我们如果想得到文件版本信息,则传入\\StringFileInfo\\080404b0\\FileVersionName,如果是产品版本号,则是\\StringFileInfo\\080404b0\\ProduceVersion,等等,就不一一举例了。
得到我们想要的字符串信息的缓存之后,很好办了,直接替换这段缓存,然后UpdateResource回去就可以了。最后记得EndUpdateResource。最后附下修改版本信息的源码吧:
BOOL UpdateDllFile(LPCTSTR lpszFile) { if( NULL == lpszFile || !PathFileExists(lpszFile) ) return FALSE; BOOL bRet = FALSE; DWORD dwHandle = 0; DWORD dwSize = 0; dwSize = GetFileVersionInfoSize(lpszFile, &dwHandle); if( 0 >= dwSize) return bRet; LPBYTE lpBuffer = new BYTE[dwSize]; memset( lpBuffer, 0, dwSize ); if (GetFileVersionInfo(lpszFile, dwHandle, dwSize, lpBuffer) != FALSE) { HANDLE hResource = BeginUpdateResource(lpszFile, FALSE); if (NULL != hResource) { UINT uTemp; DWORD dwVer[4] = {0}; if (VerQueryValue(lpBuffer, _T("\\VarFileInfo\\Translation"), (LPVOID *)&lpTranslate, &uTemp) != FALSE) { // 修改版本信息,给副版本号加1 LPVOID lpFixedBuf = NULL; DWORD dwFixedLen = 0; if( FALSE != VerQueryValue( lpBuffer, _T("\\"), &lpFixedBuf, (PUINT)&dwFixedLen )) { VS_FIXEDFILEINFO* pFixedInfo = (VS_FIXEDFILEINFO*)lpFixedBuf; pFixedInfo->dwFileVersionLS = pFixedInfo->dwFileVersionLS + 0x1; pFixedInfo->dwProductVersionLS = pFixedInfo->dwProductVersionLS + 0x1; dwVer[0] = HIWORD(pFixedInfo->dwFileVersionMS); dwVer[1] = LOWORD(pFixedInfo->dwFileVersionMS); dwVer[2] = HIWORD(pFixedInfo->dwFileVersionLS); dwVer[3] = LOWORD(pFixedInfo->dwFileVersionLS); } // 修改版本的文本信息 LPVOID lpStringBuf = NULL; DWORD dwStringLen = 0; TCHAR szTemp[MAX_PATH] = {0}; TCHAR szVersion[MAX_PATH] = {0}; _stprintf_s( szTemp, MAX_PATH - 1, _T("\\StringFileInfo\\%04x%04x\\FileVersion"), lpTranslate->wLanguage, lpTranslate->wCodePage ); _stprintf_s( szVersion, MAX_PATH - 1, _T("%d, %d, %d, %d"), dwVer[0], dwVer[1], dwVer[2], dwVer[3] ); if( FALSE != VerQueryValue( lpBuffer, szTemp, &lpStringBuf, (PUINT)&dwStringLen ) ) memcpy( lpStringBuf, szVersion, (_tcslen(szVersion) + 1) * sizeof(TCHAR) ); memset( szTemp, 0, sizeof(szTemp) ); _stprintf_s( szTemp, MAX_PATH - 1, _T("\\StringFileInfo\\%04x%04x\\ProductVersion"), lpTranslate->wLanguage, lpTranslate->wCodePage ); if( FALSE != VerQueryValue( lpBuffer, szTemp, &lpStringBuf, (PUINT)&dwStringLen ) ) memcpy( lpStringBuf, szVersion, (_tcslen(szVersion) + 1) * sizeof(TCHAR) ); // 更新 if (UpdateResource(hResource, RT_VERSION, MAKEINTRESOURCE(VS_VERSION_INFO), lpTranslate->wLanguage, lpBuffer, dwSize) != FALSE) { if (EndUpdateResource(hResource, FALSE) != FALSE) bRet = TRUE; } } } } if( lpBuffer ) delete [] lpBuffer; return bRet; }