Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年09月26日] [更新 2022年09月26日] Delphi 一般・その他
EXE に埋め込んだリソースの読み書き
動作確認等 Windows 10 ビルド 19043 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 1200]







EXE に埋め込んだリソースの読み書き


EXE に埋め込んだリソースを読み出したり、変更するサンプルです。
起動中の EXE のリソースを書き換えることはできません。本ページのサンプルは、他の、実行中ではない EXE のリソースを操作する例です。本ページで扱うリソースは、ユーザが [プロジェクト] [リソースと画像] のリソースマネージャで追加したリソースです。

このリソースには、形式の異なる複数のデータを格納することができます。リソースを埋め込んだ EXE を、アプリのデータ保存用ファイルとして利用することが考えられます。



[備考]
Delphi 2009 より前の Delphi には、リソースマネージャの機能はありません。Halbow 資料館の記事を参考にしてください。




01_リソースだけを埋め込んだ EXE を作成


次のコードをテキストエディタに貼り付けて、EXERes.dpr の名前で保存します。保存したら、このファイルを Delphi の IDE で開きます。このファイルは、フォームを使用しない Delphi のプロジェクトファイルです。



リスト1
program EXERes;

{$R *.dres}

uses
  Winapi.Windows;

var
  LuType : Cardinal;

begin
  LuType := MB_ICONWARNING or MB_TOPMOST;
  MessageBox(0, 'このファイルは実行できません。', '実行不可', LuType);
end.


テキストファイルと、ビットマップ画像を用意しておきます。テキストファイルには適当なテキストを書き込み、Unicode (UTF-16) の little endian で保存します。メモ帳の場合は、文字コードを UTF-16LE にして保存します。
IDE のメニューの [プロジェクト] から [リソースと画像] を選択しすると下図のダイアログが現れます。これが [リソースマネージャ] です。[追加] ボタンで、用意しておいたテキストファイルとビットマップ画像を追加します。テキストファイルの [リソース識別子] を Text_1 とします。
[OK] ボタンでダイアログを閉じます。



図1
埋め込んだリソース

このプロジェクトを保存したら、[Shift] + [f9] または [Ctrl] + [F9] でコンパイルします。IDE を閉じて、作成した EXE を実行したのが下図です。
このプログラムは、このダイアログを表示するだけです。
リソースを変更した場合は、[Shift] + [F9] でコンパイルします。



図2
作成した EXE を起動
  • ダイアログだけは表示するようにした




02_リソースだけを埋め込んだ EXE を使用するアプリを作成
EXEResource.zip [903 KB] 2022年09月26日版 (EXE同梱)


作成した EXERes.exe のリソースを操作するプログラムを作成します。
新規に VCL フォームアプリケーションを作成して、図の下のコードでテストした結果です。EXERes.exe を、このプロジェクトの EXE と同じフォルダ内に置いています。テキストと画像は、EXERes.exe にあらかじめ埋め込んだリソースです。



図3
作成した EXE を起動
  • EXERes.exe のリソースを読み込んで表示

Button2 をクリックすると、別の画像を TImage に描画します。TMemo のテキストを編集して EXE を終了します。フォームを閉じる時に、EXERes.exe のリソースを、これらの画像とテキストに置き換えます。その結果、再度 EXE を起動すると、変更した画像とテキストが表示されます。



図4
[Button1] をクリック
  • 画像を変更
  • テキストは手作業で変更
  • フォームを閉じる
  • 再度 EXE を起動
  • この図と同じになる

テストに使用したコードです。
自分自身の EXE のインスタンスハンドルは、SysInt ユニットのグローバル変数 HInstance が使用できますが、他の EXE の場合は LoadLibraryEx 関数で取得します。使用しなくなったら FreeLibrary 関数でそのインスタンスを開放します。
リソースの書き換えの場合は、BeginUpdateResource 関数で取得したインスタンスを使用します。この関数で取得したインスタンスは、 EndUpdateResource を実行すると、リソースの更新と共に解放されます。



リスト2
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Image1: TImage;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    FEXEResPath : string;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//
//  外部の EXE に埋め込んだリソースを読み出して表示
//  埋め込んだリソースは文字列とビットマップ画像
//
//  リソースの文字列は Unicode と仮定
//  メモ帳の場合、文字コードを UTF-16LE (little endian) にして保存したテキスト
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LhModule    : THandle;
  LTextStream : TResourceStream;
  LBitmap     : TBitmap;
begin
  // 外部 EXE のフルパス 
  FEXEResPath := ExtractFilePath(Application.ExeName) + 'EXERes.exe';
//
  // EXE のモジュールハンドルを取得
  LhModule := LoadLibraryEx(PChar(FEXEResPath),
                       0,
                       LOAD_LIBRARY_AS_DATAFILE or LOAD_WITH_ALTERED_SEARCH_PATH);
  if LhModule = 0 then Exit;

  LTextStream := TResourceStream.Create(LhModule, 'Text_1', RT_RCDATA);
  LBitmap     := TBitmap.Create;
  try
     // 文字列 (テキスト) を取得
     Memo1.Lines.LoadFromStream(LTextStream, TEncoding.Unicode);

     // ビットマップ画像を取得して TImage に描画
     LBitmap.LoadFromResourceName(LhModule, 'Bitmap_1');
     Image1.Picture.Assign(LBitmap);
  finally
    FreeAndNil(LTextStream);
    FreeAndNil(LBitmap);
    FreeLibrary(LhModule);
  end;
end;

//=============================================================================
//  フォームを閉じる時の処理
//  外部 EXE に埋め込んだリソースを書き換える
//=============================================================================
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  LBitmap    : TBitmap;
  LhModule   : THandle;
  LBmpStream : TMemoryStream;
  LResStream : TMemoryStream;
  LText      : string;
begin
  // EXE のモジュールハンドルを取得
  LhModule := BeginUpdateResource(PChar(FEXEResPath), False);
  if LhModule = 0 then Exit;


  LBmpStream := TMemoryStream.Create;
  LResStream := TMemoryStream.Create;
  LBitmap    := TBitmap.Create;
  try
    // 文字列のリソースを書き換える
    LText := Memo1.Lines.Text;
    UpdateResource(LhModule, RT_RCDATA, 'Text_1', 0, 
                   Pointer(LText), Length(LText) * SizeOf(Char));

    // TImage に描画しているビットマップをメモリストリームに格納
    Image1.Picture.Bitmap.SaveToStream(LBmpStream);

    // ビットマップファイルヘッダ部分を削除したメモリストリームを作成
    // リソースのビットマップには、ビットマップファイルヘッダはない
    LResStream.Size := LBmpStream.Size - SizeOf(TBitmapFileHeader);
    LBmpStream.Position := SizeOf(TBitmapFileHeader);
    LResStream.CopyFrom(LBmpStream, LBmpStream.Size - SizeOf(TBitmapFileHeader));

    // ビットマップ画像リソースを書き換える
    UpdateResource(LhModule, RT_BITMAP, 'Bitmap_1', 0,
                   LResStream.Memory, LResStream.Size);
  finally
    // リソースを更新
    EndUpdateResource(LhModule, False);

    FreeAndNil(LBmpStream);
    FreeAndNil(LResStream);
    FreeAndNil(LBitmap);
  end;
end;

//=============================================================================
//  別のビットマップ画像 TImage にを表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBitmap : TBitmap;
begin
  LBitmap := TBitmap.Create;
  try
    if Sender = nil then begin
      LBitmap.LoadFromFile('Smail_01.bmp');
    end else begin
      LBitmap.LoadFromFile('Smail_02.bmp');
    end;
    Image1.Picture.Assign(LBitmap);
  finally
    FreeAndNil(LBitmap);
  end;
end;

//=============================================================================
//  別のビットマップ画像 TImage にを表示
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Button1Click(nil);
end;

end.




03_Windows 10 における偽 EXE ファイルの実行


適当なテキストファイルの拡張子を .exe に変更します。例えば AppIni.exe という名前にします。これは、本当の EXE ファイルではないので、偽 (ニセ) EXE と呼ぶことにします。
エクスプローラで、この偽 EXE をダブルクリックするか、選択して [Enter] キーを押すと、Windows 10 では下図のダイアログが表示されます。


// テキストファイルの拡張子を .exe にした偽 EXE ファイル
AppIni.exe



図5

テキストファイルをメモ帳などのテキストエディタで読めないようにしたいという要望があります。「読めない」という状態が、とのような操作による結果としての状態なのかにもよりますが、この例のように、拡張子を変更して、偽 EXE にししまうことも考えられます。

このテキストファイル AppIni.exe も、起動したメモ帳から [ファイル] [開く] で開けば、メモ帳で表示することができます。メモ帳へのドラッグ・アンド・ドロップでも表示できます。
リソースとして EXE に埋め込んだテキストも同じです。下図は、今回作成したリソースを埋め込んだ EXERes.exe をメモ帳で開いた状態です。リソースのテキストが表示されています。ただし、今回作成した EXERes.exe には、Unicode (UTF-16) で保存しています。したがって、メモ帳へのドラッグ・アンド・ドロップで開くと文字化けします。



図6
EXERes.exe をメモ帳で開く
  • リソースとして埋め込んだ文字列が見えている