はじめての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 InteropCLRを呼び出した場合は、インストールされている最新バージョンの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]