potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# imageres.dll.munからProgressRingのリソース(PNGデータ)を読み込む

動作確認環境:C# 9.0 preview

C#imageres.dll.munからProgressRingのリソースのPNGデータを読み込むサンプルコードです。実際には複数のPNGリソースが保管されているため、代表としてリソースID 5021を読み込んでいます。

Win32APIのLockResource関数はリソースデータのアドレスを返すため、通常はバイト配列にMarshal.Copyした後MemoryStreamを介してImageクラスのインスタンスを作成できます。unsafe環境ではアドレスを直接UnmanagedMemoryStreamに渡してImageクラスのインスタンスを作成できます。

unsafe

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

// DLLの場所をシステムフォルダに限定(DLLハイジャック対策)
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]


// リソースファイルからProgressRingのPNGデータを読み込む。
using var imageResDllMunHandle = SafeLibraryHandle.Load(
    @"SystemResources\imageres.dll.mun");
var pngData = Utility.LoadResource(imageResDllMunHandle, 5021, "PNG");

// ProgressRingのPNGデータから画像を作成する。
using var pngImage = Utility.CreateImageFromByteArray(pngData);

// ProgressRingの画像をフォームへ表示する。
var mainForm = new Form();
var pictureBox = new PictureBox();
pictureBox.Image = pngImage;
pictureBox.AutoSize = true;
mainForm.Controls.Add(pictureBox);
Application.Run(mainForm);


static class Utility
{
    /// <summary>
    /// バイト配列からImageインスタンスを返します。
    /// </summary>
    public static Image CreateImageFromByteArray(byte[] data)
    {
        using var stream = new MemoryStream(data, false);
        return Image.FromStream(stream);
    }

    /// <summary>
    /// モジュールのリソースデータを読み込みます。
    /// </summary>
    /// <remarks>
    /// モジュールが解放されても使用できるように関数はバイト配列を返します。
    /// </remarks>
    public static byte[] LoadResource(SafeHandle handle, ushort id, string type)
    {
        return LoadResource(handle, "#" + id, type);
    }

    /// <summary>
    /// モジュールのリソースデータを読み込みます。
    /// </summary>
    /// <remarks>
    /// モジュールが解放されても使用できるように関数はバイト配列を返します。
    /// </remarks>
    public static byte[] LoadResource(SafeHandle handle, string name, string type)
    {
        var resInfoHandle = NativeMethods.FindResourceW(handle, name, type);
        if (resInfoHandle == default) throw new Win32Exception();

        var resDataHandle = NativeMethods.LoadResource(handle, resInfoHandle);
        if (resDataHandle == default) throw new Win32Exception();

        var resSize = NativeMethods.SizeofResource(handle, resInfoHandle);
        if (resSize == default) throw new Win32Exception();

        var resPointer = NativeMethods.LockResource(resDataHandle);
        if (resPointer == default) throw new Win32Exception();

        var buffer = new byte[resSize];
        Marshal.Copy(resPointer, buffer, 0, buffer.Length);

        return buffer;
    }

    private static class NativeMethods
    {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
        public static extern IntPtr FindResourceW(SafeHandle hModule, [In] string lpName, [In] string lpType);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadResource(SafeHandle 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(SafeHandle hModule, IntPtr hResInfo);
    }
}

/// <summary>
/// ライブラリハンドルを安全に管理します。
/// </summary>
sealed class SafeLibraryHandle : SafeHandle
{
    private SafeLibraryHandle()
        : base(default, false)
    { }

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

    protected override bool ReleaseHandle()
    {
        NativeLibrary.Free(handle);
        return true;
    }

    public override bool IsInvalid => handle == default;

    public static SafeLibraryHandle Load(string path)
    {
        IntPtr handle = default;
        try
        {
            handle = NativeLibrary.Load(path);
            return new SafeLibraryHandle(handle, true);
        }
        catch
        {
            NativeLibrary.Free(handle);
            throw;
        }
    }
}

unsafe

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

// DLLの場所をシステムフォルダに限定(DLLハイジャック対策)
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]


// リソースファイルからProgressRingのPNGデータを読み込む。
using var imageResDllMunHandle = SafeLibraryHandle.Load(
    @"SystemResources\imageres.dll.mun");
var pngImage = Utility.LoadResourceAsImage(imageResDllMunHandle, 5021, "PNG");

// ProgressRingの画像をフォームへ表示する。
var mainForm = new Form();
var pictureBox = new PictureBox();
pictureBox.Image = pngImage;
pictureBox.AutoSize = true;
mainForm.Controls.Add(pictureBox);
Application.Run(mainForm);


static class Utility
{
    /// <summary>
    /// モジュールのリソースデータをImageクラスのインスタンスとして読み込みます。
    /// </summary>
    /// <remarks>
    /// モジュールが解放されても使用できるように関数はバイト配列を返します。
    /// </remarks>
    public static Image LoadResourceAsImage(SafeHandle handle, ushort id, string type)
    {
        return LoadResourceAsImage(handle, "#" + id, type);
    }

    /// <summary>
    /// モジュールのリソースデータをImageクラスのインスタンスとして読み込みます。
    /// </summary>
    /// <remarks>
    /// モジュールが解放されても使用できるように関数はバイト配列を返します。
    /// </remarks>
    public static Image LoadResourceAsImage(SafeHandle handle, string name, string type)
    {
        var resInfoHandle = NativeMethods.FindResourceW(handle, name, type);
        if (resInfoHandle == default) throw new Win32Exception();

        var resDataHandle = NativeMethods.LoadResource(handle, resInfoHandle);
        if (resDataHandle == default) throw new Win32Exception();

        var resSize = NativeMethods.SizeofResource(handle, resInfoHandle);
        if (resSize == default) throw new Win32Exception();

        var resPointer = NativeMethods.LockResource(resDataHandle);
        if (resPointer == default) throw new Win32Exception();

        unsafe
        {
            using var stream = new UnmanagedMemoryStream((byte*)resPointer, resSize);
            return Image.FromStream(stream);
        }
    }

    private static class NativeMethods
    {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
        public static extern IntPtr FindResourceW(SafeHandle hModule, [In] string lpName, [In] string lpType);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadResource(SafeHandle 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(SafeHandle hModule, IntPtr hResInfo);
    }
}

/// <summary>
/// ライブラリハンドルを安全に管理します。
/// </summary>
sealed class SafeLibraryHandle : SafeHandle
{
    private SafeLibraryHandle()
        : base(default, false)
    { }

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

    protected override bool ReleaseHandle()
    {
        NativeLibrary.Free(handle);
        return true;
    }

    public override bool IsInvalid => handle == default;

    public static SafeLibraryHandle Load(string path)
    {
        IntPtr handle = default;
        try
        {
            handle = NativeLibrary.Load(path);
            return new SafeLibraryHandle(handle, true);
        }
        catch
        {
            NativeLibrary.Free(handle);
            throw;
        }
    }
}