アセンブリへのリソースの埋め込みと読み込み

注意:
この文書は以前「.NETでいきまっしょい!」で公開していたものですが、公開以降メンテナンスされていません。 今や古い情報となった内容が記載されている場合があるのでご注意ください。

1.アセンブリへのリソースの埋め込み


 VBやVC++では、アプリケーションが使用するビットマップやアイコンなどのイメージ、マルチメディアファイル、テキストなどのいくつかのファイルをリソースとして実行可能ファイルに埋め込むことができます。 これによって外部ファイルとしてそれぞれ単体で存在するものを実行可能ファイルとしてひとまとめにすることができます。 Visual Studio .NETでもリソースを実行可能ファイルに埋め込むことができます。 その方法を順を追って見てみます。
1.リソースを用意する (新しく作る)
 リソースを埋め込むにはリソースとなるファイルが存在している必要があります。 あらかじめ用意しておくこともできますし、数種のリソースはVisual Studio .NETで作成することもできます。 新しくリソースを作成するには、ソリューションエクスプローラでプロジェクトを右クリックして「追加」メニューの「新しい項目の追加」をクリックします。

新しい項目の追加

 現れたダイアログの中から追加したい項目を選び、適当なファイル名を与えてやります。 ここではアイコンを選択し、「ApplicationIcon.ico」と名付けることにします。 種類とファイル名を決めたら、「開く」ボタンを押します。

リソースの種類を選択

 リソースがプロジェクトに追加されるので、あとは生成されたリソースを編集します。

プロジェクトに追加されたリソース
2.リソースを用意する (既存のものを利用する)
 Visual Studio .NETのアイコンエディタなどは非常に使いにくいので、画像などのリソースはフォトショップなど専門のアプリケーションを用いて編集し、完成したものをリソースとして埋め込むことが多いと思います。 既存のリソースはリソースの新規作成と同様の方法で、「追加」メニューの「既存項目の追加」をクリックします。

既存項目の追加

 現れたダイアログの中から目的のファイルを選択します。 選択したら「開く」ボタンを押します。

追加する項目を選択

 これで目的のファイルがプロジェクトに追加されました。

追加する項目を選択
3.リソースのプロパティを変更する
 このままだとただのプロジェクトファイルなので、プロパティを変更してビルド時にアセンブリに埋め込まれるようにします。 埋め込みたいファイルを選択し、プロパティページで「ビルド アクション」を「埋め込まれたリソース」に変更します。

プロパティを変更

2.埋め込まれたリソースの読み込み


 アセンブリに埋め込まれたリソースを実行時に読み込むには、大まかに次の手順を踏みます。

1. Reflection.Assembly.GetExecutingAssembly()メソッドにより現在実行中のアセンブリを取得する。
2. 取得できたアセンブリに対してGetManifestResourceStream()を呼び出し、指定したリソースに対するストリームを取得する。
3. 取得できたストリームから必要なデータを読み込む。

 次のコードではこの手順により先ほど埋め込んだリソースを読み込んでいます。 この例ではSample.txtというテキストファイルもリソースとして埋め込んでいます。
リソースの読み込み
Sample.txt の内容
Sample.txt
これはリソースとして埋め込まれたテキストファイルです。
テキストも当然読み込むことができます。
ソースコード
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
Option Strict On

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows フォーム デザイナで生成されたコード "
#End Region

    ' アイコン
    Dim ico As Drawing.Icon

    ' 画像
    Dim img As Drawing.Image

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        ' 現在実行中のアセンブリを取得
        Dim assm As Reflection.Assembly = Reflection.Assembly.GetExecutingAssembly()

        ' リソースを読み込むためのストリームを宣言
        Dim stream As IO.Stream



        ' アイコンのリソースのストリームを取得
        stream = assm.GetManifestResourceStream("ResourceAndMultimedia.ApplicationIcon.ico")

        ' ストリームを引数として取るコンストラクタを使用
        ico = New Drawing.Icon(stream)

        ' ストリームをクローズ
        stream.Close()

        ' 取得したアイコンをフォームのアイコンとして指定
        Me.Icon = ico



        ' イメージのリソースのストリームを取得
        stream = assm.GetManifestResourceStream("ResourceAndMultimedia.Sample.jpg")

        ' ストリームを引数として取るコンストラクタを使用
        img = New Drawing.Bitmap(stream)

        ' ストリームをクローズ
        stream.Close()



        ' テキストのリソースのストリームを取得
        stream = assm.GetManifestResourceStream("ResourceAndMultimedia.Sample.txt")

        ' リソースのストリームからStreamReaderオブジェクトを作成
        Dim reader As New IO.StreamReader(stream, System.Text.Encoding.GetEncoding(932))

        TextBox1.Text = ""

        ' 一行ずつ読み込み
        While (reader.Peek <> -1)

            TextBox1.Text += reader.ReadLine() + vbNewLine

        End While

        ' ストリームをクローズ
        reader.Close()

    End Sub


    Private Sub Form1_Paint(ByVal sender As System.Object, _
                 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint

        e.Graphics.Clear(Control.DefaultBackColor)

        ' フォーム上に画像を描画
        e.Graphics.DrawImage(img, 0, 0)

    End Sub

End Class
実行結果
実行結果

 GetManifestResourceStream()メソッドでリソースのストリームを取得する際には、目的のリソースの名前を指定する必要があります。 その名前は「プロジェクト名.ファイル名」となります。 この例ではプロジェクト名が「ResourcesAndMultimedia」なので、たとえば「Sample.jpg」というファイル名のリソースを参照したい場合は「ResourcesAndMultimedia.Sample.jpg」となります。 ちなみに、このメソッドで目的のリソースが発見できなかった場合、Nothingが返されます。


リソースが発見できなかった場合

 それ以外のソースコードについても、ほとんど問題ないと思います。 BitmapおよびIconクラスはStreamオブジェクトを利用してインスタンスを作成するためのコンストラクタが用意されているため、これに取得できたストリームを渡すだけでイメージのインスタンスを作成することができます。 また、取得できたストリームを元にしてStreamReaderクラスなどのオブジェクトを作成することもできます。 テキストファイルのリソースの読み込みは、この方法を用いて一行ずつ読み込んでいます。
 これを実行した結果が上の図です。 小さくて見づらいですが、アイコンもちゃんと変わっています。 下の図はタスクバーの部分を拡大したものです。


実行結果

Waveファイルの埋め込み・読み込み・再生


 リソースをイメージやテキストなどの限定的なオブジェクトとしてではなく、ストリームとして取得できることから、様々な操作を行うことができます。 リソースとしてWaveを埋め込み、独自のアプリケーションの警告音として使用したい場合などにも今まで紹介してきた方法と同じ方法が利用できます。 次のコードでは、アセンブリにSample.wavというファイルを埋め込んでおき、実行時にそれをバイト配列にバッファとして読み込み、ボタンが押された時点でそれを鳴らすということをしています。
Waveファイルの埋め込み・読み込み・再生
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
Option Strict On

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows フォーム デザイナで生成されたコード "
#End Region

    ' バッファ(バイト配列)から音を鳴らすためのPlaySound関数 (API関数)
    <Runtime.InteropServices.DllImport("winmm.dll", EntryPoint:="PlaySound")> _
    Private Shared Function PlaySoundFromMemory( _
       <Runtime.InteropServices.MarshalAs(Runtime.InteropServices.UnmanagedType.LPArray)> ByVal lpBuffer As Byte(), _
       ByVal hModule As Integer, _
       ByVal dwFlags As Integer) _
       As Integer
    End Function

    ' PlaySound関数で再生方法を指定する定数
    Private Const SND_ASYNC As Integer = &H1    ' 非同期再生
    Private Const SND_MEMORY As Integer = &H4    ' バッファからの再生

    ' バッファとしてのバイト配列
    Dim soundBuffer() As Byte

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        ' 現在実行中のアセンブリを取得
        Dim assm As Reflection.Assembly = Reflection.Assembly.GetExecutingAssembly()

        ' リソースのストリームを取得
        Dim stream As IO.Stream = assm.GetManifestResourceStream("ResourceAndMultimedia.Sample.wav")

        ' 取得できたストリームを元にバイナリ形式で読み込むためのBinaryReaderオブジェクトを作成
        Dim reader As New IO.BinaryReader(stream)

        ' バッファにリソースの内容を読み込み
        soundBuffer = reader.ReadBytes(CInt(reader.BaseStream.Length))

        ' ストリームをクローズ
        reader.Close()

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' バッファを再生
        PlaySoundFromMemory(soundBuffer, Nothing, SND_ASYNC Or SND_MEMORY)

    End Sub

End Class

 まず、Wave音声を再生するためのAPI関数であるPlaySound関数について説明する必要があります。 APIとは何か、またAPIを詳しく知らない方はそういうものだと思って読み飛ばしてもかまいません。 この関数は、Waveファイル・リソース・システムの音声のいずれかのWave音声を鳴らすものです。 この場合のリソースは、ここで使用しているリソースの形式と異なるので利用することはできません。 この関数の一つ目のパラメータはLPCSTR型で、三つ目のパラメータでSND_FILENAMEフラグを指定した場合はファイル名を指定し、SND_MEMORYフラグを指定した場合はバッファとして格納されているWaveイメージへの先頭ポインタを指定します。 この例では後者の方法を利用しています。 また、二番目のパラメータは指定すべき値がないのでNothingにしておきます。
 このAPIをVB.NETで使用するため、DllImport属性を利用してインポートしています。 また、バッファからの再生をサポートするため、LPCSTR型である一つ目のパラメータは、MarshalAs属性で配列へのポインタ型へマーシャリングします。 そして、メモリからの再生に特化したPlaySoundであることを示すために、メソッド名をPlaySoundFromMemory()に変更しました。 ちなみに、C++形式でのPlaySound関数の宣言は次のようになっています。
PlaySound関数の宣言
001
002
003
004
005
BOOL PlaySound(
    LPCSTR pszSound,  // 再生対象のサウンド
    HMODULE hmod,     // インスタンスハンドル
    DWORD fdwSound    // 再生フラグ
);

 リソースからWaveファイルを読み込む場合は、バイト配列として取得することでPlaySound関数が利用可能になるため、BinaryReaderクラスのReadBytes()メソッドを利用して直接バイト配列を取得しています。 後は、再生したいときにPlaySoundFromMemory関数にWaveイメージが格納されたバッファと再生フラグを渡すだけで再生することができます。