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 3 years have passed since last update.

C# PEファイルの文字列リソースをすべて取得する

Last updated at Posted at 2020-05-31

言語と動作確認環境

  • C# 8、.NET Core 3.1 (Preview)
  • Visual Studio Community 2019 Preview(Version 16.7.0 Preview 1.0)、Windows 10。

目的

C# 8と.NET Core 3.1でPEファイルの文字列リソースをすべて取得するサンプルコードです。同様のコードはQiitaや海外サイトでも公開されていますが、自身の学習のために作成しています。

サンプルコード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            using var handle = NativeMethods.LoadLibraryExW(
                "user32.dll",
                IntPtr.Zero,
                NativeMethods.DONT_RESOLVE_DLL_REFERENCES
                | NativeMethods.LOAD_LIBRARY_AS_DATAFILE
                | NativeMethods.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
            if (handle.IsInvalid)
            {
                throw new Win32Exception();
            }

            var strings = GetStringResources(handle.DangerousGetHandle());
        }

        /// <summary>
        /// ある型のリソースのIDをすべて取得します。
        /// </summary>
        private static ushort[] GetResourceIDs(IntPtr moduleHandle, IntPtr resourceType)
        {
            var resnames = new List<ushort>();
            NativeMethods.EnumResourceNamesW(
                moduleHandle,
                resourceType,
                (IntPtr hModule, IntPtr lpszType, IntPtr lpszName, nint lParam) =>
                {
                    var id = lpszName.ToInt64();
                    if (id >> 16 != 0)
                        throw new Exception();
                    resnames.Add((ushort)id);
                    return true;
                },
                0);
            return resnames.ToArray();
        }

        /// <summary>
        /// モジュールの文字列リソースをすべて取得します。
        /// </summary>
        private static string[] GetStringResources(IntPtr moduleHandle)
        {
            var stringResIds = GetResourceIDs(moduleHandle, NativeMethods.RT_STRING);
            Array.Sort(stringResIds);
            var strings = new List<string>();
            foreach (var strResId in stringResIds)
            {
                var resHandle = NativeMethods.FindResourceW(moduleHandle,
                    new IntPtr(strResId), NativeMethods.RT_STRING);
                var memoryHandle = NativeMethods.LoadResource(moduleHandle, resHandle);
                var size = NativeMethods.SizeofResource(moduleHandle, resHandle);
                // pointerの中身は2バイト(文字数N)+N*2バイト(UTF-16文字列)の配列
                var pointer = NativeMethods.LockResource(memoryHandle);
                for (int offset = 0; offset < size;)
                {
                    uint len = (ushort)Marshal.ReadInt16(pointer + offset);
                    strings.Add(Marshal.PtrToStringUni(pointer + offset + sizeof(ushort), (int)len));
                    offset += sizeof(ushort) + (int)len * 2;
                }
            }
            return strings.ToArray();
        }

        private static class NativeMethods
        {
            [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
            public static extern SafeModuleHandle LoadLibraryExW(
                [In] string lpLibFileName,
                IntPtr hFile,
                uint dwFlags);

            [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EnumResourceNamesW(
                IntPtr hModule,
                IntPtr lpType,
                EnumResNameProcW lpEnumFunc,
                nint lParam);

            public const uint DONT_RESOLVE_DLL_REFERENCES = 0x00000001;
            public const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
            public const uint LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000;

            [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public delegate bool EnumResNameProcW(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, nint lParam);

            public static readonly IntPtr RT_STRING = new IntPtr(6);

            [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
            public static extern IntPtr FindResourceW(
                IntPtr hModule,
                IntPtr lpName,
                IntPtr lpType);

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

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

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

    public sealed class SafeModuleHandle : SafeHandle
    {
        private static class NativeMethods
        {
            [DllImport("kernel32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool FreeLibrary(IntPtr hLibModule);
        }

        public SafeModuleHandle()
            : base(IntPtr.Zero, true)
        {
        }

        public SafeModuleHandle(IntPtr handle, bool ownsHandle)
            : base(handle, ownsHandle)
        {
        }

        public override bool IsInvalid => handle == IntPtr.Zero;

        protected override bool ReleaseHandle()
        {
            return NativeMethods.FreeLibrary(handle);
        }
    }
}

HMODULE型のSafeHandleによるラップ

LoadLibraryEx関数の戻り値はHMODULE型のハンドルであり、使用後はFreeLibrary関数で解放する必要があります。C#ではusing構文とSafeHandleの派生クラスにより確実な解放を保証できるため、ここではSafeHandleを継承したクラスを作成しています。

このハンドルを解放しなかった場合、プロセスの終了までライブラリがメモリ存在したままとなります。なお、同じHMODULE型を返す関数でもGetModuleHandle関数の戻り値は基本的に解放してはいけません。

LoadLibraryEx関数とフラグ

LoadLobraryEx関数の呼び出し時にはいくつかのフラグを指定することができます。今回は既定の場所からPEファイルを読み込み、そのリソースのみが必要なので以下のフラグを指定しています。

フラグ 目的
DONT_RESOLVE_DLL_REFERENCES DLL参照の解決を無効化する。
LOAD_LIBRARY_AS_DATAFILE データファイルとして読み込む。
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 既定の場所から検索する。

LoadResource関数とLockResource関数

LoadResource関数とLockResource関数は後方互換性のために別々に存在します。Windows 10ではどちらも同じ値を返します。戻り値はプロセスの終了時に自動的に解放されます(Microsoft Docs)。

ポインタから整数や文字列の読み込み

System.Runtime.InteropServices名前空間のMarshalクラスを使用してポインタから整数や文字列を読み込むことができます。16ビット整数はMarshal.ReadInt16、UTF-16文字列の読み込みはMarshal.PtrToStringUniです。

RT_STRING型リソースの中身

RT_STRING型リソースは各IDに長さ(UInt16、2バイト)とその長さのUTF-16文字列(Char型)のペアが1~32個含まれます。各IDに含まれる文字列の個数は記録されていませんが、SizeofResource関数で取得したバイト数まで上記ペアを読み込むことですべての文字列を取得できます。

RT_STRING型リソースの模式図
#ID 1 <- バイト数はSizeofResource関数で取得
Length(2バイト), String(Length文字 = Length*2バイト)
...
Length(2バイト), String(Length文字 = Length*2バイト) <- 最大32個

#ID 2
Length(2バイト), String(Length文字 = Length*2バイト)
...
Length(2バイト), String(Length文字 = Length*2バイト)

...

#ID N <- EnumResourceNamesで見つかった個数だけ存在
...
```
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?