Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年04月23日] [更新 2016年05月11日] Delphi サンプルプログラム集
480_拡張子に関連付けされた実行ファイル名の取得
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
480_GetFullExePath.zip [1,851 KB] 2016年05月11日版 (EXE 同梱)



  • 2010年05月03日
  • Delphi 2010 用にコードを更新
  • EXCEL.EXE 等のファイル名からフルパスを取得するコードを削除
  • 2011年07月15日
  • 取得条件をチェックボックスで設定するようにした
  • 2014年06月07日
  • Delphi 7 以前用のコードを、Delphi に実装されていない Windows API の説明用に再掲
  • 2014年08月17日
  • 動作の引数の値を open からデフォルトの動作を意味する nil に変更
  • 2016年05月10日
  • ディレクトリ名なしの EXE 名から EXE のフルパスを取得するサンプルを再掲載




01_拡張子に関連付けされた実行ファイル名の取得


AssocQueryString 関数を使用して、拡張子に関連付けされた最初の実行ファイルのフルパス名を取得します。取得したパス名を選択してコピー可能なようにしています。
引数付きのフルパスも取得できます。StringReplace 関数等を使用して、この引数の %1 をファイル名等に置き換えると、CreateProcess 関数の引数にそのまま利用可能な文字列として使えます。

アプリケーションの実行ファイル名を取得するということは、そのプログラムのインストール先を取得することと同じです。



[備考]
Delphi 7 以前の Delphi には ShLwApi.pas は実装されていません。



図1
設計時画面

図2
実行例 1

  • " 付きで取得
図3
実行例 2

  • コマンドラインの引数も取得した場合

リスト1
拡張子に関連付けされたフルパスの実行ファイル名の取得
unit GetFullExePathUnit;

interface

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

type
  TForm1 = class(TForm)
    Edit2: TEdit;
    Edit1: TEdit;
    Button1: TButton;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ComObj,
  ShLwApi;

{$R *.DFM}

//=============================================================================
//  テスト用の拡張子をEditにセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  CheckBox1.Checked := True;
  CheckBox2.Checked := False;
  Edit1.Text     := '.doc';

  Edit2.ReadOnly := True;
  Edit2.Text     := '';
end;

//=============================================================================
//  指定した拡張子に関連づけた実行ファイルのフルパス名を取得する関数
//  取得するのは拡張子の関連付けがレジストリに登録されているものの最初のもの
//
//  AssocQueryStringの使用にはusesにShLwApiが必要
//  OleCheckやWin32Checkを入れておくと例外を検出してくれる
//  OleCheckやWin32Checkの使用にはuuseにComObjが必要
//=============================================================================
function GetRelatedFullExePath(ExtName: String;
  DoubleQuote: Boolean=True; CmdParams: Boolean=False): String;
var
  LBuffer : array [0..MAX_PAtH-1] of Char;
  LdwOut  : DWORD;
begin
  Result := '';

  FillChar(LBuffer, SizeOf(LBuffer), #0);
  LdwOut := MAX_PATH;

  try
    //第2引数をASSOCSTR_EXECUTABLEにすれば"を含まないパス文字列となる
    if DoubleQuote then begin
      OleCheck(ShLwApi.AssocQueryString(ASSOCF_NOTRUNCATE,
                                        ASSOCSTR_COMMAND,
                                        PChar(ExtName),
                                        nil,
                                        LBuffer,
                                        @LdwOut));
    end else begin
      OleCheck(ShLwApi.AssocQueryString(ASSOCF_NOTRUNCATE,
                                        ASSOCSTR_EXECUTABLE,
                                        PChar(ExtName),
                                        nil,
                                        LBuffer,
                                        @LdwOut));
    end;
    Result := LBuffer;

    if Length(Result) = 0 then begin
      Result := '';
    end else begin
      if not CmdParams then begin
        //"(ダブルコーテーション)で囲まれている場合
        if Pos('" ', Result) > 0 then begin
          Result := Copy(Result, 1, Pos('" ', Result) + 1);
        end;
      end;
    end;
  except
  end;
end;

//=============================================================================
//  指定した拡張子に関連付け実行ファイルのフルパス名を取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit2.Text := GetRelatedFullExePath(Edit1.Text,
                                      CheckBox1.Checked,
                                      CheckBox2.Checked);
end;

end.




02_未定義の識別子 : 'AssocQueryString' のエラーの場合


このエラーが発生した場合、その Delphi には AssocQueryString 関数が実装されていません。
この関数は、Windows の Shlwapi.dll が提供している関数です。開発者がこの関数の定義を実装しないと利用できません。以下の記事は、この関数の実装方法です。
本項のサンプルは、Delphi 7 以前用ですが、Windows API (DLL 内の関数) の使用方法に関する一般的な内容となっています。





02-01_使用する Windows API の関数名を決める


この関数は、MSDN オンラインの記事では次のようになっています。

リスト2
MSDN オンラインの記事での AssocQueryString 関数の定義
C++

  HRESULT AssocQueryString(
  _In_       ASSOCF flags,
  _In_       ASSOCSTR str,
  _In_       LPCTSTR pszAssoc,
  _In_opt_   LPCTSTR pszExtra,
  _Out_opt_  LPTSTR pszOut,
  _Inout_    DWORD *pcchOut
);


HRESULT は、関数の戻り値の型です。_In_ は、その引数を関数に与えることを意味しています。_Out_ は関数実行後に値が返されてくることを意味しています。また、ASSOCF, ASSOCSTR は、この関数の場合は、列挙体の名前です。実際には定数値、あるいは定数名を指定します。LPCTSTR 等は変数の型で、その右側が変数名です。
AssocQueryString 関数を Delphi で使用可能なコードにするには、例えば以下のようします。

リスト3
MSDN オンラインのコードを Delphi のコードにしたコードの例
function AssocQueryStringA(
              flags    : Cardinal;
              str      : Cardinal;
              pszAssoc : PChar;
              pszExtra : PChar;
              pszOut   : PChar;
              pcchOut  : PDWORD): HRESULT; stdcall; external 'Shlwapi.dll'


上の Delphi 用のコードの関数名には、末尾に A があります。この A は、Ansi 版の関数であることを意味しています。MSDN オンラインの記事を読むと、次のような記述があります。


AssocQueryStringW (Unicode) and AssocQueryStringA (ANSI)


Unicode 用の関数名は末尾に W が、Ansi 用の関数名の末尾には A があります。Windows API 関数の多くのは、このように、Unicode 用と Ansi 用とが区別されています。今回、表題のエラーが発生するのは、Ansi 版の Delphi です。そこで AssocQueryStringA を使用します。
末尾に W も A もない AssocQueryString 関数は、実際には存在しません。

このことを確認することができます。
サンプルの [006] [05_DLL 内のエクスポート関数名を列挙] を使用して確認したのが、下図です。
DLL 内に AssocQueryString がないことが分かります。

前項のサンプルでは、末尾に W も A もない AssocQueryString という関数を使用しています。
Delphi で実装されている Windows API の関数類は、
  • Ansi 版の Delphi (Delphi 2007 以前) の時は、末尾が A の関数
  • Unicode 版の Delphi (Delphi 2009 以降) の時は、末尾が W の関数
を自動的に使用する仕組みになっています。したがって、Delphi が Unicode 版か、Ansi 版かを意識する必要はありません。意図的にどちらかを使用する場合は、A または W を付けます。


図4
shlwapi.dll 内のエクスポート関数類

  • AssocQueryStringA
    AssocQueryStringW
    という関数はあるが、
    AssocQueryString
    という関数はない

[備考]
ネット上に、末尾に A も W もない AssocQueryString 関数を定義して使用するサンプルコードがあるようですが、筆者が確認した限りでは、少なくても Windows 2000 以上にはありません。
そのネット上のサンプルの記事には、Windows のバージョンも Delphi のバージョンの記載もありません。したがって、筆者がこれ以上確認するには無理があります。




02-02_関数の引数の型を決める  -   Windwos SDK も活用


これで関数名は決まりました。次は引数です。
第 1 引数と第 2 引数は、MSDN の記事によると、定数値を指定することになっています。これらの定数名の一覧があります。これらの定数は、符号なしの 32 ビット値である Cardinal 型とします。32 ビット値なので DWORD も使用できます。多くの場合、Integer 型でも動作します。
MSDN オンラインの記事には、2014年06月07日現在、定数名はありますが、定数の実際の値が書いてありません。

そのような場合、[Windows SDK 8.1 を使用したタイプライブラリの作成] の記事にある Windows SDK をインストールします。
MSDN オンラインの記事の Header に Shlwapi.h と書いてあります。このヘッダファイルの中には、今回使用する関数関係のコードが書いてあります。そこで、Windows SDK の shlwapi.h 内を定数名で検索します。すると、そこに定数名とその値の一覧が記述されています。これを使用します。

3 番目、4 番目、5 番目は文字列ですが、C または C++ の変数はポインタ型です、そこで、文字列のポインタ型である PChar を使用します。

最後の引数は DWORD となっています。これも DWORD のポインタ型である PDWORD とします。
関数によっては、引数の型が 同じ DWORD でも、定数等の場合があります。その時は 32 ビットの符号なしの整数型とします。

5 番目の文字列引数は、_Out_XXX と書いてあります。これは文字通り、関数の実行結果として戻される値を意味しています。Delphi では var 渡しということになります。この引数の値を受け取る方法はいくつかありますが、本サンプルでは Char 型の配列を使用しています。
[670_他のアプリへ文字列送信と取得] に、Char 型の配列、String 型、PChar 型で受け取る場合のサンプルコードがあります。個人的には、Char 型の配列が扱いやすいと感じています。

C++ の Char *P という文は、ポインタとしての変数 P を Char 型として定義しますが、Delphi ではあらかじめ格納する文字数の領域を設定する必要があります。そこで、Char 型の配列とします。
Delphi は、ポインタ型であることを、変数の型で指定する方式です。


文字列としての引数は、以下のコードように、LPCTSTR 等でも Delphi では認識してくれます。ただし、Delphi のバージョンよっては「安全ではない型」という警告が現れます。

リスト4
引数の型に LPCTSTR も使用可能。LPCTSTR と LPTSTR の意味は同じ
function AssocQueryStringA(
              flags    : Cardinal;
              str      : Cardinal;
              pszAssoc : LPCTSTR;
              pszExtra : LPCTSTR;
              pszOut   : LPTSTR;
              pcchOut  : PDWORD): HRESULT; stdcall; external 'Shlwapi.dll';


stdcall は Windows API で利用されているデファクトスタンダードの呼出規約です。stdcall は、DLL 内の関数を使用する際の「おまじない」と考えていいでしょう。
external 'Shlwapi.dll' は、対象の関数が Shlwapi.dll という DLL にあることを意味します。対象の関数が、どの DLL に存在するかは、MSDN オンラインの該当関数の記事に書いてあります。





02-03_コード例と実行結果


以上の方針で前項のサンプルを書き変えたのが、次のコードです。
図は、Windows 7 U64(SP1) + Delphi 7 Pro での実行結果です。

このコードを Delphi 2009 以降で使用するには、AssocQueryStringA ではなく、末尾に W がある関数名にします。逆に、Ansi 版として使用する場合は、PChar を PAnsiChar に、Char を AnsiChar にして、更に、関数の引数 (コード中の変数名は ExtName) も AnsiString 型にします。
この関数は、Delphi 2005 以上には実装されていますが、ユニット内に同じ名前の定義があれば、それが優先的に使用されます。重複のエラーは発生しません。

DLL 内の関数を利用する際には、引数の型が重要となります。引数の型が、Delphi のどの変数の型に相当するかを知る必要があります。場合によっては、関数や、その引数の動作仕様から判断しなければならない場合もあります。
MSDN オンラインの記事は、C または C++ で書かれていることにも注意します。C または C++ の変数の多くはポインタ型となっています。


図5
Delphi 7 での実行例


リスト5
拡張子に関連付けされたフルパスの実行ファイル名の取得
Delphi 7 以前用
unit GetFullExePathUnit;

interface

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

type
  TForm1 = class(TForm)
    Edit2: TEdit;
    Edit1: TEdit;
    Button1: TButton;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

function AssocQueryStringA(
              flags    : Cardinal;
              str      : Cardinal;
              pszAssoc : PChar;
              pszExtra : PChar;
              pszOut   : PChar;
              pcchOut  : PDWORD): HRESULT; stdcall; external 'Shlwapi.dll';

const
  //第1引数用の定数
  ASSOCF_INIT_NOREMAPCLSID    = $00000001;
  ASSOCF_INIT_BYEXENAME       = $00000002;
  ASSOCF_OPEN_BYEXENAME       = $00000002;
  ASSOCF_INIT_DEFAULTTOSTAR   = $00000004;
  ASSOCF_INIT_DEFAULTTOFOLDER = $00000008;
  ASSOCF_NOUSERSETTINGS       = $00000010;
  ASSOCF_NOTRUNCATE           = $00000020;
  ASSOCF_VERIFY               = $00000040;
  ASSOCF_REMAPRUNDLL          = $00000080;
  ASSOCF_NOFIXUPS             = $00000100;
  ASSOCF_IGNOREBASECLASS      = $00000200;
  ASSOCF_INIT_IGNOREUNKNOWN   = $00000400;

  //第2引数用の定数
  ASSOCSTR_COMMAND          = 1;
  ASSOCSTR_EXECUTABLE       = 2;
  ASSOCSTR_FRIENDLYDOCNAME  = 3;
  ASSOCSTR_FRIENDLYAPPNAME  = 4;
  ASSOCSTR_NOOPEN           = 5;
  ASSOCSTR_SHELLNEWVALUE    = 6;
  ASSOCSTR_DDECOMMAND       = 7;
  ASSOCSTR_DDEIFEXEC        = 8;
  ASSOCSTR_DDEAPPLICATION   = 9;
  ASSOCSTR_DDETOPIC         = 10;
  ASSOCSTR_INFOTIP          = 11;
  ASSOCSTR_MAX              = 12;

var
  Form1: TForm1;

implementation

uses ComObj;

{$R *.DFM}

//=============================================================================
//  サンプルの拡張子をEditにセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  CheckBox1.Checked := True;
  CheckBox2.Checked := False;
  Edit1.Text     := '.doc';

  Edit2.ReadOnly :=True;
  Edit2.Text     :='';
end;

//=============================================================================
//  指定した拡張子に関連づけた実行ファイルのフルパス名を取得する関数
//  取得するのは拡張子の関連付けがレジストリに登録されているものの最初のもの
//
//  OleCheckやWin32Checkを入れておくと例外を検出してくれる
//  OleCheckやWin32Checkの使用にはuuseにComObjが必要
//=============================================================================
function GetRelatedFullExePath(ExtName: String;
  DoubleQuote: Boolean=True; CmdParams: Boolean=False): String;
var
  LBuffer : array [0..MAX_PAtH-1] of Char;
  LdwOut  : DWORD;
begin
  Result := '';

  FillChar(Buf, SizeOf(LBuffer), #0);
  LdwOut := MAX_PATH;

  try
    //第2引数をASSOCSTR_EXECUTABLEにすれば"を含まないパス文字列となる
    if DoubleQuote then begin
      OleCheck(AssocQueryStringA(ASSOCF_NOTRUNCATE,
                                 ASSOCSTR_COMMAND,
                                 PChar(ExtName),
                                 nil,
                                 LBuffer,
                                 @LdwOut));
    end else begin
      OleCheck(AssocQueryStringA(ASSOCF_NOTRUNCATE,
                                 ASSOCSTR_EXECUTABLE,
                                 PChar(ExtName),
                                 nil,
                                 LBuffer,
                                 @LdwOut));
    end;
    Result := LBuffer;

    if Length(Result) = 0 then begin
      Result := '';
    end else begin
      if not CmdParams then begin
        //"(ダブルコーテーション)で囲まれている場合
        if Pos('" ', Result) > 0 then begin
          Result := Copy(Result, 1, Pos('" ', Result) + 1);
        end;
      end;
    end;
  except
  end;
end;

//=============================================================================
//  指定した拡張子に関連付け実行ファイルのフルパス名を取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit2.Text := GetRelatedFullExePath(Edit1.Text,
                                      CheckBox1.Checked,
                                      CheckBox2.Checked);
end;

end.




02-04_引数の列挙型を定義して使用する例


AssocQueryString 関数の第 1, 第 2 引数は、MSDN オンラインの記事によると、列挙型となっています。したがって、次のように、ASSOCF と ASSOCSTR の各々の列挙型を定義して、それを引数の型として使用することもできます。
TOleEnum の使用には、uses に ActiveX が必要です。

リスト6
列挙型を定義して使用する場合
//  ASSOCF列挙型
//  TOleEnumの使用にはusesにActiveXが必要
type
  ASSOCF = TOleEnum;
const
  ASSOCF_INIT_NOREMAPCLSID    = $00000001;
  ASSOCF_INIT_BYEXENAME       = $00000002;
  ASSOCF_OPEN_BYEXENAME       = $00000002;
  ASSOCF_INIT_DEFAULTTOSTAR   = $00000004;
  ASSOCF_INIT_DEFAULTTOFOLDER = $00000008;
  ASSOCF_NOUSERSETTINGS       = $00000010;
  ASSOCF_NOTRUNCATE           = $00000020;
  ASSOCF_VERIFY               = $00000040;
  ASSOCF_REMAPRUNDLL          = $00000080;
  ASSOCF_NOFIXUPS             = $00000100;
  ASSOCF_IGNOREBASECLASS      = $00000200;
  ASSOCF_INIT_IGNOREUNKNOWN   = $00000400;


//  ASSOCSTR列挙型
//  TOleEnumの使用にはusesにActiveXが必要
type
  ASSOCSTR = TOleEnum;
const
  ASSOCSTR_COMMAND          = 1;
  ASSOCSTR_EXECUTABLE       = 2;
  ASSOCSTR_FRIENDLYDOCNAME  = 3;
  ASSOCSTR_FRIENDLYAPPNAME  = 4;
  ASSOCSTR_NOOPEN           = 5;
  ASSOCSTR_SHELLNEWVALUE    = 6;
  ASSOCSTR_DDECOMMAND       = 7;
  ASSOCSTR_DDEIFEXEC        = 8;
  ASSOCSTR_DDEAPPLICATION   = 9;
  ASSOCSTR_DDETOPIC         = 10;
  ASSOCSTR_INFOTIP          = 11;
  ASSOCSTR_MAX              = 12;


function AssocQueryStringA(
              flags    : ASSOCF;
              str      : ASSOCSTR;
              pszAssoc : PChar;
              pszExtra : PChar;
              pszOut   : PChar;
              pcchOut  : PDWORD): HRESULT; stdcall; external 'Shlwapi.dll';

var
  Form1: TForm1;

implementation




03_ディレクトリ名を除く EXE 名から EXE のフルパスを取得


AssocQueryString 関数の第 1 引数のフラグに ASSOCF_OPEN_BYEXENAME を含めると、ディレクトリ名を除いた EXE 名から、その EXE のフルパスを取得することができます。
取得するフルパスは、レジストリに登録されている EXE 名です。つまり、拡張子の関連付けと関係するプログラムだけが対象です。例えば calc.exe (電卓) 等のフルパスは取得できません。以下の関数では、該当 EXE のフルパスが見つからない場合は、空文字を返します。

アプリケーションのインストール先は、コンピュータによって異なる場合があります。そのような場合に、インストール先を調べるのに利用できます。また、対象のアプリケーションが使用可能かを調べる用途等に利用できます。



[備考 1]
前こ、このサンプルの掲載を中止したのは、任意のフォルダ (ディレクトリ) の EXE のフルパスが取得できないという質問を頂いたからです。基本的に、このサンプルコードでは不可能です。誤解を避けるために掲載を中止していました。
LAN 上やディスク内のファイルを全て検索するようなコードにすれば可能です。その場合、筆者のような開発環境では、Project1.exe が大量に見つかることになります。

[備考 2]
ShellExecute 関数でアプリを起動する場合、システム的にパスが通ったプログラムは、ディレクトリ名がなくても起動します。
例えば、WINWORD.EXE でワードが起動します。AcroRd32.exe では、Adobe Reader が起動します。ただし、これらのプログラムがインストールされていなければ起動しません。本サンプルのような関数を使用すれば、インストールされているか (起動可能か) を、ShellExecute 関数を実行する前に確認することができます。



図6
実行結果例


リスト7
ディレクトリ名を除く EXE 名から EXE のフルパスを取得
//=============================================================================
//  ディレクトリを除くEXE名からEXEのフルパスを取得する関数
//  取得可能なのはレジストリに登録されているもの
//  つまり、システム的にフルパスでなくても起動可能なプログラムに限る
//
//  AssocQueryStringの使用にはusesにShLwApiが必要
//  OleCheckやWin32Checkを入れておくと例外を検出してくれる
//  OleCheckやWin32Checkの使用にはuuseにComObjが必要
//=============================================================================
function GetRelatedFullExePath(ExtName: String;
  DoubleQuote: Boolean=True; CmdParams: Boolean=False): String;
var
  LBuffer : array [0..MAX_PAtH-1] of Char;
  LdwOut  : DWORD;
begin
  Result := '';

  FillChar(LBuffer, SizeOf(LBuffer), #0);
  LdwOut := MAX_PATH;

  try
    //第2引数をASSOCSTR_EXECUTABLEにすれば"を含まないパス文字列となる
    if DoubleQuote then begin
      OleCheck(ShLwApi.AssocQueryString(ASSOCF_NOTRUNCATE or ASSOCF_OPEN_BYEXENAME,
                                        ASSOCSTR_COMMAND,
                                        PChar(ExtName),
                                        nil,
                                        LBuffer,
                                        @LdwOut));
    end else begin
      OleCheck(ShLwApi.AssocQueryString(ASSOCF_NOTRUNCATE  or ASSOCF_OPEN_BYEXENAME,
                                        ASSOCSTR_EXECUTABLE,
                                        PChar(ExtName),
                                        nil,
                                        LBuffer,
                                        @LdwOut));
    end;
    Result := LBuffer;

    if Length(Result) = 0 then begin
      Result := '';
    end else begin
      if not CmdParams then begin
        //"(ダブルコーテーション)で囲まれている場合
        if Pos('" ', Result) > 0 then begin
          Result := Copy(Result, 1, Pos('" ', Result) + 1);
        end;
      end;
    end;
  except
  end;
end;




04_FindExecutable 関数を使用する方法


ファイルを開く、起動するアプリケーションの EXE 名は、FindExecutable 関数でも取得することができます。ただし、この関数は、拡張子ではなく、実際に開くファイルをフルパスで指定します。
次のサンプルでは、ワードの文書を開くアプリケーションのフルパスを取得して表示します。

アプリケーションでは通常、操作対象のファイル名をフルパスで指定します。FindExecutable 関数を使用すれば、拡張子だけを取り出すという操作が必要ありません。



[備考]
ネット上に FindExecutable 関数では、結果のパス名に空白文字がある場合、パス名が正しく取得できない等の問題があるという記事があります。
拡張子の関連付けのアプリは、レジストリに記録されています。レジストリに記録されている該当アプリのインストール先のパス名に空白があり、そのパス名が " (ダブルクォーテーション) で括られていない場合に発生するようです。



図7
実行結果

リスト8
ファイルを起動するアプリ (EXE) のフルパスを取得
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  テスト用のファイル名をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := ExtractFilePath(Application.ExeName) + 'Test.doc';
  Memo1.Lines.Clear;
end;

//=============================================================================
//  ファイルのフルパスから、起動するアプリのEXE名を取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath    : string;
  LExeFullPath : String;
  LBuffer      : array[0..MAX_PATH-1] of Char;
  LResult      : HINST;
  i            : Integer;
begin
  //ファイルのフルパス
  LFilePath := Trim(Edit1.Text);

  //初期化しないとゴミが入って正しく取得できない場合がある
  FillChar(LBuffer, SizeOf(LBuffer), #0);
  LResult := FindExecutable(PChar(LFilePath), nil, LBuffer);

  if LResult > 32 then begin
    for i := Low(LBuffer) to High(LBuffer) - 1 do begin
      if LBuffer[i] = #0 then LBuffer[i] := #32;
    end;
    LBuffer[High(LBuffer)] := #0;
    LExeFullPath := Trim(LBuffer);
  end else begin
    LExeFullPath := IntToStr(LResult);
  end;

  Memo1.Lines.Add(LExeFullPath);
end;

end.


上のコードは Char 型の静的配列を使用しています。これが一番扱いやすいのですが、参考に、文字列バッファとして、PChar 型、String 型、Char 型の動的配列を使用する例を掲載しておきます。
null 文字の処理は省略しています。null 文字の処理は、上のコードを参考にしてください。



リスト9
バッファ用の文字列に PChar, String、Char 型の動的配列を使用する例
implementation

uses
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  テスト用のファイル名をセット
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := ExtractFilePath(Application.ExeName) + 'Test.doc';
  Memo1.Lines.Clear;
end;

//=============================================================================
//  ファイルのフルパスから、起動するアプリのEXE名を取得
//  文字列バッファとしてPChar型を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath    : string;
  LExeFullPath : String;
  LResult      : HINST;
  LpResultChar : PChar;
 begin
  //ファイルのフルパス
  LFilePath := Trim(Edit1.Text);

  //文字列バッファの領域を確保して関数を実行
  LpResultChar := StrAlloc(MAX_PATH + 1);
  try
    LResult := FindExecutable(PChar(LFilePath), nil, LpResultChar);

    if LResult > 32 then begin
      LExeFullPath := LpResultChar;
      Memo1.Lines.Add(TButton(Sender).Name + ' : ' + LExeFullPath);
    end else begin
      Memo1.Lines.Add(TButton(Sender).Name + ' : ' + IntToStr(LResult));
    end;
  finally
    StrDispose(LpResultChar);
  end;
end;

//=============================================================================
//  ファイルのフルパスから、起動するアプリのEXE名を取得
//  文字列バッファとしてString型を使用する例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LFilePath    : string;
  LExeFullPath : String;
  LResult      : HINST;
  LBuffer      : string;
 begin
  //ファイルのフルパス
  LFilePath := Trim(Edit1.Text);

  //文字列バッファの領域を確保して関数を実行
  SetLength(LBuffer, MAX_PATH + 1);
  LResult := FindExecutable(PChar(LFilePath), nil , @LBuffer[1]);

  if LResult > 32 then begin
    LExeFullPath := LBuffer;
    Memo1.Lines.Add(TButton(Sender).Name + ' : ' + LExeFullPath);
  end else begin
    Memo1.Lines.Add(TButton(Sender).Name + ' : ' + IntToStr(LResult));
  end;
end;

//=============================================================================
//  ファイルのフルパスから、起動するアプリのEXE名を取得
//  文字列バッファとしてChar型の動的配列を使用する例
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LFilePath    : string;
  LExeFullPath : string;
  LBuffer      : array of Char;
  LResult      : HINST;
begin
  //ファイルのフルパス
  LFilePath := Trim(Edit1.Text);

  //文字列バッファの領域を確保して関数を実行
  SetLength(LBuffer, MAX_PATH + 1);
  LResult := FindExecutable(PChar(LFilePath), nil, PChar(LBuffer));

  if LResult > 32 then begin
    LExeFullPath := string(LBuffer);
    Memo1.Lines.Add(TButton(Sender).Name + ' : ' + LExeFullPath);
  end else begin
    Memo1.Lines.Add(TButton(Sender).Name + ' : ' + IntToStr(LResult));
  end;
end;