Delphi Tips 
-----------------------------

キーワード:アプリケーション

>> Index

10/15 EXE ファイルのスリム化
02/08 リソースにあるバージョン情報を取得したい。
10/02 二重起動防止措置で既存プロセスを最前面に送る
06/25 Ini ファイルに published プロパティを保存する方法
05/17 Photoshop のようなツールウィンドウを実現したい
05/17 OnShow イベント中で SetFocus すると不具合
04/04 国際化アプリケーションの作り方
09/27 アップリケーションにサウンドリソースを埋め込んで使いたい。
09/20 MDI等で二重起動を防止して新しいファイルを開く
08/26 他のアプリの起動パスを取得する
06/08 LhasaのようなUIを持つアプリケーションの作成方法
02/11 IniFile に書き込みを行った後にはバッファのクリアが必要
02/11 アプリケーションが最小化されているかどうかを判定する
02/08 NT のタスクマネージャにアプリケーションのアイコンが表示されない
02/08 TIniFileにクオートを含む文字列を与えるときの注意点
02/08 Delphi3.0でDLLにバージョン情報が入らない

最終更新: 4420 日前

0302  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 2000/06/01 osamu rev 1.4
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2005/10/15 nameless <> 編集
EXE ファイルのスリム化

.exe ファイルを圧縮しておき、実行時に自動的に解凍することで、.exe ファイルを小型化することの出来るソフトがいくつか出回っています。フリーソフトでは ezip, upx、商用ソフトでは aspack というものが有名なようです。

ezip>
        http://web.archive.org/*/http://www.jonathanclark.com/ezip/ezip.exe

upx>
    http://upx.sourceforge.net/

aspack>
    http://www.entechtaiwan.com/aspack.htm

圧縮ではなく、W32 PE EXE ファイルのの 'reloc' セクションを削除してしまう方法もあります。reloc が無くても EXE はメモリー空間に Windows のローダがチャンと割り付けてくれます。(DLL は削除してはいけません)

StripReloc>
    http://www.jrsoftware.org/striprlc.htm
参照: <開発環境> <配布>

0226  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/08/29 西坂良幸 rev 1.5
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2005/02/08 <> 編集
リソースにあるバージョン情報を取得したい。

Delphiのメニュー[プロジェクト|オプション]のバージョン情報ページのデータはリソースとして製作したアプリケーションの EXE ファイルの中にあります。バージョン情報ダイアログのバージョン番号は、是非このリソースの内容を反映させたいですね。

この方法は、多くの書物に書かれているようですが、ML でも多い話題です。
自作のダイアログをつくり、リポジトリに登録しておきましょう。
コンポーネント化するのもひとつの方法です。
  
VerQueryValue 関数の第二パラメータに、 '\' をつかって バージョン番号を取得する方法は [Delphi-ML:31478] で紹介しています。
 
ここでは、すべてのバージョン情報を取得する関数を紹介します。

type
  TVerResourceKey = (
        vrComments,         // コメント
        vrCompanyName,      // 会社名
        vrFileDescription,  // 説明
        vrFileVersion,      // ファイルバージョン
        vrInternalName,     // 内部名
        vrLegalCopyright,   // 著作権
        vrLegalTrademarks,  // 商標
        vrOriginalFilename, // 正式ファイル名
        vrPrivateBuild,     // プライベートビルド情報
        vrProductName,      // 製品名
        vrProductVersion,   // 製品バージョン
        vrSpecialBuild);     // スペシャルビルド情報

const
  KeyWordStr: array [TVerResourceKey] of String = (
        'Comments',
        'CompanyName',
        'FileDescription',
        'FileVersion',
        'InternalName',
        'LegalCopyright',
        'LegalTrademarks',
        'OriginalFilename',
        'PrivateBuild',
        'ProductName',
        'ProductVersion',
        'SpecialBuild');

// バージョン情報を取得
function GetVersionInfo(KeyWord: TVerResourceKey): string;
const
  Translation = '\VarFileInfo\Translation';
  FileInfo = '\StringFileInfo\%0.4s%0.4s\';
var
  BufSize, HWnd: DWORD;
  VerInfoBuf: Pointer;
  VerData: Pointer;
  VerDataLen: Longword;
  PathLocale: String;
begin
  // 必要なバッファのサイズを取得
  BufSize := GetFileVersionInfoSize(PChar(Application.ExeName), HWnd);
  if BufSize <> 0 then
  begin
    // メモリを確保
    GetMem(VerInfoBuf, BufSize);
    try
      GetFileVersionInfo(PChar(Application.ExeName), 0, BufSize, VerInfoBuf);
      // 変数情報ブロック内の変換テーブルを指定
      VerQueryValue(VerInfoBuf, PChar(Translation), VerData, VerDataLen);
      if not (VerDataLen > 0) then
        raise Exception.Create('情報の取得に失敗しました');
      // 8桁の16進数に変換
      // →'\StringFileInfo\027382\FileDescription'
      PathLocale := Format(FileInfo + KeyWordStr[KeyWord],
        [IntToHex(Integer(VerData^) and $FFFF, 4),
         IntToHex((Integer(VerData^) shr 16) and $FFFF, 4)]);
      VerQueryValue(VerInfoBuf, PChar(PathLocale), VerData, VerDataLen);
      if VerDataLen > 0 then
      begin
        // VerDataはゼロで終わる文字列ではないことに注意
        result := '';
        SetLength(result, VerDataLen);
        StrLCopy(PChar(result), VerData, VerDataLen);
      end;
    finally
      // 解放
      FreeMem(VerInfoBuf);
    end;
  end;
end;

// テスト
procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption := GetVersionInfo(vrFileVersion);
  Label2.Caption := GetVersionInfo(vrLegalCopyright);
end;


参照: [Delphi-ML:31478] [Delphi-ML:37794] <開発環境> <ダイアログ>

0349  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 2003/10/02 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2003/10/02 osamu 編集
二重起動防止措置で既存プロセスを最前面に送る

以下のコードでうまく動くそうです。
定数宣言部分を必要に応じて書き換えて使います。

program Project1;

uses
  Windows,
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

const
  MutexName = 'TEST_MUTEX';
  ClassName = 'TForm1';
  WindowName = 'Form1';
var
  Mutex: THandle;
  Handle: THandle;
begin
  Mutex := OpenMutex(MUTEX_ALL_ACCESS, False, MutexName);
  if Mutex <> 0 then
  begin
    Handle := FindWindow(ClassName, WindowName);
    SetForegroundWindow(Handle);
    Exit;
  end;
  CreateMutex(nil, False, MutexName);
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
参照: [Tips:269] [Delphi-ML:78270]

0338  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 2003/06/25 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2003/06/25 osamu 編集
Ini ファイルに published プロパティを保存する方法

published プロパティを Ini ファイルなどに保存するためにテキストに/テキストから変換する方法を [Delphi-ML:75958] に載せました。

例えば、Font.Style プロパティを [fsBold] などとして保存することができます。
参照: [Delphi-ML:75958] <PASCAL>

0327  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 2002/05/17 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2002/05/17 osamu 編集
Photoshop のようなツールウィンドウを実現したい

1.細いタイトルバーでクローズボタンのみが付いている
2.アプリケーションがアクティブであればツールウィンドウのタイトルバーもアクティブ色
3.常にアプリケーションのフォームよりも手前にある

というようなツールウィンドウを実現しようと思うと、フォーカス関連のイベントが貧弱な Delphi ではなかなか難しいようです。

[Delphi-ML:66799][Delphi-ML:66801] で書かれているように、アプリケーションに飛んでくるすべてのメッセージの中からフォーカスの移動に関係するものをチェックしてやることで実現が可能です。実際のツールウィンドウはコード中の TToolWindow を継承して作成することになります。
参照: [Delphi-ML:66799] <フォーム>

0318  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 2002/05/17 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2002/05/17 osamu 編集
OnShow イベント中で SetFocus すると不具合

フォームにMemoを貼り付けて実験してみたのですが

procedure TForm1.FormShow(Sender: TObject);
begin
  Memo1.SetFocus;
end;

とすると「ソフトを起動したとき、それがアクティブウィンドウになるにもかかわらず、そのソフトを示すタスクバーのボタンが押されない状態で表示される」という症状が出るようです。

Memo1 の TabOrder をゼロにするなど、別の方法でフォーカスをコントロールする必要があるようです。
参照: [Delphi-ML:67321] <フォーム>

0315  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 2002/04/01 osamu rev 1.2
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 2002/04/04 osamu 編集
国際化アプリケーションの作り方

日本語フォントを使わない、など基本的なこと以外にも、ダイアログ関連を修正する必要があります。

詳細は過去ログにいくつか存在します。

[Delphi-ML:44083], [Delphi-ML:53963]

「英語版」「国際化」などで検索すると、他にも見つかるかも知れません。

また、Microsoft のサイトに国際化対応ソフトウェア開発のためのページがあります。そこの情報も一読されることをお薦めします。

Top ページ
http://www.microsoft.com/globaldev/

Guidelines
http://www.microsoft.com/globaldev/guides/guides.asp


その他の注意点としては、
・小数点は "." 、リストの区切りは ","、日付は"年月日の順"など
  とは仮定してはいけない。
・そのため 数値←→数値文字列、日付←→日付文字列の変換はロケー
 ルを適切に使用する Delphi の関数/手続きを使用する。
・でも HTML や XML などの属性値における小数点は必ず "."。
あたりは常に意識しておいたほうが良いでしょう。


ちょっと古い本ですが国際化対応のアプリケーションを作ろうとする場合とても参考になる本として、

Developing International Software For Windows 95 and Windows NT
Microsoft Press 発行 Nadine Kano 著
http://www.microsoft.com/globaldev/dis_v1/disv1.asp

があります。オンラインで内容すべてを閲覧できます。
参照: [Delphi-ML:44083] [Delphi-ML:53963] [Delphi-ML:66183]

0276  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/09/27 西坂良幸 rev 1.2
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/09/27 西坂良幸 編集
アップリケーションにサウンドリソースを埋め込んで使いたい。

リソースファイルには、*.WAVファイルを埋め込むことが出来ます。しかし、これは手作業で行わなければなりません。

たとえば、TEST.WAV というファイルがあるとします。
・このファイルをproject1.dprと同じディレクトリに起きます。
・メニューから[ファイル|新規作成−テキスト]を選びます。
・このテキストに、

   MYSOUND WAVE TEST.WAV

と1行を書き込み、PROJTEST.rc(適当でよい)という*.rcファイルとしてセーブします。
  ※ TEST.WAVが同じディレクトリでないときはフルパスで指定
  ※ リソースは、原則、大文字です。
・binディレクトリにあるbrcc32.exeで、*.resファイルを作成します。

   brcc32.exe PROJTEST.rc

  ※ コマンドラインEXE であることに注意
・project1.dprを開き作成したリソースファイルを書き込みます

・・省略・・・
{$R *.RES}               // デフォルトである
{$R projtest.res}        // ←ここを書き加える

begin
  Application.Initialize;
・・省略・・・


以上で、プロジェクトの再構築を行えば、EXEの中にTEST.WAVのデータが埋め込まれます。
Demosディレクトリの中にあるリソースエクスプローラの見本をコンパイルして、このプロジェクトのEXEファイルを読み込むと、WAVEリソースが出来ていることがわかります。

以下は、簡単な使用方法です。

// 音を鳴らす
procedure TForm1.Button1Click(Sender: TObject);
begin
  PlaySound('MYSOUND',HInstance, SND_RESOURCE or SND_ASYNC);
end;

// 停止させる
procedure TForm1.Button2Click(Sender: TObject);
begin
  PlaySound(nil, 0, SND_RESOURCE);
end;


上記の場合、SND_RESOURCE は必ず必要です。
また、SND_ASYNCをSND_SYNCにすると終わるまで制御が戻りません。
参照: [Delphi-ML:8787] [Delphi-ML:12338] <その他Windows関連> <開発環境> <Windows>

0267  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/09/19 おばQ rev 1.1.1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/09/20 K.Takaoka 編集
MDI等で二重起動を防止して新しいファイルを開く


なんか変なタイトルですいません。
Exe本体やExeのショートカットにファイルをドラッグ&ドロップ(以下D&D)した時にアプリケーションが起動してファイルが開く処理を実現したとします。
アプリケーションが起動している最中にもう一度、Exe本体やショートカットにファイルを D&D するともう一つアプリケーションが起動しませんか?あまり素敵じゃないですよね?

貴方がお望みの動作は、たぶんアプリケーションは一つだけ立ち上がっていて MDI 子ウィンドウで新しく D&D されたファイルを開きたいというものでしょう?そこで以下のTipsを利用します。

・二重起動の判定(t269)
・簡易アプリケーション間通信(t268)

t269 のままでは Halt 手続きによってアプリケーションが終了してしまうので、引数に渡されたファイル名を t268 を利用して送信します.

halt 手続きの変わりに呼び出される CopyDataToOld 手続きを作成します.


procedure CopyDataToOld;
var
  wnd: HWND;
begin
  { 既に存在している TForm1 を探す }
  Wnd := FindWindow('TForm1', nil);  
  if Wnd<>0 then
  begin
    // SIGNATURE_FILEOPEN 定数で ParamStr の内容を送信するとする
    // SendMessage(wnd, WM_COPYDATA, ...); // t268 参照
  end;
end;

そして、送信された WM_COPYDATA を受け取るメッセージハンドラを実装します。

インターフェス部

type
  TForm1 = class(TForm)
  // …省略…
  private
    procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
  public
  end;

実装部

procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  ArriveStr: String;
begin
  if Msg.CopyDataStruct.dwData=SIGNATURE_FILEOPEN then
  begin
    // 受信処理 : t268 参照
    // ArriveStr に受け取った文字列が入るとする

    // ※ 受け取ったファイル名で開く
    FileOpen(String(pcData));  //例です
  end;
end;

以上のソースでは※印部分では D&D されるファイルは一つだとしか考慮していません。
参照: [Delphi-ML:42587] [Tips:268] [Tips:269] <その他Windows関連> <Windows>

0182  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/05/08 osamu rev 1.2
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/08/26 西坂良幸 編集
他のアプリの起動パスを取得する


> (ウィンドウハンドルの場合)
> GetWindowLong(hwnd, GWL_HINSTANCE) でモジュールハンドルを取り出す。
> (モジュールハンドルの場合)
> GetModuleFileName(hModule, strBuffer, nBufferSize) でファイル名を得る。

 残念ですがこの方法では、他のアプリの起動パス は取得できません。自分自身のExe名しか取得できません。(私も最初はこれではまった。)(^.^)

 モジュールハンドルはプロセス毎に独立して管理されるので、他のアプリ(他のプロセス)のモジュールハンドルを持ってきても意味がありません。

http://www.microsoft.com/japan/support/kb/articles/J041/6/32.htm

にシステム内で稼働中のプロセスを列挙する方法がでてますから、参考にしてください。ウィンドウハンドルが判っているのでしたら、
GetWindowthreadProcessID でプロセスIDを求めて、列挙中にこれと合致するプロセスを探せばよいでしょう。

たとえば、Win95/98用(WinNTではダメ)ですが、


uses TLHelp32;

// ハンドルからファイル名を得る
function GetProcesFileNameFrom(Handle: hWnd):string;
var
  PID: DWORD;
  SnapShot: THandle;
  ProcessEntry32: TProcessEntry32;
begin
  // ハンドルから作成スレッドを調べてプロセスIDを得る
  GetWindowThreadProcessId(Handle, @PID);
  // TProcessEntry32構造体の初期化
  ProcessEntry32.dwSize := SizeOf(TProcessEntry32);
  // システム中の情報のスナップショットをとる
  SnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  try
    // 最初のプロセスの検索
    if Process32First(SnapShot, ProcessEntry32) then
    begin
      repeat
        // IDが一致したら
        if ProcessEntry32.th32ProcessID = PID then
        begin
          Result := string(ProcessEntry32.szExeFile);
          break;
        end;
      // 次のプロセスの検索
      until Process32Next(SnapShot, ProcessEntry32) = False;
    end;
  finally
    CloseHandle(SnapShot);
  end;
end;

などです。
参照: [Delphi-ML:30032] <Windows> <ファイル>

0193  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/06/08 おばQ rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/06/08 おばQ 編集
LhasaのようなUIを持つアプリケーションの作成方法

Lhasaという有名な圧縮解凍ソフトをご存知だと思います。
そのUIはファイルをexeにDrag&Dropした時は解凍を行い、
普通に起動の場合、Formが開きます。

つまり最小化起動して処理をして終了するアプリケーションです。
その基本的なUIを実現します。

普通にFormを作り以下のコードを書きます。

procedure TForm1.FormShow(Sender: TObject);
var
  i:Integer;
begin
  if ParamCount=0 then
//Drag&Dropされていない場合はParamCount=0
  begin
    Application.Title := 'サンプルプログラム';
//普通に起動します。
  end else
  begin
    Application.Title := '処理中';
    Application.Minimize;//最小化起動

    for i:=1 to ParamCount do
    begin
    {
    ここでDrag&Dropされたファイルに対しての処理を行う
    ParamStr(i)でファイル名がパス名付きで取得できるので
    読込み、処理を行い、書きこむと良いです。
    }
    end;
    Application.Terminate;  
//最小化起動したアプリを終了させる
  end;
end;

参照:

0162  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/11 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/11 osamu 編集
IniFile に書き込みを行った後にはバッファのクリアが必要

Delphi4 になって追加されたメソッドに、TIniFile.UpdateFile というのがあります。

TIniFile.UpdateFile メソッドは,バッファリングされた INI ファイルのデータをディスクにフラッシュします。

function UpdateFile; override;

説明

UpdateFile メソッドを呼び出すと,バッファリングされた,INI ファイルからの読み取りや INI ファイルへの書き込みをディスクにフラッシュできます。UpdateFile は,Windows95 では便利ですが,Windows NT では INI ファイルの読み取りや書き込みをバッファリングしないので、効果がありません。

Delphi3 以前では自分で
    WritePrivateProfileString(nil, nil, nil, PChar(FileName));
などとして IniFile のキャッシュを書き込む必要があります。

>   Ini := TIniFile.Create(Filename);
>   with Ini do begin
>     WriteString(aaaa, bbbb, cccc);
>     WriteString(dddd, eeee, ffff);
>     Free;
>     //  以下の処理で、キャッシュをフラッシュ
>     WritePrivateProfileString(nil, nil, nil, PChar(Filename));
>   end;
参照: [Delphi-ML:31512] <Windows> <バグ>

0148  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/11 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/11 osamu 編集
アプリケーションが最小化されているかどうかを判定する

最小化された状態の判定:

  IsIconic(Application.Handle)

「元のサイズに戻す」

Application.Restore;

で良いと思います。
参照: [Delphi-ML:25293] <フォーム>

0103  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
NT のタスクマネージャにアプリケーションのアイコンが表示されない

Delphi で作られるソフトすべてでこの問題が発生します。ちなみに、Delphi 自身も同じ問題を抱えています。
アプリケーションのクラスアイコンをセットしたら表示されるようになると思います。
プロジェクトソースかメインフォームの OnCreate イベントで、

SetClassLong(Application.Handle,
             GCL_HICON,
             Application.Icon.Handle);

を実行して下さい。
参照: [Delphi-ML:21168] <その他Windows関連> <Windows> <バグ> <アイコン>

0034  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
TIniFileにクオートを含む文字列を与えるときの注意点

APIのGetPrivateProfileStringの仕様として、値文字列の両端がクォート( " or ' )で囲まれていると、これを外してから返すことになっています。
これは、次のような問題を引き起こしかねません。

[Iniファイルの内容]
SomeItem="a bd","de fg","h ijk"

これをTIniFileを使って読み出すと、結果は、

Result='a bd","de fg","h ijk';

となって、両端のクォートだけが取り払われ、おかしなことになってしまいます。
これは、特に、TStringList.CommaTextをTInifileを使って読み書きするときに注意すべき点です。

これを防ぐには、クォートで括られた文字列群をさらに一段、クォートで囲ってしまいます。

[Iniファイルの内容]
SomeItem=""a bd","de fg","h ijk""

これをTIniFileを使って読み出すと、結果は、

Result='"a bd","de fg","h ijk"';

となって、OKです。

コード的には、

    IniFile.WriteString('TEST', 'ZZZ', TmpLst.CommaText);

    IniFile.WriteString('TEST', 'ZZZ', '"'+TmpLst.CommaText+'"');

とします。

ちなみに、TRegIniFileを使う場合には、このようなことは起こりません。
参照: [Delphi-ML:8412] [Delphi-ML:8417] <バグ>

0007  D1   D2   D3   D4   D5   D6   D7   3.1   95   98    作成: 1999/02/08 osamu rev 1.1
   B1   B3   B4   B5   B6   B7   NT3   NT4   2K   XP  更新: 1999/02/08 osamu 編集
Delphi3.0でDLLにバージョン情報が入らない

Delphi3.0で、「新規作成」から DLL を作ると、「バージョン情報を含める」のオプションを指定しても、効果が無い。
これは、.dpr ファイルに {$R*.RES} の一文が入らないために、リソースファイルはできるのに、リンクされないためだ。
uses節の後ろに手動で{$R*.RES} を書いてやればよい。
参照: [Delphi-ML:19012] <その他Windows関連> <開発環境> <Windows> <バグ> <DLL>

[新規作成] [最新の情報に更新]

How To
Lounge
KeyWords

Tips
Delphi
Home
Osamu Takeuchi osamu@big.or.jp

.