はじめてのCLRホスティングその3
昨日に引き続き、更に脱線。
CorBindToRuntimeExに渡されるバージョンは、基本的にはビルドした環境のバージョンに引っ張られます。では、アセンブリからCLRのバージョンを取得してみましょう。
#include <windows.h> #include <stdio.h> // ファイル読み込み用ヘルパークラス class Image { HANDLE hFile; HANDLE hMap; PBYTE pData; public: Image () : hFile (INVALID_HANDLE_VALUE), hMap (NULL), pData (NULL) { } ~Image () { Cleanup (); } bool Load (const char* path) { Cleanup (); hFile = CreateFile (path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return false; hMap = CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { Cleanup (); return false; } pData = (PBYTE)MapViewOfFile (hMap, FILE_MAP_READ, 0, 0, 0); if (!pData) { Cleanup (); return false; } return true; } void Cleanup() { if (pData) { UnmapViewOfFile (pData); pData = NULL; } if (hMap) { CloseHandle (hMap); hMap = NULL; } if (hFile != INVALID_HANDLE_VALUE) { CloseHandle (hFile); hFile = INVALID_HANDLE_VALUE; } } operator PBYTE () const { return pData; } }; // PEヘッダのチェック bool CheckHeader (PBYTE pImage, PIMAGE_NT_HEADERS& pNTHeader) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)pImage; if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return false; pNTHeader = (PIMAGE_NT_HEADERS)*1; // Unicode -> ANSI WideCharToMultiByte (CP_ACP, 0, tmp, -1, verStr, size, NULL, NULL); return true; } int main (int argc, char* argv[]) { if (argc < 2) return 0; Image image; if (!image.Load (argv[1])) return 0; PIMAGE_NT_HEADERS pNTHeader; PIMAGE_NT_HEADERS64 pNTHeader64; if (!CheckHeader (image, pNTHeader)) return 0; char verStr [128]; pNTHeader64 = (PIMAGE_NT_HEADERS64)pNTHeader; bool is64Bit = pNTHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC; if (is64Bit) { if (!GetVersionString (image, pNTHeader64, verStr, sizeof(verStr))) return 0; } else { if (!GetVersionString (image, pNTHeader, verStr, sizeof(verStr))) return 0; } printf(".NET Framework version %s\n", verStr); return 0; } /* clrview.exe hello.exe .NET Framework version v2.0.50727 */
延々とヘッダとメタデータを参照して取得できました。
それでは、ビルド環境と同一のバージョンでしか動作しないかと言えば、そんなことはありません。例えば、互換性のあるアップグレード(.NET1.0 -> 1.1など)の場合は、.NET1.0でビルドしたアセンブリが.NET1.1しかインストールされていない環境で動作したり、COM InteropでCLRを呼び出した場合は、インストールされている最新バージョンのCLRがロードされたりします。それについてはまた次回。今日は、バージョン情報取得プログラム書いて疲れてしまいました。
しかし、この内容のどこがCLRホスティングだ! 看板に偽りありですね。(^^;
*1:DWORD_PTR)dosHeader + dosHeader->e_lfanew); if (IsBadReadPtr (pNTHeader, sizeof (pNTHeader->Signature))) return false; if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) return false; return true; } template <class T> PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva, T* pNTHeader) { PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader); for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; ++i, ++section) { DWORD size = section->Misc.VirtualSize; if (!size) size = section->SizeOfRawData; if ( (rva >= section->VirtualAddress) && (rva < (section->VirtualAddress + size))) return section; } return NULL; } template <class T> LPVOID GetPtrFromRVA (DWORD rva, PBYTE image, T* pNTHeader) { PIMAGE_SECTION_HEADER pSectionHeader; INT d; pSectionHeader = GetEnclosingSectionHeader (rva, pNTHeader); if (!pSectionHeader) return NULL; d = (INT)(pSectionHeader->VirtualAddress - pSectionHeader->PointerToRawData); return (PVOID) (image + rva - d); } // アセンブリファイルからビルド時のCLRバージョンを取得 template <class T> bool GetVersionString (PBYTE pImage, T* pNTHeader, char* verStr, int size) { DWORD rva; rva = pNTHeader->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress; if (!rva) return false; PIMAGE_COR20_HEADER pCIL; pCIL = (PIMAGE_COR20_HEADER)GetPtrFromRVA (rva, pImage, pNTHeader); if (!pCIL) return false; PBYTE pMeta = (PBYTE)GetPtrFromRVA (pCIL->MetaData.VirtualAddress, pImage, pNTHeader); wchar_t tmp [128]; // UTF-8 -> Unicode MultiByteToWideChar (CP_UTF8, 0, (const char*)pMeta + 16, -1, tmp, sizeof (tmp) / sizeof (tmp[0]