Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2015年04月05日] [更新 2017年07月05日] Delphi サンプルプログラム集
   
314_アプリケーションマニフェストの作成とリソースの操作
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
314_CreateAppManifest.zip [4,003 KB] 2017年07月05日版 (EXE 同梱)







アプリケーションマニフェスト


アプリケーションマニフェストについては以下のリンクを参考にしてください.
本ページのサンプルは,主に EXE 内のマニフェストのリソース操作に関するサンプルです.


[備考 1]
Delphi 10.1 Berlin 等,より新しいバージョンの Delphi では,[プロジェクト] [オプション] のダイアログで [アプリケーション] の [マニュフェストファイル] を自動生成にすれば,自動的にアプリケーションマニュフェストが EXE に組み込まれます.また,[管理者権限の有効化] をチェックすれば,作成した EXE を起動すると自動的に管理者への昇格ダイアログを表示します.

[備考 2]
UPX 等の実行ファイル圧縮ソフトで圧縮された EXE,あるいは自動解凍書庫の EXE のリソースの変更は正常に行われません.変更操作を実行すると,対象の EXE が起動できなくなります.




01_EXE からアプリケーションマニフェストのリソースを取り出す


EXE のリソースからアプリケーションマニフェストのリソースを取り出して TMemo に表示するサンプルです.拡張子が .manifest のファイルも選択して表示することができます.
TMemo での編集結果をマニフェストファイルとして保存するようにしています.その時,マニフェストのリソース定義ファイルである RC ファイルも作成して保存します.

EXE ファイル名と .manifest ファイルを Edit1 にドラッグしてリソースあるいはマニフェストファイルの内容を表示できるようにしています.これは IDE 上で実行した場合は機能しません.



図1
設計時画面
図2
リソースを読み込んだ結果
  • この表示内容をマニフェストファイルとして保存する

Delphi XE 以前では,マニフェストをリソースに埋め込むには,リソースの定義ファイルが必要です.このリソースの定義ファイルの拡張子は .rc です.内容は次のようになっています.
最初の 1 はリソースの ID,次の 24 はリソースのタイプで,定数名は RT_MANIFEST です.定数名を使用しても構いません.

1 24 "plSamples.manifest"


図3
保存したファイルの例
  • Delphi XE2 以降では .rc ファイルは不要

リスト1
EXE からアプリケーションマニフェストのリソースを取り出す
マニフェストファイルと RC ファイルの保存機能付き
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    ApplicationEvents1: TApplicationEvents;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    FSourceExePath : String;
    FSaveInitDir   : String;
    function GetManifestText(FileName: String): AnsiString;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses 
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  DragAcceptFilesの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;
  FSourceExePath := '';
  FSaveInitDir   := '';
  Edit1.Text     := '';

  //Edit1へのドラッグアンドドロップを許可
  //対象コントロールはEdit1のみ
  DragAcceptFiles(Edit1.Handle, True);
end;

//=============================================================================
//  EXEまたはmanifestをロードしてTMemoに表示
//  TMemoに直接マニフェストのコードを書いてもよい
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFileExt : String;
  LPos     : Integer;
  LText    : String;
begin
  if Sender <> nil then begin
    if FSourceExePath = '' then begin
      OpenDialog1.InitialDir := ExtractFileDir(FSourceExePath);
    end;
    OpenDialog1.Filter := '|*.exe;*.manifest|';
    if not OpenDialog1.Execute then exit;

    FSourceExePath := OpenDialog1.FileName;
  end;

  Edit1.Text := FSourceExePath;
  LFileExt   := ExtractFileExt(FSourceExePath);

  if (UpperCase(LFileExt) = '.EXE') then begin
    Memo1.Lines.Text := String(GetManifestText(FSourceExePath));

    //先頭にゴミが入ることがあるのでその対策
    LText := Memo1.Lines[0];
    LPos  := Pos('<', LText);
    if LPos >= 1 then begin
      Memo1.Lines[0] := Copy(LText, LPos, Length(LText));
    end;
  end else
  if (UpperCase(LFileExt) = '.MANIFEST') then begin
    Memo1.Lines.LoadFromFile(FSourceExePath);
  end;

  if Trim(Memo1.Lines.Text) = '' then begin
    MessageBox(Handle, 'マニフェストはありません', '情報', MB_ICONINFORMATION);
  end;
end;

//=============================================================================
//  マニフェストファイルとRCファイルの保存
//  Delphi XE2 以降ではrcファイルは不要
//  現在TMemoに表示している内容をマニフェストとして保存
//  拡張子は自動付加
//  マニフェストとしていの正当性は確認していないので注意
//
//   1 : マニフェストのリソースのIDの値
//  24 : マニフェストのリソースのタイプの値(定数名はRT_MANIFEST)
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LSavePath : String;
  LRcList   : TStringList;
begin
  if FSaveInitDir = '' then begin
    FSaveInitDir := ExtractFilePath(Application.ExeName);
  end;
  SaveDialog1.InitialDir := FSaveInitDir;
  if not SaveDialog1.Execute then exit;
  if Trim(Memo1.Lines.Text) = '' then exit;


  LSavePath := SaveDialog1.FileName;
  //マニフェストファイルの保存
  //UTF-8で保存する
  LSavePath := ChangeFileExt(LSavePath, '.manifest');
  Memo1.Lines.SaveToFile(LSavePath, TEncoding.UTF8);

  //RCファイルの保存
  LRcList := TStringList.Create;
  try
    LRcList.Add('1 24 ' + AnsiQuotedStr(ExtractFileName(LSavePath), '"'));
    LSavePath := ChangeFileExt(LSavePath, '.rc');
    LRcList.SaveToFile(LSavePath);
  finally
    FreeAndNil(LRcList);
  end;
  FSaveInitDir := ExtractFileDir(LSavePath);
end;

//-----------------------------------------------------------------------------
//  EXEのリソースからRT_MANIFESTのリソースのテキストを取り出す
//  FileNameはEXEのフルパス
//  戻り値はAnsiString型の文字列
//-----------------------------------------------------------------------------
function TForm1.GetManifestText(FileName: String): AnsiString;
var
  LhModule  : THandle;
  LResource : TResourceStream;
begin
  Result := '';

  //ファイルの読み込み
  LhModule := LoadLibraryEx(PChar(FileName), 0, LOAD_LIBRARY_AS_DATAFILE);
  try
     if LhModule <> 0 then begin
       //マニフェストのリソースの確認
       if FindResource(LhModule, MakeIntResource(1), RT_MANIFEST) <> 0 then begin
         //リソースの読み込み
         LResource := TResourceStream.CreateFromID(LhModule, 1, RT_MANIFEST);
         try
           try
             SetLength(Result, LResource.Size);
             LResource.ReadBuffer(Result[1], LResource.Size);
           except
             Result := '';
           end;
         finally
           FreeAndNil(LResource);
         end;
       end;
     end;
  finally
    FreeLibrary(LhModule);
  end;
end;

//=============================================================================
//  TApplicationEventsのOnMessageイベント処理
//  エクスプローラからのファイルのドラッグ・ドロップ処理
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  LCount  : Integer;
  LBuffer : array [0..MAX_PATH-1] of char;
begin
  if Msg.message = WM_DROPFILES then begin
    //ドラッグドロップしたファイル数を取得
    LCount := DragQueryFile(Msg.wParam, High(Cardinal), nil, 0);
    if LCount = 1 then begin
      //ドラッグドロップしたファイル名を取得
      DragQueryFile(Msg.WParam, 0, LBuffer, MAX_PATH);
      FSourceExePath := LBuffer;
      //マニフェストのテキストを表示
      Button1Click(nil);
    end;
    //ドロップが完了したことを伝える
    DragFinish(msg.wParam);
  end;
end;

end.




02_外部アプリケーションマニフェストファイルの使用


アプリケーションマニフェストは,EXE を作成する際に [ランタイムテーマを有効にする] に設定するか,外部のマニフェストファイルをリソースに指定して利用するのが基本です.
アプリケーションマニフェストを指定しないで作成した EXE でも,アプリケーションマニフェストファイルに書いた内容を有効にする方法があります.それには,
  • マニフェストファイルの拡張子を除いたファイル名は,EXE 名と同じにする
  • マニフェストファイルと EXE は同じフォルダ内に置く
  • マニフェストファイルは UTF-8 で保存する
という条件が必要です.
下図は添付のテスト用のフォルダ内のファイルです.上記の条件を満たしています.


図4
作成したマニフェストファイル
  • マニフェストファイルは EXE と同じフォルダ内に置く
  • 拡張子を除いたファイル名を EXE の名前と同じにする

Windows XP であれば,この方法で常にマニフェストファイルの設定が有効となり,ランタイムテーマを有効にすることができます.
しかし,Windows Vista 以降では,以下のような現象が発生します.
  • (1) アプリケーションマニフェストを組み込んでいない EXE を任意のフォルダに置く
  • (2) その EXE を実行する.当然テーマは有効とならない
  • (3) 作成したマニフェストファイルを,その EXE があるフォルダに置く
  • (4) EXE を実行してもテーマが有効にならない

MSDN の記事によれば,Windows XP では外部のマニフェストファイルが優先されるが,Windows Vista 以降では EXE 内部に埋め込まれたマニフェストのコードが優先して使用され,外部マニフェストは使用されないとあります.
しかし,実際には利用可能なようです.上記の現象は,[アプリケーションキャッシュ] という機能によるものらしいのですが,詳細については不明です.この現象は,EXE の作成日時を変更することで回避できるようです.もし,マニフェストファイルを EXE と同じフォルダに置いてもテーマが有効にならない場合は,EXE の作成日時と更新日時を変更してテストしてください.

下図は添付の Test.exe を Windows 7 U64(SP1) で実行した結果です.



図5
マニフェストファイルがない場合
図6
マニフェストファイルがある場合

下図は Windows 8.1 Pro64 での実行例です.最初から EXE ファイルとマニフェストファイルを同じファイルに置いて EXE を実行しました.2 番目の図は,マニフェストファイルを削除して実行した結果です.再度,マニフェストファイルを EXE ファイルと同じフォルダ内におけば,またランタイムテーマが有効になります.


図7
マニフェストファイルがある場合
図8
マニフェストファイルを削除した場合




03_EXE にアプリケーションマニフェストを埋め込む


アプリケーションマニフェストのコードは,コンパイルすることによって EXE のリソースとして埋め込まれます.
本サンプルは,作成済みの EXE に直接マニフェストのコードをリソースとして埋め込みます.そうすると,マニフェストのコードを変更しても,再コンパイルの必要がありません.
マニフェストのコードを変更して埋め込む場合は,リソースの上書き,つまり,リソースの更新となります.リソースの削除は文字通り削除です.マニフェストのリソースが EXE 内にない場合は何もしません.
本ページの最初のサンプルに,リソースの埋め込みと削除のコードを追加しています.


[備考 1]
実行中の EXE のリソースの書き換えはできません.


[備考 2]
UpdateResource 関数を使用して EXE 内のリソースを操作すると,PADDINGXX という文字列の繰り返しが EXE の末尾に挿入されます.この文字列は PE ヘッダのセッション再配置用のパディングらしいのですが,詳細については不明です.
リソースは 4 の倍数のオフセットに格納されていなければなりません.これの調整のための残骸ではないかと思われます.



図9
設計時画面
図10
TMemo に表示している内容を EXE にリソースとして埋め込む

リスト2
EXE にマニフェストのリソースを埋め込むコードとリソースを削除するコード
最初のサンプルに追加したコード
//=============================================================================
//  マニフェストをEXEに埋め込む
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LExtFullPath : String;
begin
  SaveDialog1.Filter     := '|*.exe|';
  SaveDialog1.InitialDir := ExtractFileDir(FSaveExeName);
  SaveDialog1.FileName   := ExtractFileName(FSaveExeName);
  if not SaveDialog1.Execute then exit;
  if Trim(Memo1.Lines.Text) = '' then exit;


  LExtFullPath := SaveDialog1.FileName;
  //マニフェストをEXEに埋め込む
  if SetManifestToExe(LExtFullPath, Trim(Memo1.Text)) then begin
    MessageBox(Handle, '埋め込みに成功しました', '情報', MB_ICONINFORMATION);
  end else begin
    MessageBox(Handle, '埋め込みに失敗しました', '情報', MB_ICONWARNING);
  end;
end;

//=============================================================================
//  EXEからマニフェストのリソースを削除する
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LExtFullPath : String;
begin
  SaveDialog1.Filter     := '|*.exe|';
  SaveDialog1.InitialDir := ExtractFileDir(FSaveExeName);
  SaveDialog1.FileName   := ExtractFileName(FSaveExeName);
  if not SaveDialog1.Execute then exit;
  if Trim(Memo1.Lines.Text) = '' then exit;


  LExtFullPath := SaveDialog1.FileName;
  //マニフェストをリソースから削除
  if SetManifestToExe(LExtFullPath, '', True) then begin
    MessageBox(Handle, '削除に成功しました', '情報', MB_ICONINFORMATION);
  end else begin
    MessageBox(Handle, '削除に失敗しました', '情報', MB_ICONWARNING);
  end;
end;


下のコードが実際に EXE 内にアプリケーションマニフェストをリソースとして埋め込む関数です.引数の値でリソースとしての埋め込みか削除かを指定します.
このコードは EXE 内の他のリソースの操作にも応用が可能です.そのためには,リソースの ID とリソースのタイプの指定が必要です.また,そのリソースの型にも注意が必要です.


リスト3
EXE にアプリケーションマニフェストを埋め込む関数
これも最初のサンプルに追加したコード
//-----------------------------------------------------------------------------
//  引数で指定するテキストをEXEのマニフェストリソースとして埋め込む
//  マニフェストとしての正当性は確認していないので注意
//
//  ExeFileName  : マニフェストを埋め込む対象のEXEのフルパス
//  ManifestText : マニフェストXMLコード
//  IsDelete     : マニフェストのリソースをEXEから削除する場合はTrueにする
//-----------------------------------------------------------------------------
function TForm1.SetManifestToExe(ExeFileName, ManifestText: String;
  IsDelete: Boolean=False): Boolean;
var
  LID_Lang : Cardinal;
  LhUpdate : THandle;
  LXMLText : AnsiString;
  LlpData  : Pointer;
begin
  Result := False;

  //リソースの言語識別子
  //以下の値は1041になる
  LID_Lang := MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT);
  //リソースに埋め込むテキスト
  LXMLText := AnsiString(ManifestText);
  //データのポインタ
  if IsDelete then begin
    LlpData := nil;
  end else begin
    LlpData := Pointer(LXMLText);
  end;

  //リソースの更新を開始
  LhUpdate := BeginUpdateResource(PChar(ExeFileName), False);
  if LhUpdate <> 0 then begin
    //リソースを更新(マニフェスト埋め込み)
    if UpdateResource(LhUpdate,
                      RT_MANIFEST,
                      MakeIntResource(1),
                      LID_Lang,
                      LlpData,
                      Length(ManifestText)) then begin
      //リソースの更新を終了
      if EndUpdateResource(LhUpdate, False) then begin
        Result := True;
      end;
    end;
  end;
end;




04_アプリケーションマニフェストコードの自動編集  -  XML の操作例


これまでのサンプルでは,マニフェストのコードを手動で編集するのが前提でした.
本サンプルは,これを自動で変更あるいは設定するサンプルです.

マニフェストのコードは XML 言語で書かれています.したがって DOM (Document Object Model) としての操作が可能です.本サンプルは,Delphi に実装されている MSXML ユニット内のインターフェイスを利用しています.
まず XPath で指定ノードの要素名あるいは属性の存在を確認し,その属性の値を変更します.ただし,requestedExecutionLevel と compatibility 関係については DOM としての操作ではなく,文字列の操作で設定しています.
文字列の操作も,正規表現を使用した方がいいのですが,本サンプルでは使用していません.簡略法ということになります.今回扱ったマニフェストのコードには重複した属性名がありますが,属性値を含めた文字列は重複する可能性はないと思われます.

[表示更新] ボタンをクリックすると,RadioGroup1 と CheckBox1 の設定を表示に反映します.その結果をマニフェストファイルとして保存,あるいは,EXE にリソースとして埋め込みます.



図11
設計時画面

リスト4
マニフェストのコードを自動編集する例
前項のサンプルに追加したコード
//=============================================================================
//  マニフェストのコードを自動編集
//
//  (1) assemblyIdentityのnamecの値を DelphiApplication にする
//  (2) assemblyIdentityのversionの値を 1.0.0.0 にする
//  (3) requestedExecutionLevel/levelの値を任意の値に変更する
//  (4) compatibility/applicationノードを追加
//
//  このコードの実行にはusesにMSXMLが必要
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  LXMLDomDoc : IXMLDOMDocument;
  LDomNode   : IXMLDomNode;
  LXPathText : WideString;
  LList      : TStringList;
  LBeforeVal : String;
  LAfterVal  : String;
  i          : Integer;
  k          : Integer;
begin
  if Memo1.Lines.Count = 0 then exit;
  Memo1.Lines.BeginUpdate;

  //IXMLDOMDocumentのインスタンスを生成
  LXMLDomDoc := CreateComObject(CLASS_DOMDocument) as IXMLDOMDocument;
  LXMLDomDoc.async := False;
  LXMLDomDoc.preserveWhiteSpace := True;

  //XMLコードを読み込む
  LXMLDomDoc.loadXML(Memo1.Text);

  //assembly/assemblyIdentityのnameの値の確認と変更
  LXPathText := '/assembly/assemblyIdentity/@name';
  LDomNode := LXMLDomDoc.selectSingleNode(LXPathText);
  if LDomNode <> nil then begin
    LBeforeVal := LDomNode.xml;
    LDomNode.nodeValue := 'DelphiApplication';
    LAfterVal  := LDomNode.xml;
    Memo1.Text := StringReplace(Memo1.Text, LBeforeVal, LAfterVal, [rfIgnoreCase]);
  end;

  //assembly/assemblyIdentityのversionの値の確認と変更
  LXPathText := '/assembly/assemblyIdentity/@version';
  LDomNode := LXMLDomDoc.selectSingleNode(LXPathText);
  if LDomNode <> nil then begin
    LBeforeVal := LDomNode.xml;
    LDomNode.nodeValue := '1.0.0.0';
    LAfterVal  := LDomNode.xml;
    Memo1.Text := StringReplace(Memo1.Text, LBeforeVal, LAfterVal, [rfIgnoreCase]);
  end;

  //requestedExecutionLevelのlevelの値の確認と変更
  //なければ追加
  LXPathText := '/assembly/trustInfo/security'
              + '/requestedPrivileges/requestedExecutionLevel';
  LDomNode := LXMLDomDoc.selectSingleNode(LXPathText);
  if LDomNode <> nil then begin
    LXPathText := LXPathText + '/@level';

    LDomNode := LXMLDomDoc.selectSingleNode(LXPathText);
    if LDomNode <> nil then begin
      LBeforeVal := LDomNode.xml;
      LDomNode.nodeValue := Trim(RadioGroup1.Items[RadioGroup1.ItemIndex]);
      LAfterVal  := LDomNode.xml;
      Memo1.Text := StringReplace(Memo1.Text, LBeforeVal, LAfterVal, [rfIgnoreCase]);
    end;
  end else begin
    LList := TStringList.Create;
    try
      LList.Add('<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">');
      LList.Add('  <security>');
      LList.Add('    <requestedPrivileges>');
      LList.Add('      <requestedExecutionLevel');
      LList.Add('        level="' +
                         Trim(RadioGroup1.Items[RadioGroup1.ItemIndex]) + '"');
      LList.Add('        uiAccess="false"/>');
      LList.Add('      </requestedPrivileges>');
      LList.Add('  </security>');
      LList.Add('</trustInfo>');

      for i := 0 to Memo1.Lines.Count - 1 do begin
        if Trim(Memo1.Lines[i]) = '</dependency>' then begin
          for K := 0 to LList.Count - 1 do begin
            Memo1.Lines.Insert(K + i + 1, '  ' + LList[k]);
          end;
          break;
        end;
      end;
    finally
      FreeAndNil(LList);
    end;
  end;

  //compatibility/applicationノードの確認
  LXPathText := '/assembly/compatibility/application';
  LDomNode := LXMLDomDoc.selectSingleNode(LXPathText);

  //compatibilityがある場合は一旦全ての項目削除する
  //どのOSの定義があるかは調べないで全て新規追加にするため
  if LDomNode <> nil then begin
    for i := Memo1.Lines.Count - 1 downto 0 do begin
      if Trim(Memo1.Lines[i]) = '</compatibility>' then begin
        for k := i downto 0 do begin
          if ContainsText(Memo1.Lines[k], '<compatibility xmlns="') then begin
            Memo1.Lines.Delete(k);
            Break;
          end else begin
            Memo1.Lines.Delete(k);
            Continue;
          end;
        end;
        Break;
      end;
    end;
  end;
  //compatibilityのノード部分を追加
  if CheckBox1.Checked then begin
    LList := TStringList.Create;
    try
      if CheckBox1.Checked then begin
        LList.Add('<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">');
        LList.Add('  <application>');
        LList.Add('    <!--Windows 8.1-->');
        LList.Add('    <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />');
        LList.Add('    <!--Windows 8-->');
        LList.Add('    <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />');
        LList.Add('    <!--Windows 7-->');
        LList.Add('    <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />');
        LList.Add('    <!--Windows Vista-->');
        LList.Add('    <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />');
        LList.Add('  </application>');
        LList.Add('</compatibility>');

        for i := 0 to Memo1.Lines.Count - 1 do begin
          if Trim(Memo1.Lines[i]) = '</assembly>' then begin
            for K := 0 to LList.Count - 1 do begin
              Memo1.Lines.Insert(K + i, '  ' + LList[k]);
            end;
            break;
          end;
        end;
      end;
    finally
      FreeAndNil(LList);
    end;
  end;
  Memo1.Lines.EndUpdate;

  LXMLDomDoc := nil;
end;


上のコードでは TMemo のテキストを直接書き換えていますが,DOM 操作の結果はテキストとして,以下のように IXMLDOMDocument.xml プロパティの値で取得できます.


リスト5
DOM の操作だけで XML のコードを書き換えて,結果の XML のコードを取得
//=============================================================================
//  このコードの実行にはusesにMSXMLが必要
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  LXMLDomDoc : IXMLDOMDocument;
  LDomNode   : IXMLDomNode;
  LXPathText : WideString;
begin
  if Memo1.Lines.Count = 0 then exit;

  //IXMLDOMDocumentのインスタンスを生成
  LXMLDomDoc := CreateComObject(CLASS_DOMDocument) as IXMLDOMDocument;
  LXMLDomDoc.async := False;
  LXMLDomDoc.preserveWhiteSpace := True;

  //XMLコードを読み込む
  LXMLDomDoc.loadXML(Memo1.Text);

  //assembly/assemblyIdentityのnameの値を変更
  LXPathText := '/assembly/assemblyIdentity/@name';
  LDomNode := LXMLDomDoc.selectSingleNode(LXPathText);
  if LDomNode <> nil then begin
    LDomNode.nodeValue := 'Delphi Application';
  end;

  //編集結果のXMLのコードを表示
  Memo1.Text := LXMLDomDoc.xml;

  LXMLDomDoc := nil;
end;


上のコードの実行結果が下の図です.最初の図が元のマニフェストのコードです.その下が name 属性の値を変更した結果です.赤枠で示しているように,name 属性の値が CodeGear RAD Studio から Delphi Application に変わっていることが確認できます.

属性の値を変更した結果の XML では,同じ要素は 1 行となっています.こちらの方が XML のコードを表示する際の一般的な表示形式です.
XML のコードが複数行で書かれていようと,全てが 1 行であろうと,XML のデータを利用する際には全く関係ありません.XML のパース (解析) はノード対する操作によって行われます.本サンプルのように,テキストの行位置で操作することはほとんどありません.

アプリケーションマニフェストのコードを表示する時に,属性名ごとに改行して表示するようにしているのは,視覚的な効果の意味ではないかと思われます.本項のサンプルで上の XML (DOM) の操作を使用しなかったのも,コードを見る時の視覚的な効果を重視したからです.



[備考]
アプリケーションマニフェストの XML コードは,ノードまたは属性要素の記載順番が意味を持つ場合があります.詳しくは MSDN 等の記事で確認してください.
一般的には,他のデータベースと同じように,ノードの親子関係が保たれていれば XML コード内のノードと要素の記載順番は関係ありません.一般的なデータベースと同じで,XML コード内の行番号には意味がありません.ただし,中にはノードあるいはノード内の要素をシーケンシャルに扱っているアプリケーションもあるようです.



図12
XML コード




05_自アプリの EXE へ埋め込むリソースの操作


自アプリの EXE にアイコン,ビットマップ等の画像,テキスト,あるいは文書類をリソースとして埋め込むにはプロジェクトの設計時にリソースマネージャーで設定します.リソースマネージャーは,メニューの [プロジェクト] [リソースと画像] (または [リソース]) で表示できます.
プロジェクトを保存してコンパイルすると,拡張子が .dres と .rc のファイルができます..dres はコンパイルしたリソースです.このリソースが EXE に埋め込まれます.リソースの使用宣言は,プロジェクトのソースコードに自動で挿入されます.
これらのファイルは,EXE 実行時には必要ありません.

リソースを実行時に取り出すには,LoadFromResourceName メソッド,あるいは TResourceStream 等を使用します.PDF 文書,ワードの文書やエクセルのブック等は,ディスクに保存してから表示することになります.例えば TPath.GetTempPath で Windows の一時ディレクトリを取得して,そこに保存してから ShellExecute 関数等で表示することも考えられます.

Delphi 2009 より前の Delphi には,リソースマネージャーは実装されていません.Halbow 資料館の記事を参考にしてください.



[備考 1]
筆者は過去に Vector でアプリを提供していたことがあります.その際,そのアプリ専用の DLL もリソースとして埋め込んでいました.そうすると,たとえ DLL が削除されたとしても,EXE さえあればアプリを動作させることができます.
ワード等の文書もリソースとして埋め込んでおけば,EXE 起動時に,常にオリジナルの文書として利用できることになります.


[備考 2]
リソースエディタで登録した画像等は,プロジェクトに追加されます.これはプロジェクトマネージャで確認できます.一方,.rc ファイルを自分で作成して .rc ファイルをプロジェクトに追加する方法では .rc ファイルがプロジェクトに追加されます.



図13
リソースマネージャー
  • リソースにするファイル等を [追加] ボタンで追加す
  • [リソース識別子], [リソースタイプ] 共にデフォルト値が設定される
  • この図では [リソース識別子] の名前を変えている
図14
2 つのファイルが生成される

実際のテストプログラムの実行結果とコードです.
設計時に,ビットマップと JPEG 画像をリソースとして設定しています.



図15
設計時画面
図16
EXE 実行直後.ビットマップ画像を表示
図17
[Button1] をクリック.JPEG 画像を表示

リスト6
自アプリの EXE へ埋め込むリソースの操作例
ビットマップと JPEG 画像をリソースとして埋め込み,実行時に読み出して TImage に表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Jpeg;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LBitmap: TBitmap;
begin
  Image1.Stretch := True;

  //アプリ起動時にリソースのビットマップ画像を読み出して表示
  //ビットマップの場合はLoadFromResourceNameを使用する
  LBitmap := TBitmap.Create;
  try
    LBitmap.LoadFromResourceName(HInstance, 'MyImage_1');
    Image1.Picture.Assign(LBitmap);
  finally
    FreeAndNil(LBitmap);
  end;
end;

//=============================================================================
//  JPEG画像をリソースから読み出して表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LResource : TResourceStream;
  LJPegImg  : TJPEGImage;
begin
  Image1.Stretch := True;

  //JPEG画像を読み出して表示
  LResource := TResourceStream.Create(HInstance, 'MyImage_2', RT_RCDATA);
  LJPegImg  := TJpegImage.Create;
  try
    LJPegImg.LoadFromStream(LResource);
    Image1.Picture.Assign(LJPegImg);
  finally
    FreeAndNil(LJPegImg);
    FreeAndNil(LResource);
  end;
end;

end.


TBitmap にはリソースからの読み出し用の関数,LoadFromResourceName がありますが,TJpegImage にはありません.LoadFromStream を使用します.
ビットマップのリソース画像も,リソースマネージャーで,リソースタイプを RCDATA にした場合は,以下のように,LoadFromStream で取り出すことになります.
リソースエディタ,あるいは EXE を展開してリソース関係を調べて見ると分かりますが,リソースのタイプが BITMAP と RCDATA とではリソースの格納場所と格納方法が違います.この違いよって取得方法が異なることになります.



//=============================================================================
//  リソースマネージャーでビットマップのリソースタイプをRCDATAとした場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LResource : TResourceStream;
  LBitmap   : TBitmap;
begin
  Image1.Stretch := True;

  LResource := TResourceStream.Create(HInstance, 'MyImage_1', RT_RCDATA);
  LBitmap   := TBitmap.Create;
  try
    LBitmap.LoadFromStream(LResource);
    Image1.Picture.Assign(LBitmap);
  finally
    FreeAndNil(LBitmap);
    FreeAndNil(LResource);
  end;
end;




06_EXE に埋め込んだマウスカーソルのリソースの使用方法


マウスカーソルは,リソースとして EXE に組み込む時に,リソースタイプを CURSOR にします.リソース識別子の名前は任意です.



図18
リマウスカーソルをソースマネージャーで設定する

リソースからマウスカーソルを取り出すには LoadCursor 関数を使用します.この関数はマウスカーソルのハンドルを返します.型は HCURSOR です.
ハンドルの値は TScreen.Cursors プロパティに代入しますが,あらかじめ使用するマウスカーソルに任意の定数名を割り当てておきます.後は通常のマウスカーソルと使い方は同じです.設定したマウスカーソルの定数名を使用します.
本サンプルでは,Panel1 と Panel2 ののマウスカーソルを個別に変更します.



図19
Panel1 上にマウスカーソルがある場合
図20
Panel2 上にマウスカーソルがある場合

リスト7
自アプリの EXE へ埋め込むリソースの操作例.マウスカーソルの場合
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const
  //マウスカーソルに名前を付けて重複しない番号を割り当てておく
  FcrLivejournal = 1;
  FcrBaintcan    = 2;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //リソースからマウスカーソルを読み込んで登録しておく
  Screen.Cursors[FcrLivejournal] := LoadCursor(HInstance, 'Cursor_1');
  Screen.Cursors[FcrBaintcan]    := LoadCursor(HInstance, 'Cursor_2');

  //実際に施用するマウスカーソルを設定する
  Panel1.Cursor := FcrLivejournal;
  Panel2.Cursor := FcrBaintcan;
end;

end.