2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FormatMessageのループでまだ消耗しているの!?dllなどからメッセージテーブルを根こそぎ取得する方法

Last updated at Posted at 2018-05-21

はじめに

消耗していたのは、私のことです。。
Win32API の GetLastError 関数に関連するエラーメッセージを全部見てみたくて、0 から 0xFFFFFFFF まで for 文でループさせてみたり、winerror.h から #define ラベル部分を抜いてC言語ソースにして抜いてみたりしましたが、前者はとても時間がかかりますし、後者はヘッダに定義のないメッセージが取得対象から外れてしまいます。
幸運にもリソースのメッセージテーブルからまるごと文字列データを抜き出す方法を知ることが出来ましたので、あえてC#にてここにまとめてみます。VisualBasicのエラーメッセージもこれで一覧にすることが出来ますよ。

2018/5/22:ストリングテーブル (RT_STRING) に関する姉妹編も投稿してみました。

参考にしたサイト

コードと解説

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace EnumSystemMessagesTool
{
    class Program
    {
        static void Main(string[] args)
        {
            //  サンプルDLL
            var targetFilePath = @"C:\Windows\System32\kernel32.dll";

            var messages = EnumMessageTableMessages(targetFilePath);
            if (!messages.Any())
            {
                Console.WriteLine("読み込めませんでした");
                return;
            }
            foreach (var msg in messages)
            {
                Console.WriteLine("{0} : {1}", msg.Item1, msg.Item2);
            }
            Console.WriteLine("おわりました");
            Console.Read();
        }

        static IEnumerable<System.Tuple<UInt32, string>> EnumMessageTableMessages(string filePath)
        {
            //  メッセージテーブルリソースをもつファイルを読み込みます。
            var hModule = NativeMethods.LoadLibraryEx(filePath, IntPtr.Zero, NativeMethods.LoadLibraryExFlags.DONT_RESOLVE_DLL_REFERENCES | NativeMethods.LoadLibraryExFlags.LOAD_LIBRARY_AS_DATAFILE);
            if (IntPtr.Zero.Equals(hModule))
                yield break;
            try
            {
                var hRsrc = NativeMethods.FindResourceEx(hModule, new IntPtr(NativeMethods.RT_MESSAGETABLE), new IntPtr(1), NativeMethods.LANGID_NEUTRAL);
                if (!IntPtr.Zero.Equals(hRsrc))
                {
                    var hGlobal = NativeMethods.LoadResource(hModule, hRsrc);
                    if (!IntPtr.Zero.Equals(hGlobal))
                    {
                        var topOfResourceData = NativeMethods.LockResource(hGlobal);
                        if (!IntPtr.Zero.Equals(topOfResourceData))
                        {
                            // ブロック数はDWORD
                            var numOfBlocks = Marshal.ReadInt32(topOfResourceData);
                            var ptrInBlocks = topOfResourceData + 4;
                            for(int i = 0; i < numOfBlocks; i++)
                            {
                                // 先頭IDはDWORD
                                var lowID = (UInt32)Marshal.ReadInt32(ptrInBlocks);
                                ptrInBlocks += 4;
                                // 終端IDはDWORD
                                var highID = (UInt32)Marshal.ReadInt32(ptrInBlocks);
                                ptrInBlocks += 4;
                                // オフセットはDWORD
                                var offsetFromTop = Marshal.ReadInt32(ptrInBlocks);
                                ptrInBlocks += 4;

                                var ptrOfMessage = topOfResourceData + offsetFromTop;
                                for(var j = lowID; j <= highID; j++)
                                {
                                    // この構造のバイトサイズはWORD
                                    var lengthInBytes = Marshal.ReadInt16(ptrOfMessage);
                                    var messageLengthInBytes = lengthInBytes - 4;
                                    ptrOfMessage += 2;
                                    // フラグ(文字種)はWORD
                                    var flag = Marshal.ReadInt16(ptrOfMessage);
                                    ptrOfMessage += 2;

                                    string str = string.Empty;
                                    switch (flag)
                                    {
                                        case 0:
                                            str = Marshal.PtrToStringAnsi(ptrOfMessage, messageLengthInBytes);
                                            break;
                                        case 1:
                                            str = Marshal.PtrToStringUni(ptrOfMessage, messageLengthInBytes / 2);
                                            break;
                                    }
                                    ptrOfMessage += messageLengthInBytes;

                                    yield return new Tuple<uint, string>(j, str.Trim());
                                }
                            }
                        }
                    }
                }
            }
            finally
            {
                // 読み込んだファイルを閉じます。
                NativeMethods.FreeLibrary(hModule);
            }

        }

        private class NativeMethods
        {
            public const int RT_MESSAGETABLE = 0x0000000B;
            public const UInt16 LANGID_NEUTRAL = 0x0000;

            [Flags]
            public enum LoadLibraryExFlags : UInt32
            {
                DONT_RESOLVE_DLL_REFERENCES = 0x0000001,
                LOAD_LIBRARY_AS_DATAFILE = 0x00000002
            }

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr LoadLibraryEx(
                string lpFileName,
                IntPtr hFile,
                LoadLibraryExFlags dwFlags
                );

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool FreeLibrary(
                IntPtr hModule
                );

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr FindResourceEx(
                IntPtr hModule,
                IntPtr lpszType,
                IntPtr lpszName,
                UInt16 wLangID
            );

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr LoadResource(
                IntPtr hModule,
                IntPtr hRsrc
            );

            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr LockResource(
                IntPtr hResData
            );
        }
    }
}

データ構造

winnt.hの一部
typedef struct _MESSAGE_RESOURCE_DATA {
    DWORD NumberOfBlocks;
    MESSAGE_RESOURCE_BLOCK Blocks[ 1 ];
} MESSAGE_RESOURCE_DATA, *PMESSAGE_RESOURCE_DATA;

typedef struct _MESSAGE_RESOURCE_BLOCK {
    DWORD LowId;
    DWORD HighId;
    DWORD OffsetToEntries;
} MESSAGE_RESOURCE_BLOCK, *PMESSAGE_RESOURCE_BLOCK;

typedef struct _MESSAGE_RESOURCE_ENTRY {
    WORD   Length;
    WORD   Flags;
    BYTE  Text[ 1 ];
} MESSAGE_RESOURCE_ENTRY, *PMESSAGE_RESOURCE_ENTRY;

MESSAGE_RESOURCE_DATA

リソースデータをロックして得られるメモリ領域に、上記のような構造でメッセージテーブルのデータが格納されています。
先頭に MESSAGE_RESOURCE_DATA の構造があり、先頭 4バイト (NumberOfBlocks) には後続する MESSAGE_RESOURCE_BLOCK 構造の個数が入っていて、直後から MESSAGE_RESOURCE_BLOCK 構造が連続して格納されています。

MESSAGE_RESOURCE_BLOCK

それぞれの MESSAGE_RESOURCE_BLOCK には、連続するメッセージIDをもつ MESSAGE_RESOURCE_ENTRY の情報が格納されており、先頭のメッセージID (LowId) と最後のメッセージID (HighId)、メッセージの実体である MESSAGE_RESOURCE_ENTRY 構造の、ロックして得られたメモリ領域の先頭からのバイトオフセット (OffsetToEntries) が、おのおの 4バイトで入っています。

MESSAGE_RESOURCE_ENTRY

MESSAGE_RESOURCE_ENTRY には、Length や Flags を含む、自身のMESSAGE_RESOURCE_ENTRY 構造全体のバイトサイズが Length に、Flagsにはメッセージテキストデータのエンコード (0:ANSI 1:Unicode) が格納されており、さらにメッセージテキストデータが Length - 4 バイト分入っています。そして、その次 (=+1) のメッセージIDをもつMESSAGE_RESOURCE_ENTRY構造が直後につづきます。

ロックしたリソースは FreeLibrary 関数を呼び出してモジュールをアンロードすることでリリースされます。

コードのきも

  • メッセージテーブルは、リソース名 1 つまり MAKEINTRESOURCE(1) にすべて集約されています。
  • FindResource関数では取得できず、FindResourceEx関数で取得できます。外国語もリソースに含んでいれば、EnumResourceLanguages 関数で言語IDを列挙して、それを指定して取得できます。

おわりに

先達に感謝します!
内容や誤字のご指摘、お待ちしております。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?