はじめに
Qiita:メッセージテーブル (RT_MESSAGETABLE) に関する姉妹編です。
文字列リソースは一般的にLoadString関数で読み出しますが、リソースIDが不明でも抜き出す方法があります。
コードと解説
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関数を呼び出すと、整数値リソース名が列挙されます。
FindResource関数、LoadResource関数、LockResource関数で得られるリソースデータは、ロックして得られた領域の最後(SizeOfResource関数で領域のバイトサイズが得られます)まで "文字数(2バイト整数) + UNICODE文字列" が連続して格納されています。
先頭の文字列リソースIDは、列挙された整数値リソース名の値から1を引いて16倍したもので、次につづく文字列リソースのリソースIDはその前の文字列リソースに1を加えたものです(つまり、リソースIDは連続しています)
おわりに
内容や誤字のご指摘、お待ちしております。