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にバージョン情報が入らない
最終更新: 6978 日前
0302 D1D2 D3 D4 D5 D6 D7 3.195 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
Osamu Takeuchi osamu@big.or.jp
Tips
Delphi
Home