言語と動作確認環境
- 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や海外サイトでも公開されていますが、自身の学習のために作成しています。
サンプルコード
usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Runtime.InteropServices;namespaceConsoleApp1{classProgram{staticvoidMain(){usingvarhandle=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){thrownewWin32Exception();}varstrings=GetStringResources(handle.DangerousGetHandle());}/// <summary>/// ある型のリソースのIDをすべて取得します。/// </summary>privatestaticushort[]GetResourceIDs(IntPtrmoduleHandle,IntPtrresourceType){varresnames=newList<ushort>();NativeMethods.EnumResourceNamesW(moduleHandle,resourceType,(IntPtrhModule,IntPtrlpszType,IntPtrlpszName,nintlParam)=>{varid=lpszName.ToInt64();if(id>>16!=0)thrownewException();resnames.Add((ushort)id);returntrue;},0);returnresnames.ToArray();}/// <summary>/// モジュールの文字列リソースをすべて取得します。/// </summary>privatestaticstring[]GetStringResources(IntPtrmoduleHandle){varstringResIds=GetResourceIDs(moduleHandle,NativeMethods.RT_STRING);Array.Sort(stringResIds);varstrings=newList<string>();foreach(varstrResIdinstringResIds){varresHandle=NativeMethods.FindResourceW(moduleHandle,newIntPtr(strResId),NativeMethods.RT_STRING);varmemoryHandle=NativeMethods.LoadResource(moduleHandle,resHandle);varsize=NativeMethods.SizeofResource(moduleHandle,resHandle);// pointerの中身は2バイト(文字数N)+N*2バイト(UTF-16文字列)の配列varpointer=NativeMethods.LockResource(memoryHandle);for(intoffset=0;offset<size;){uintlen=(ushort)Marshal.ReadInt16(pointer+offset);strings.Add(Marshal.PtrToStringUni(pointer+offset+sizeof(ushort),(int)len));offset+=sizeof(ushort)+(int)len*2;}}returnstrings.ToArray();}privatestaticclassNativeMethods{[DllImport("kernel32.dll",SetLastError=true,ExactSpelling=true,CharSet=CharSet.Unicode)]publicstaticexternSafeModuleHandleLoadLibraryExW([In]stringlpLibFileName,IntPtrhFile,uintdwFlags);[DllImport("kernel32.dll",SetLastError=true,ExactSpelling=true,CharSet=CharSet.Unicode)][return:MarshalAs(UnmanagedType.Bool)]publicstaticexternboolEnumResourceNamesW(IntPtrhModule,IntPtrlpType,EnumResNameProcWlpEnumFunc,nintlParam);publicconstuintDONT_RESOLVE_DLL_REFERENCES=0x00000001;publicconstuintLOAD_LIBRARY_AS_DATAFILE=0x00000002;publicconstuintLOAD_LIBRARY_SEARCH_DEFAULT_DIRS=0x00001000;[UnmanagedFunctionPointer(CallingConvention.StdCall,CharSet=CharSet.Unicode)][return:MarshalAs(UnmanagedType.Bool)]publicdelegateboolEnumResNameProcW(IntPtrhModule,IntPtrlpszType,IntPtrlpszName,nintlParam);publicstaticreadonlyIntPtrRT_STRING=newIntPtr(6);[DllImport("kernel32.dll",SetLastError=true,ExactSpelling=true,CharSet=CharSet.Unicode)]publicstaticexternIntPtrFindResourceW(IntPtrhModule,IntPtrlpName,IntPtrlpType);[DllImport("kernel32.dll",SetLastError=true)]publicstaticexternIntPtrLoadResource(IntPtrhModule,IntPtrhResInfo);[DllImport("kernel32.dll",SetLastError=true)]publicstaticexternIntPtrLockResource(IntPtrhResData);[DllImport("kernel32.dll",SetLastError=true)]publicstaticexternuintSizeofResource(IntPtrhModule,IntPtrhResInfo);}}publicsealedclassSafeModuleHandle:SafeHandle{privatestaticclassNativeMethods{[DllImport("kernel32.dll")][return:MarshalAs(UnmanagedType.Bool)]publicstaticexternboolFreeLibrary(IntPtrhLibModule);}publicSafeModuleHandle():base(IntPtr.Zero,true){}publicSafeModuleHandle(IntPtrhandle,boolownsHandle):base(handle,ownsHandle){}publicoverrideboolIsInvalid=>handle==IntPtr.Zero;protectedoverrideboolReleaseHandle(){returnNativeMethods.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
関数で取得したバイト数まで上記ペアを読み込むことですべての文字列を取得できます。
#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で見つかった個数だけ存在
...