Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2018年11月04日] [更新 2018年11月19日] Delphi サンプルプログラム集
   
826_クリップボードのデータの退避と復元
動作確認等 Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
826_Clipboard_SaveLoad.zip [3,535 KB] 2018年11月19日版(EXE同梱)







クリップボードのデータの退避と復元


画像,テキストあるいはファイルをクリップボードから取得したり,クリップボードにセットするサンプルが以下の記事にあります.それらのサンプルでは,クリップボードのフォーマット形式の 1 つが操作対象となっています.


本ページのサンプルは,現在クリップボードにセットされているデータを全てメモリストリームに保存します.その保存したクリップボードのデータをクリップボードにセットすることができます.
本サンプルでは,貼り付け操作可否のチェックの記事のサンプルで使用している TNkClipDetector コンポーネントを使用しています.


[備考]
Windows 10 では 2018年5月のアップデートで,制限はありますが,クリップボードの履歴の利用と他の端末との共有ができるようになりました.
その一方で,クリップボードを狙った新規のマルウェアも登場しているようです.





01_クリップボードのデータをメモリストリームに保存


以下が実際のサンプルプログラムのコードです.
[退避] ボタンでクリップボードの内容をメモリストリームに格納します.クリップボードにデータがある時だけ操作できます.[復元] ボタンでメモリストリームのデータをクリップボードに貼り付け,つまりセットします.[クリア] はクリップボードを空にします.

サイズが大きいフォルダやファイルを退避したりクリップボードにセットすると時間がかかります.時間がかかる処理は,別のスレッドで処理するのが一般的です.



図1
設計時の画面
図2
実行時の画面

リスト1
クリップボードのデータをメモリストリームに保存.保存したデータをクリップボートにセット
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    NkClipDetector1: TNkClipDetector;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure NkClipDetector1ClipChange(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    FMemStream : TMemoryStream;
  end;

  procedure ClipBoardSaveToStream(AStream: TMemoryStream);
  procedure ClipBoardLoadFromStream(AStream: TMemoryStream);
  function WERDigThrough(const ExpInfo: TExceptionPointers): Integer; stdcall;
  function EOSErrorContinue(ExceptionInfo : TExceptionPointers): Integer; stdcall;
  function AddVectoredExceptionHandler(FirstHander: ULONG; VectoredHandler: Pointer):
    PVOID; stdcall; external 'kernel32.dll';

var
  Form1: TForm1;

implementation

uses
  Clipbrd;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FMemStream := TMemoryStream.Create;

  // エラー処理モードの設定
  // 呼び出し側のアプリケーションで処理
  SetErrorMode(SEM_FAILCRITICALERRORS);

  // 例外発生時の処理用関数を設定(非デバッグ時専用)
  SetUnhandledExceptionFilter(@WERDigThrough);

  // 例外発生時の処理用関数を設定(デバッグ時専用)
  AddVectoredExceptionHandler(0, @EOSErrorContinue);

  NkClipDetector1ClipChange(nil);
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FMemStream);
end;

//=============================================================================
//  クリップボードのデータをメモリストリームに格納
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LInfoText : string;
begin
  Button1.Enabled := False;
  Button2.Enabled := False;
  Button3.Enabled := False;

  if Clipboard.FormatCount = 0 then begin
    LInfoText := 'クリップボードが空か無効です';
    MessageBox(Handle, PChar(LInfoText), '情報', MB_ICONINFORMATION);
  end else begin
    ClipboardSaveToStream(FMemStream); //待避
    if FMemStream.Size = 0 then begin
      LInfoText := 'クリップボードのデータ取得に失敗しました';
      MessageBox(Handle, PChar(LInfoText), '情報', MB_ICONINFORMATION);
    end;
  end;

  NkClipDetector1ClipChange(nil);
end;

//=============================================================================
//  退避したクリップボードのデータ内容を復元
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Button1.Enabled := False;
  Button2.Enabled := False;
  Button3.Enabled := False;

  ClipboardLoadFromStream(FMemStream);

  NkClipDetector1ClipChange(nil);
end;

//=============================================================================
//  クリップボードの内容をクリア(消去)
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  Clipboard.Clear;
end;

//=============================================================================
//  TNkClipDetectorのClipChangeイベント
//  クリップボードの内容に変更があると発生するイベント
//=============================================================================
procedure TForm1.NkClipDetector1ClipChange(Sender: TObject);
var
  LFmtCnt : Integer;
begin
  LFmtCnt := Clipboard.FormatCount;
  Button1.Enabled := LFmtCnt > 0;
  Button2.Enabled := FMemStream.Size > 0;
  Button3.Enabled := LFmtCnt > 0;
end;

//-----------------------------------------------------------------------------
//  クリップボードのデータをメモリストリームに格納するルーチン
//
//  CF_BITMAPまたはCF_DIBの時にGlobalLock,GlobalSizeで例外が発生することがある
//  そこでWin32Checを実行してEOSError(OSのエラー)を発生させる
//  するとRaiseLastOSErrorが実行されてtry - exceptで捕捉可能になる
//  ただし,そのままではデバッガの例外通知のダイアログが表示されてしまう
//-----------------------------------------------------------------------------
procedure ClipBoardSaveToStream(AStream: TMemoryStream);
var
  LhGlobal  : HGLOBAL;
  LFormatId : UINT;
  LdwSize   : DWORD;
  LpBuffer  : Pointer;
  LIndex    : Integer;
begin
  AStream.Clear;
  AStream.Position := 0;

  Clipboard.Open;
  try
    for LIndex := 0 to ClipBoard.FormatCount - 1 do begin
      LFormatId := ClipBoard.Formats[LIndex];
      if not IsClipboardFormatAvailable(LFormatId) then Continue;

      // クリップボードのデータを取得
      LhGlobal := GetClipBoardData(LFormatId);
      if LhGlobal > 0 then begin
        try
          try
            // クリップボードのデータのポインタとデータサイズの取得
            if Win32Check(GlobalLock(LhGlobal) <> nil) then begin
              LpBuffer := GlobalLock(LhGlobal);
            end;

            if Win32Check(GlobalSize(LhGlobal) <> 0) then begin
              LdwSize := GlobalSize(LhGlobal);
            end;
          except
            LdwSize  := 0;
            LpBuffer := nil;
          end;

          if (LdwSize > 0) then begin
            // フォーマットの形式,データのサイズ,実査際のデータの順に格納
            // 取り出す時もこの順番で取り出す
            AStream.Write(LFormatID, SizeOf(LFormatId));
            AStream.Write(LdwSize, SizeOf(LdwSize));
            AStream.Write(LpBuffer^, LdwSize);
          end;
        finally
          GlobalUnlock(LhGlobal);
        end;
      end;
    end;
  finally
    Clipboard.Close;
  end;
end;

//-----------------------------------------------------------------------------
//  メモリストリームのデータをクリップボードにコピー(セット)するルーチン
//-----------------------------------------------------------------------------
procedure ClipBoardLoadFromStream(AStream: TMemoryStream);
var
  LhGlobal  : HGLOBAL;
  LFormatId : UINT;
  LdwSize   : DWORD;
  LpBuffer  : Pointer;
begin
  AStream.Position := 0;
  Clipboard.Clear;
  Clipboard.Open;

  try
    while True do begin
      if AStream.Position >= AStream.Size then Break;

      // クリップボードの形式(クリップボードの識別子)とデータサイズを取り出す
      AStream.Read(LFormatId, SizeOf(LFormatId));
      AStream.Read(LdwSize, SizeOf(LdwSize));

      LhGlobal := GlobalAlloc(GMEM_MOVEABLE, LdwSize);
      LpBuffer := GlobalLock(LhGlobal);
      try
        AStream.Read(LpBuffer^, LdwSize);
      finally
        GlobalUnlock(LhGlobal);
      end;
      // クリップボードにデータをセット
      SetClipboardData(LFormatId, LhGlobal);
    end;
  finally
    Clipboard.Close;
  end;
end;

//-----------------------------------------------------------------------------
//  SetUnhandledExceptionFilter関数のコールバック関数
//  SetUnhandledExceptionFilter関数は非デバッグ時専用の関数
//
//  EXE実行時に例外が発生するとWER(Windows Error Report)のダイアログが現れる
//  するとアプリが終了またはハングアップする
//  WERを表示しないでアプリも終了させないようにするための処理
//-----------------------------------------------------------------------------
function WERDigThrough(const ExpInfo: TExceptionPointers): Integer; stdcall;
const
  EXCEPTION_EXECUTE_HANDLER = 1;
  EXCEPTION_CONTINUE_SEARCH = 0;
begin
  Result := EXCEPTION_EXECUTE_HANDLER;
end;

//-----------------------------------------------------------------------------
//  AddVectoredExceptionHandler関数のコールバック関数(デバッグ時専用)
//
//  本サンプルではOSの例外(DLL内部での例外))を捕捉するようにしている
//  そのままではデバッガの例外通知のダイアログが表示される
//  そのダイアログを表示しないようにするための処理
//  時々機能しないことがある
//
//  Delpi XE5にはAddVectoredExceptionHandler関数は実装されていない
//-----------------------------------------------------------------------------
function EOSErrorContinue(ExceptionInfo : TExceptionPointers): Integer; stdcall;
const
  EXCEPTION_EXECUTE_HANDLER = 1;
  EXCEPTION_CONTINUE_SEARCH = 0;
begin
  Result := EXCEPTION_CONTINUE_SEARCH;
end;

end.




02_Win32Check を使用して OS 例外を try - except で捕捉


上のサンプルでは,クリップボードのデータを取得する際,データのポインタとデータサイズ取得のコードが以下のようになっています.Win32Check で OS の例外発生をチェックし,その例外を捕捉するための try - except を追加しています.

リスト2
上のサンプルにおけるクリップボードのデータのポインタとデータサイズ取得のコード
          try
            // クリップボードのデータのポインタとデータサイズの取得
            if Win32Check(GlobalLock(LhGlobal) <> nil) then begin
              LpBuffer := GlobalLock(LhGlobal);
            end;

            if Win32Check(GlobalSize(LhGlobal) <> 0) then begin
              LdwSize := GlobalSize(LhGlobal);
            end;
          except
            LdwSize  := 0;
            LpBuffer := nil;
          end;

もし Windows API の関数 GlobalLock あるいは GlobalSize で例外が発生しなければ,上のコードは以下のコードで十分です.

リスト3
クリップボードのデータのポインタとデータサイズ取得の本来のコード
          LpBuffer := GlobalLock(LhGlobal);
          LdwSize  := GlobalSize(LhGlobal);

しかし,上のクリップボードのデータ取得のサンプルではこの部分で例外が発生し,下図のようにデバッガ例外通知のダイアログ現れることがあります.
この例外は,クリップボードのフォーマット形式に CF_BITMAP と CF_DIB の両方が含まれている時に発生することがあります.発生しないこともあります.どちらか一方だけを検出してデータを取得するようなコーディングでは発生しません.

この例外が発生しても,デバッガ例外通知のダイアログで [継続] を選択すればクリップボードのデータは取得できます.
クリップボードに CF_BITMAP 形式でデータをセットすると CF_DIB のデータもセットされます.逆にフォーマット形式が CF_DIB のデータをセットすると CF_BITMAP のデータもセットされます.おそらくこのことが関係しているのかも知れません.



図3
OS 例外

デバッガ例外通知のダイアログで [継続] をクリックすればデータは取得できると書きましたが,実はそのままでは正しいデータが取得できる保証はありません.
GlobalLock 関数と GlobalSize 関数は Windows API の関数です.つまり Windows が提供している DLL 内部で発生する例外です.このような OS の例外は try - except では捕捉できません.

本サンプルでは,Win32Check 関数を使用して OS の例外を try - except で捕捉できるようにしています.それだけではデバッガ例外通知のダイアログは表示されてしまいます.
そこで SetErrorMode 関数でエラーの処理モードを設定します.更に,AddVectoredExceptionHandler 関数のコールバック関数内で,デバッガ例外通知のダイアログで [継続] を選択した時と同じ操作をするように設定しています.

残念ながら,これらの処理をしてもデバッガ例外通知のダイアログが現れることがあります.しかし [継続] をクリックすれば try - except は確実に実行されますから,間違いなくクリップボードのデータは取得できます.



リスト4
エラーの処理モードの設定
  SetErrorMode(SEM_FAILCRITICALERRORS);

リスト5
AddVectoredExceptionHandler 関数のコールバック関数.デバッグ時の OS 例外処理用
function EOSErrorContinue(ExceptionInfo : TExceptionPointers): Integer; stdcall;
const
  EXCEPTION_EXECUTE_HANDLER = 1;
  EXCEPTION_CONTINUE_SEARCH = 0;
begin
  Result := EXCEPTION_CONTINUE_SEARCH;
end;


[備考]
ネイティブ OS の例外の一覧が Delphi の IDE で確認できます.
メニューの [ツール] [オプション] のダイアログで,一番下にある [ネイティブ OS 例外] を選択すると表示されます.ここでの設定は,開発するアプリ全てが対象となります.





03_Windows Error Report ( WER ) によるプログラムの終了を回避


前項で述べた処理をしても,OS の例外が発生した場合,作成した EXE を直接実行 (デバッガを使わずに実行 [Shift] + [Ctrl] + [F9]) すると下図のようなダイアログが現れてプログラムが強制終了されてしまいます.
本サンプルではこれを回避するために,SetUnhandledExceptionFilter 関数を使用して,try - except に処理を戻すようにしています.したがって,本サンプルでは EXE を直接実行する場合,このダイアログが現れることはありません.

SetUnhandledExceptionFilter 関数は非デバッグ時にだけ有効な関数です.デバッグ時には処理されません.一方,AddVectoredExceptionHandler 関数は,デバッグ時にだけ有効な関数です.



図4
EXE を直接実行した場合
  • [キャンセル] をクリックするとプログラムが終了する
  • 例外が発生しない場合はこのダイアログは現れない
図5
上のダイアログで [キャンセル] しなかった場合
  • WER (Windows Error Report) のダイアログが現れる
  • プログラムは強制終了となる


リスト6
SetUnhandledExceptionFilter 関数のコールバック関数.非デバッグ時の OS 例外処理用
function WERDigThrough(const ExpInfo: TExceptionPointers): Integer; stdcall;
const
  EXCEPTION_EXECUTE_HANDLER = 1;
  EXCEPTION_CONTINUE_SEARCH = 0;
begin
  Result := EXCEPTION_EXECUTE_HANDLER;
end;




04_保存したメモリストリームからビットマップ画像を取得


例えば画像をコピーして本サンプルでメモリストリームに保存したとします.このメモリストリームからビットマップを取得するにはビットマップヘッダ部の処理が必要です.ヘッダ部の具体的な処理については以下の記事のコードを参考にしてください.



もう 1 つの方法はクリップボードを利用する方法です.該当するメモリストリームのデータをクリップボードにコピーして,そのクリップボードのデータを取得します.以下はその例です.



図6
実行画面

リスト7
保存したメモリストリームからビットマップ画像を取得.クリップボードを利用する例
procedure ClipBoardLoadFromStream(AStream: TMemoryStream);
var
  LhGlobal  : HGLOBAL;
  LFormatId : UINT;
  LdwSize   : DWORD;
  LpBuffer  : Pointer;
  LBitmap   : TBitmap;
  LSaveFlag : Boolean;
begin
  AStream.Position := 0;

  LSaveFlag := False;
  while True do begin
    if AStream.Position >= AStream.Size then Break;

    // クリップボードの形式(クリップボードの識別子)とデータサイズを取り出す
    AStream.Read(LFormatId, SizeOf(LFormatId));
    AStream.Read(LdwSize, SizeOf(LdwSize));

    LhGlobal := GlobalAlloc(GMEM_MOVEABLE, LdwSize);
    LpBuffer := GlobalLock(LhGlobal);
    try
      // 実際には読み出さなくてもよい
      // メモリストリームの読み出し位置を変更すればよい
      AStream.Read(LpBuffer^, LdwSize);
    finally
      GlobalUnlock(LhGlobal);
    end;

    if (LFormatId = CF_BITMAP) or (LFormatId = CF_DIB) then begin
      // クリップボードにデータをセット
      Clipboard.Clear;
      Clipboard.Open;
      SetClipboardData(LFormatId, LhGlobal);

      Clipboard.Close;
      Clipboard.Open;
      LBitmap := TBitmap.Create;
      try
        // クリップボードからビットマップを取得
        // 以下でもOK
        // LBitmap.Handle := Clipboard.GetAsHandle(CF_BITMAP);
        LBitmap.Assign(Clipboard);

        // 保存
        LBitmap.SaveToFile('000.bmp');
      finally
        FreeAndNil(LBitmap);
        Clipboard.Close;
      end;
      Clipboard.Close;
      LSaveFlag := True;
      Break;
    end;
  end;

  if not LSaveFlag then begin
    MessageBox(0, 'ビットマップデータはありません', '情報', MB_ICONWARNING);
  end;
end;




05_保存したメモリストリームからテキストデータを取得


保存したメモリストリームからテキストを取得するサンプルです.
テキストデータはメモリストリームのデータそのものですから,そのまま TStringStream 等で取得できます.



図7
実行画面

リスト8
保存したメモリストリームからテキストデータを取得
procedure ClipBoardLoadFromStream(AStream: TMemoryStream);
var
  LFormatId : UINT;
  LdwSize   : DWORD;
  LStream   : TStringStream;
  LSaveFlag : Boolean;
begin
  AStream.Position := 0;

  LSaveFlag := False;
  LStream   := TStringStream.Create;
  try
    while True do begin
      if AStream.Position >= AStream.Size then Break;

      // クリップボードの形式(クリップボードの識別子)とデータサイズを取り出す
      AStream.Read(LFormatId, SizeOf(LFormatId));
      AStream.Read(LdwSize, SizeOf(LdwSize));

      // メモリストリームからデータを読み出す
      LStream.Clear;
      LStream.Size := LdwSize;
      AStream.Read(LStream.Bytes[0], LdwSize);

      if (LFormatId = CF_TEXT) then begin
        // 保存
        LStream.SaveToFile('000.txt');
        LSaveFlag := True;
        Break;
      end;
    end;
  finally
    FreeAndNil(LStream);
  end;

  if not LSaveFlag then begin
    MessageBox(0, 'テキストデータはありません', '情報', MB_ICONWARNING);
  end;
end;


上のコードは CF_TEXT 形式のテキストデータを取得する例です.
保存したメモリストリームから CF_UNICODETEXT 形式のテキストデータを取得するには,例えば以下のように TStringList を使用して取得できます.TStringList は文字列を読み込む時も保存する時もコードページの指定,つまりエンコードの指定が可能です.このことを利用します.



リスト9
保存したメモリストリームから CF_UNICODETEXT 形式のテキストデータを取得
      if (LFormatId = CF_UNICODETEXT) then begin
        LStrList := TStringList.Create;
        try
          LStrList.LoadFromStream(LStream, TEncoding.Unicode);
          LStrList.SaveToFile('000_Unicode.txt', TEncoding.Unicode);
          Form1.Memo1.Lines.Text := LStrList.Text;
        finally
          FreeAndNil(LStrList);
        end;
        LSaveFlag := True;
        Break;
      end;
    end;


また,メモリストリームのバイトデータをそのまま String 型の変数のデータとして以下のようにしても取得できます.この String 型の変数の文字列をテキスト表示用のコントロールに表示します.以下のコードでは TMemo に表示します.Delphi 2009 以降の TMemo はユニコードに対応したコントロールですからそのまま表示できます.
TMemo のテキストを保存する時はエンコードを指定します.エンコードを指定しないと,ユニコードな文字が文字化けします.メモリストリームのバイトデータを格納する文字列の型は,UnicodeString 型でも構いません.



リスト10
保存したメモリストリームから CF_UNICODETEXT 形式のテキストデータを取得
      if (LFormatId = CF_UNICODETEXT) then begin
        SetLength(LText, LStream.Size * SizeOf(Char));
        LStream.ReadBuffer(LText[1], LStream.Size);

        Form1.Memo1.Lines.Text := LText;
        Form1.Memo1.Lines.SaveToFile('000_Unicode.txt', TEncoding.Unicode);

        LSaveFlag := True;
        Break;
      end;
    end;




06_保存したメモリストリームからファイル名のリストを取得


エクスプローラでコピーしたファイルのリストをメモリストリームに保存した後,そのメモリストリームからファイル名のリストを取得するサンプルです.
メモリストリームの先頭はヘッダ部です.このヘッダ部は TDropFiles レコード型 (_DROPFILES 構造体) のデータです.実際のファイル名はこのヘッダ部の後に記録されています.

ファイル名は Null 終端文字列が複数結合された形で格納されています.本サンプルではこれを先頭から順番に取り出して TStringList に格納していきます.



図8
エクスプローラでコピーしたファイルのリストの例

リスト11
保存したメモリストリームからエクスプローラでコピーしたファイル名のリストを取得
procedure ClipBoardLoadFromStream(AStream: TMemoryStream);
var
  LFormatId     : UINT;
  LdwSize       : DWORD;
  LStream       : TBytesStream;
  LGetFlag      : Boolean;
  LDropFiles    : TDropFiles;
  LDataSize     : Integer;
  LSrcPChar     : PChar;
  LSrcAnsiPChar : PAnsiChar;
  LPChar        : PChar;
  LAnsiPChar    : PAnsiChar;
  LStrList      : TStringList;
begin
  AStream.Position := 0;

  LGetFlag := False;
  LStream  := TBytesStream.Create;
  try
    while True do begin
      if AStream.Position >= AStream.Size then Break;

      // クリップボードの形式(クリップボードの識別子)とデータサイズを取り出す
      AStream.Read(LFormatId, SizeOf(LFormatId));
      AStream.Read(LdwSize, SizeOf(LdwSize));

      // メモリストリームからデータを読み出す
      LStream.Clear;
      LStream.Size := LdwSize;
      AStream.Read(LStream.Bytes[0], LdwSize);

      if (LFormatId <> CF_HDROP) then Continue;

      //ヘッダ部の情報を取り出す
      LStream.Position := 0;
      LStream.Read(LDropFiles, SizeOf(LDropFiles));

      if LDropFiles.fNC then Break;

      LDataSize := LStream.Size - LDropFiles.pFiles;
      LStrList := TStringList.Create;
      try
        if LDropFiles.fWide then begin
          // ユニコード (Unicode) の場合
          // 文字列を格納するメモリ領域を確保
          LSrcPChar := AllocMem(LDataSize);
          try
            // ファイル名のデータ部分をPChar型として取得
            LStream.Position := LDropFiles.pFiles;
            LStream.ReadBuffer(LSrcPChar^, LDataSize);
            LPChar := LSrcPChar;

            //各々のNull終端文字列を取得する
            while (StrLen(LPChar) > 0) do begin
              //取得した文字列をリストに格納
              LStrList.Add(LPChar);
             //取得元のポインタを移動(+1はnull終端(#0)の分)
              //LPChar := LPChar + Length(LPChar) + 1; でもよい
              LPChar := LPChar + StrLen(LPChar) + 1;
            end;
          finally
            FreeMem(LSrcPChar);
          end;
        end else begin
          // 非ユニコード (Ansi) の場合
          // 手順はユニコードの場合と同じ
          LSrcAnsiPChar := AllocMem(LDataSize);
          try
            LStream.Position := LDropFiles.pFiles;
            LStream.ReadBuffer(LSrcAnsiPChar^, LDataSize);
            LAnsiPChar := LSrcAnsiPChar;

            while (Length(LAnsiPChar) > 0) do begin
              LStrList.Add(LAnsiPChar);
              LAnsiPChar := LAnsiPChar + Length(LAnsiPChar) + 1;
            end;
          finally
            FreeMem(LSrcAnsiPChar);
          end;
        end;

        // 取得結果を表示
        LStrList.Sort;
        Form1.Memo1.Lines.Text := LStrList.Text;

        LGetFlag := LStrList.Count > 0;
      finally
        FreeAndNil(LStrList);
      end;
      Break;
    end;
  finally
    FreeAndNil(LStream);
  end;

  if not LGetFlag then begin
    Form1.Memo1.Lines.Clear;
    MessageBox(0, 'ファイルリスト関係のデータはありません', '情報', MB_ICONWARNING);
  end;
end;