LoadStringのループでまだ消耗しているの！？dllなどからストリングテーブルを根こそぎ取得する方法

#はじめに
Qiita:[メッセージテーブル](https://qiita.com/otagaisama-1/items/b1807f42350da390134c) (RT_MESSAGETABLE) に関する姉妹編です。
文字列リソースは一般的に[LoadString](https://msdn.microsoft.com/ja-jp/library/cc410872.aspx)関数で読み出しますが、リソースIDが不明でも抜き出す方法があります。

#コードと解説
```C#:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

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

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

            Console.Read();
        }

        static IEnumerable<System.Tuple<Int32, string>> EnumResourceStrings(string filePath)
        {
            var retVal = new System.Collections.Generic.List<System.Tuple<Int32, string>>();

            //  メッセージテーブルリソースをもつファイルを読み込みます。
            var hModule = NativeMethods.LoadLibraryEx(filePath, IntPtr.Zero, NativeMethods.LoadLibraryExFlags.DONT_RESOLVE_DLL_REFERENCES | NativeMethods.LoadLibraryExFlags.LOAD_LIBRARY_AS_DATAFILE);
            if (!IntPtr.Zero.Equals(hModule))
            {
                try
                {
                    // STRINGテーブルのエントリを列挙させます。
                    NativeMethods.EnumResourceNames(hModule, new IntPtr(NativeMethods.RT_STRING), (hMod, typ, nam, prm) => 
                    {
                        // 文字列リソースIDは、得られたリソース名（INTRESOURCE）-1の16倍
                        var resId = (nam.ToInt32() - 1) * 16;
                        var hRes = NativeMethods.FindResource(hMod, nam, typ);
                        if (!IntPtr.Zero.Equals(hRes))
                        {
                            var hGlobal = NativeMethods.LoadResource(hMod, hRes);
                            if (!IntPtr.Zero.Equals(hGlobal))
                            {
                                var bytesLen = NativeMethods.SizeofResource(hMod, hRes);
                                var ptr = NativeMethods.LockResource(hGlobal);
                                if (!IntPtr.Zero.Equals(ptr))
                                {
                                    Int32 offset = 0;
                                    while(offset < bytesLen)
                                    {
                                        var strLen = (UInt16)Marshal.ReadInt16(ptr + (int)offset);
                                        offset += 2;
                                        if (strLen > 0)
                                        {
                                            var str = Marshal.PtrToStringUni(ptr + (int)offset, strLen);
                                            offset += strLen * 2;
                                            retVal.Add(new System.Tuple<Int32, string>(resId, str));
                                        }
                                        resId += 1;
                                    }
                                }
                            }
                        }
                        return true;
                    }, IntPtr.Zero);
                }
                finally
                {
                    // 読み込んだファイルを閉じます。
                    NativeMethods.FreeLibrary(hModule);
                }
            }

            return retVal;
        }

        class NativeMethods
        {
            public const int RT_STRING = 0x00000006;

            [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
                );

            [return: MarshalAs(UnmanagedType.Bool)]
            public delegate bool EnumResNameProc(
                IntPtr hModule,
                IntPtr lpszType,
                IntPtr lpszName,
                IntPtr lParam
            );

            [DllImport("kernel32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EnumResourceNames(
                IntPtr hModule,
                IntPtr lpszType,
                EnumResNameProc lpEnumFunc,
                IntPtr lParam
            );

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

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

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

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

```

##データ構造
STRINGリソース(RT_STRING)を指定して[EnumResourceNames](https://msdn.microsoft.com/ja-jp/library/cc410855.aspx)関数を呼び出すと、整数値リソース名が列挙されます。
[FindResource](https://msdn.microsoft.com/ja-jp/library/cc410845.aspx)関数、[LoadResource](https://msdn.microsoft.com/ja-jp/library/cc410882.aspx)関数、[LockResource](https://msdn.microsoft.com/ja-jp/library/cc410874.aspx)関数で得られるリソースデータは、ロックして得られた領域の最後（[SizeOfResource](https://msdn.microsoft.com/ja-jp/library/cc410874.aspx)関数で領域のバイトサイズが得られます）まで "文字数(2バイト整数) + UNICODE文字列" が連続して格納されています。
先頭の文字列リソースIDは、列挙された整数値リソース名の値から1を引いて16倍したもので、次につづく文字列リソースのリソースIDはその前の文字列リソースに1を加えたものです（つまり、リソースIDは連続しています）

#おわりに
内容や誤字のご指摘、お待ちしております。