Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2014年05月05日] [更新 2014年05月30日] Delphi サンプルプログラム集
316_EXE を作成した Delphi のバージョンを判定
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
316_GetDelphiVersion.zip [4,729 KB] 2014年05月30日版 (EXE 同梱)



  • 2014年05月28日
  • バージョン判定の文字列を一部修正
  • Delphi のエディションの判定と Access License の値取得のサンプルを追加
  • 2014年05月30日
  • Delphi 2007 でも使用可能なサンプルを追加
  • コードをコピペして利用した時に「無効なポインタ操作」が発生する現象を修正 (Delphi のバージョンによっては発生しない) - コピペとは直接の関係はないようである
  • バージョンとビット数の取得等のコードを別ユニットにした




EXE を作成した Delphi のバージョンの判定


EXE を作成した Delphi のバージョンを調べるサンプルです.以下の手順で調べます.
  • EXE が Delphi あるいは BCB (C++Builder) で作成されたかをリソースから判定する
  • Delphi の時は,Delphi のバージョンを調べる
  • そのために,EXE のバイナリを文字列にして,バージョンを特定する文字列を検索する
  • バージョンを特定する文字列は [EXE を生成するのに使われた...] の記事を利用する
  • 32 ビットと 64 ビットの判定には GetBinaryType 関数または PE ヘッダから取得する
EXE のリソースを,リソースエディタで調べると下図のようになっています.
Delphi または BCB で作成されたバイナリには RC DATA 階層下に,DVCLAL があります.Delphi には更に PACKAGEINFO という項目がありますが,BCB (C++Builder) で作成された EXE, DLL には,この PACKAGEINFO という項目がないようです.そこで,これを判定材料に使用しています.ただし,ネット上には,PACKAGEINFO リソースの内容で Delphi か C++ または他の開発言語であるかを判定している記事があります.各自で確認してください.

DVCLAL には,16 バイトのデータが格納されていますが,これはコンパイラのバージョン情報ではありません.DVCLAL は,Delphi Visual Component Libraray Access License の略です.

バイナリデータを操作する場合,Delphi XE で実装された TBinaryReader や TBinaryWriter は,指定した変数の型での読み書きができ便利なので,本ページのサンプルで使用していますが,Delphi 2009, 2010 でも使用可能なコードも掲載しています.

なお,本ページのサンプルはコードは,[メモリストリーム内の ASCII 文字列を検索] 以外は,メモリを大量に消費します.


図1
Delphi XE で作成した EXE のリソース
  • Delphi には DVCLAL と PACKAGEINFO がある
  • BCB で作成した EXE には PACKAGEINFO がないらしい
  • Delphi 2 以前の EXE にも PACKAGEINFO がない


自分自身の EXE を作成した Delphi のバージョンであれば,以下のように,CompilerVersion を使用して,実行時に取得することができます.
CompilerVersion, RTLVersion は Delphi 6 以降で使用可能です.



自分自身のバージョン (Delphi XE の場合)

procedure TForm1.Button1Click(Sender: TObject);
var
  LVersion : String;
begin
  Memo1.Lines.Clear;

  LVersion := FloatToStr(System.CompilerVersion);
  Memo1.Lines.Add('CompilerVersion : ' + LVersion);

  LVersion := FloatToStr(System.RTLVersion);
  Memo1.Lines.Add('RTLVersion      : ' + LVersion);
end;




01_EXE ファイルを選択して Delphi のバージョンを判定


実際のサンプルコードと実行結果です.
2014年05月03日現在の時点では,Delphi 1 ~ Delphi XE5 までに対応しています.BCB (C++Builder) の場合は,バージョンは取得しません.また,コンソールアプリケーションや DLL の場合も,バージョンの取得コードは実行していません.
コンソールアプリケーションや DLL には,多くの場合,フォーム関係の情報は含まれていません.そのため,Delphi のバージョンを特定する文字列を推定することは困難です.
コンソールアプリケーションであるか,DLL であるかの判定が必要であれば,PE ヘッダから取得することができます.

サンプルプログラムでは,判定結果を確実にするため,上位のバージョンから順番に,階層的に検出するようにしています.バイナリファイル内全ての文字列を検索するこの方法は,Delphi のバージョンの判定が常に正確に判定できる保証はありません.

バージョン取得の実際のコードは,別のユニットにしています.添付のプロジェクトでは,このユニットをプロジェクトに追加して使用しています.


図2
設計時画面
図3
実行結果例
  • Delphi XE4 の 64 ビットの FireMonkey アプリケーションの場合
  • 上はファイル名.その下が調査結果
図4
Delphi 1 以降の調査が可能
図5
BCB (C++Builder) で作成された EXE のバージョンは調査しない
図6
Delphi XE5 の IDE の EXE
  • Delphi 7 で出来ているらしい
  • EXE のサイズが大きいので (約 17 MB),調査には時間がかかる

リスト1
EXE を作成した Delphi のバージョンを調べる
Delphi XE 以降用のコード
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    InitDir : String;
    function GetEXEDLLText(FileName: String): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses plGetDelphiVer;

{$R *.dfm}

//=============================================================================
//  EXEまたはDLLを作成したDelphiのバージョンを調べる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  FileFullPath : String;
  StrCompiler  : String;
  BinaryType   : Integer;
  StrExe       : String;
begin
  if InitDir = EmptyStr then begin
    InitDir := ExpandFileName('../');
  end;

  OpenDialog1.InitialDir := InitDir;
  OpenDialog1.Filter     := 'ファイル|*.exe;*.dll';

  if OpenDialog1.Execute then begin
    FileFullPath := OpenDialog1.FileName;
    InitDir      := ExtractFileDir(FileFullPath);

    Memo1.Clear;
    Memo1.Update;
    Memo1.Lines.BeginUpdate;

    Memo1.Lines.Add(' ' + ExtractFileName(FileFullPath));

    //まずDelphiかBCBで作成されたものかを調べる
    StrCompiler := plGetDelphiVer.CheckDelphiOrBCB(FileFullPath);
    //32ビット判定か64ビット版かを判定
    BinaryType := plGetDelphiVer.GetBinaryTypeBits(FileFullPath);

    if BinaryType > 0 then begin
      //EXE,DLLの内容をテキストに変換
      StrExe := GetEXEDLLText(FileFullPath);

      if UpperCase(ExtractFileExt(FileFullPath)) = '.EXE' then begin
        if StrCompiler = 'C++Builder' then begin
          Memo1.Lines.Add(' ' + StrCompiler + '  ' + IntToStr(BinaryType) + 'bit');
        end else begin
          Memo1.Lines.Add(' '
               + plGetDelphiVer.GetDelphiVersionFromText(StrExe, StrCompiler)
               + '  '
               + IntToStr(BinaryType) + 'bit');
        end;
      end else begin
        if StrCompiler = 'Others' then begin
          Memo1.Lines.Add(' unknown  ' + IntToStr(BinaryType) + 'bit');
        end else begin
          Memo1.Lines.Add(' ' + StrCompiler + '  ' + IntToStr(BinaryType) + 'bit');
        end;
      end;
    end;

    Memo1.Lines.EndUpdate;
  end;
end;

//-----------------------------------------------------------------------------
//  EXEまたはDLLのバイナリデータをテキストに変換
//-----------------------------------------------------------------------------
function TForm1.GetEXEDLLText(FileName: String): String;
var
  fStream   : TFileStream;
  BinReader : TBinaryReader;
  i         : Integer;
  CharArray : TCharArray;
begin
  Result := '';

  //ファイルストリームを生成
  fStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
  //ファイルストリームから読み込む
  BinReader := TBinaryReader.Create(fStream, TEncoding.Default, false);
  try
    //Char型配列に格納
    CharArray := BinReader.ReadChars(fStream.Size);
  finally
    FreeAndNil(BinReader);
    FreeAndNil(fStream);
  end;

  //制御文字を空白文字に変換
  //制御文字も検索対象にしたい場合は不要
  for i := 0 to Length(CharArray) - 1 do begin
    if CharArray[i] < Char($20) then begin
      if (CharArray[i] <> Char($0D)) and (CharArray[i] <> Char($0A)) then begin
        CharArray[i] := ' ';
      end;
    end;
  end;
  //目的の文字列に代入
  Result := Trim(String(CharArray));
end;

end.


リスト2
実際に,EXE または DLL のテキストから Delphi のバージョンを取得するコード類
添付のプロジェクトでは,このユニットをプロジェクトに追加している
unit plGetDelphiVer;

interface

uses
  Windows, SysUtils, Classes, Dialogs;

  function GetDelphiVersionFromText(BinText: String; Kind: String): String;
  function CheckDelphiOrBCB(Filename: TFileName): String;
  function GetBinaryTypeBits(FileName: TFileName): Integer;

implementation

uses StrUtils;

//-----------------------------------------------------------------------------
//  バイナリファイルが32ビット版か64ビット版かを調査
//  DLLはGetBinaryTypeでは検出できない
//  PEヘッダから取得する
//  PEヘッダは32ビットと64ビットでは構造体が違うが,ビット数は取得可能
//-----------------------------------------------------------------------------
function GetBinaryTypeBits(FileName: TFileName): Integer;
const
  SCS_32BIT_BINARY = 0;
  SCS_64BIT_BINARY = 6;

  //Delphi XE2以降では不要
  IMAGE_NT_OPTIONAL_HDR32_MAGIC = $010B;
  IMAGE_NT_OPTIONAL_HDR64_MAGIC = $020B;
var
  RetFlag        : LongBool;
  BinType        : Cardinal;
  FStream        : TFilestream;
  DummyData      : DWORD;
  HeaderInfo_DOS : TImageDosHeader;
  HeaderInfo_OPT : TImageOptionalHeader;
  OptMagic       : WORD;
begin
  Result :=0;

  RetFlag := GetBinaryType(PChar(Filename), BinType);

  //EXEの場合
  if RetFlag then begin
    if BinType = SCS_32BIT_BINARY then begin
      Result := 32;
    end else
    if BinType = SCS_64BIT_BINARY then begin
      Result := 64;
    end else
    if BinType = SCS_WOW_BINARY then begin
      Result := 16;
    end;
    exit;
  end;


  //ファイルストリームを生成
  //EXEも以下で取得できるが16ビットが検出が確実でないのでGetBinaryTypeを使用
  FStream := TFilestream.Create(FileName, fmOpenread or fmShareDenyNone);
  try
    //IMAGE_DOS_HEADERの情報をHeaderInfo_DOSレコード型に読み込む
    FStream.Read(HeaderInfo_DOS, SizeOf(HeaderInfo_DOS));

    //IMAGE_FILE_HEADER部の先頭に移動するための位置移動
    FStream.Seek(HeaderInfo_DOS._lfanew, soFromBeginning);
    FStream.Read(DummyData, SizeOf(DummyData));
    //シグネチャを確認
    if DummyData <> IMAGE_NT_SIGNATURE then exit;

    //IMAGE_FILE_HEADERの構造体の分だけ移動
    FStream.Seek(SizeOf(TImageFileHeader), soFromCurrent);

    //IMAGE_OPTIONAL_HEADER部の情報をHeaderInfo_OPTIONALレコード型に読み込む
    FStream.Read(HeaderInfo_OPT, SizeOf(HeaderInfo_OPT));
    OptMagic := HeaderInfo_OPT.Magic;
    if OptMagic = IMAGE_NT_OPTIONAL_HDR64_MAGIC then Result := 64;
    if OptMagic = IMAGE_NT_OPTIONAL_HDR32_MAGIC then Result := 32;
  finally
    FStream.Free;
  end;
end;

//-----------------------------------------------------------------------------
//  EnumResourceTypesのコールバック関数
//  RT_RCDATAがあるかをチェック
//  DelphiまたはC++Builderで作成したEXE,DLLにはデフォルトでこれがある
//  他の開発言語で開発した場合も,リソースを追加すると,RT_RCDATAができる
//-----------------------------------------------------------------------------
function EnumResTypeProc(hModule: THandle; szType: PChar;
  lParam: PInteger): Bool; stdcall;
begin
  Result := True;
  if szType = RT_RCDATA then begin
    lParam^ := 1;
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
//  EXEまたはDLLのリソースからDelphi製かBCB(C++Builder)製かを調査
//  DVCLALとPACKAGEINFOがあればDelphi製.PACKAGEINFOがなければBCB製
//-----------------------------------------------------------------------------
function CheckDelphiOrBCB(Filename: TFileName): String;
var
  hModule   : THandle;
  IntResult : THandle;
  CheckType : Integer;
begin
  Result := 'Others';

  hModule := LoadLibraryEx(PChar(Filename), 0, LOAD_LIBRARY_AS_DATAFILE);
  if hModule > 0 then begin
    //リソースにRT_RCDATEの型があるか調査
    //実際にはチェックしなくてもDVCLAL等の存在はチェック可能
    CheckType := 0;
    EnumResourceTypes(hModule, @EnumResTypeProc, LongInt(@CheckType));

    if CheckType > 0 then begin
      IntResult := FindResource(hModule, 'DVCLAL', RT_RCDATA);
      if IntResult > 0 then begin
        IntResult := FindResource(hModule, 'PACKAGEINFO', RT_RCDATA);
        if IntResult > 0 then begin
          Result := 'Delphi';
        end else begin
          Result := 'C++Builder';
        end;
      end else begin
        //Delphi 2または他の開発言語
      end;
    end else begin
      //Delphi 1または他の開発言語
    end;
  end;
  FreeLibrary(hModule);
end;

//-----------------------------------------------------------------------------
//  EXEまたはDLLを作成したDelphiのバージョンを調査
//  EXEまたはDLL内の文字列を検索していく
//  DLLは正確に判定できない
//-----------------------------------------------------------------------------
function GetDelphiVersionFromText(BinText: String; Kind: String): String;
begin
  Result := 'unknown';

  //テキストに変換したEXEまたはDLLの中身を検索してバージョンを確定
  //Kind = 'Delphi'はリソースから判断して,確実にDelphi製と言える場合
  if (Kind = 'Delphi') then begin
    if (ContainsText(BinText, 'TForm3D'))
       and (ContainsText(BinText, 'System.Classes.TMemoryStream')) then begin
      Result := 'Delphi XE6 (FireMonkey 3D) or later';
    end else
    if (ContainsText(BinText, 'System.Classes.TMemoryStream')) then begin
      Result := 'Delphi XE6 (FireMonkey HD) or later';
    end else
    if (ContainsText(BinText, 'System.Win.TaskbarCore')) then begin
      Result := 'Delphi XE6 (VCL) or later';
    end else

    if (ContainsText(BinText, 'TForm3D'))
       and (ContainsText(BinText, 'TMessage<FMX.Controls.TStyleBook>')) then begin
      Result := 'Delphi XE5 (FireMonkey 3D)';
    end else
    if (ContainsText(BinText, 'TMessage<FMX.Controls.TStyleBook>')) then begin
      Result := 'Delphi XE5 (FireMonkey HD)';
    end else
    if (ContainsText(BinText, 'TFormatSettings.TEraInfo')) then begin
      Result := 'Delphi XE5 (VCL)';
    end else

    if (ContainsText(BinText, 'TForm3D'))
       and (ContainsText(BinText, 'UsingDesignCamera'))
       and (ContainsText(BinText, 'FStyleBookChanged')) then begin
      Result := 'Delphi XE4 (FireMonkey 3D)';
    end else
    if (ContainsText(BinText, 'ShowFullScreenIcon')) then begin
      Result := 'Delphi XE4 (FireMonkey HD)';
    end else
    if (ContainsText(BinText, 'ApplyNonBiDiKeyboard')) then begin
      Result := 'Delphi XE4 (VCL)';
    end else

    if (ContainsText(BinText, 'TForm3D'))
       and (ContainsText(BinText, 'UsingDesignCamera'))
       and (ContainsText(BinText, 'EnableBorderStyling'))
       and (ContainsText(BinText, 'FormFactor')) then begin
      Result := 'Delphi XE3 (FireMonkey 3D)';
    end else
    if (ContainsText(BinText, 'EnableBorderStyling'))
       and (ContainsText(BinText, 'FormFactor')) then begin
      Result := 'Delphi XE3 (FireMonkey HD)';
    end else
    if (ContainsText(BinText, 'StyleElements'))
       and (ContainsText(BinText, 'TipMode')) then begin
      Result := 'Delphi XE3 (VCL)';
    end else

    if (ContainsText(BinText, 'TForm3D'))
       and (ContainsText(BinText, 'UsingDesignCamer')) then begin
      Result := 'Delphi XE2 (FireMonkey 3D)';
    end else
    if (ContainsText(BinText, 'StyleBook'))
       and (ContainsText(BinText, 'StyleLookup')) then begin
      Result := 'Delphi XE2 (FireMonkey HD)';
    end else
    if (ContainsText(BinText, 'UnitScope')) then begin
      Result := 'Delphi XE2 (VCL)';
    end else

    if (ContainsText(BinText, 'csPaintBlackOpaqueOnGlass')) then begin
      Result := 'Delphi XE';
    end else

    if (ContainsText(BinText, 'TTouchManager'))
       and (ContainsText(BinText, 'OnGesture')) then begin
      Result := 'Delphi 2010';
    end else

    if (ContainsText(BinText, 'CustomHint'))
       and (ContainsText(BinText, 'UnicodeString')) then begin
      Result := 'Delphi 2009';
    end else

    if (ContainsText(BinText, 'GlassFrame')) then begin
      Result := 'Delphi 2007';
    end else

    if (ContainsText(BinText, 'AlignWithMargins')) then begin
      Result := 'Delphi 2006 / Turbo Delphi';
    end else

    if (ContainsText(BinText, 'PopupMode')) then begin
      Result := 'Delphi 2005';
    end else

    if (ContainsText(BinText, 'OnMouseEnter'))
       and (ContainsText(BinText, 'OnMouseLeave'))
       and (ContainsText(BinText, 'ClxWidget_MaxWidgetSize')) then begin
      Result := 'Delphi 7 (CLX)';
    end else
    if (ContainsText(BinText, 'ScreenSnap')) then begin
      Result := 'Delphi 7';
    end else

    if (ContainsText(BinText, 'ClxWidget_MaxWidgetSize')) then begin
      Result := 'Delphi 6 (CLX)';
    end else
    if (ContainsText(BinText, 'AlphaBlend')) then begin
      Result := 'Delphi 6';
    end else

    if (ContainsText(BinText, 'TMenuAnimations')) then begin
      Result := 'Delphi 5';
    end else

    if (ContainsText(BinText, 'OldCreateOrder')) then begin
      Result := 'Delphi 4';
    end else

    if (ContainsText(BinText, 'ParentFont')) then begin
      Result := 'Delphi 3';
    end else begin
      Result := 'Delphi';
    end;
  end else
  if Kind = 'Others' then begin
    //Delphi1,2はリソースのデータでは,他の開発言語との区別ができない
    //以下のように,Delphiを特徴づける文字列であれば確認できる
    if (ContainsText(BinText, 'WideCharToMultiByte'))
       and (ContainsText(BinText, 'TFormBorderStyle')) then begin
      Result := 'Delphi 2';
    end else
    if (ContainsText(BinText, 'TFormBorderStyle')) then begin
      Result := 'Delphi 1';
    end;
  end;
end;

end.




02_Delphi のエディションの判定と Access License の値


DVCLAL (Delphi Visual Component Libraray Access License) の値と,EXE を作成した Delphi のエディションを判定するサンプルです.

自アプリであれば,RCS 関数,RPR 関数を使用して取得,判定できます.下のコードは,他の EXE の Access License の値と,エディションを判定します.Delphi のオンラインヘルプでは,クライアント/サーバライセンスの判定となっていますが,おそらく Enterprise 版以上であるかの判定ではないかと思われます.
筆者は Pro 版しか所有していませんので,確認はしていません.Enterprise 版以上でもなく,Pro 版でもなければ,おそらく Starter 版や Personal 版と思われます.
処理内容は,SysUtils 内の RSC 関数等のコードを,他の EXE 用にしたものです.

2014年05月28日現在,最新の Delphi のバージョンは Delphi XE6 です.Starter, Professional, Enterprise, Ultimate, Architect の 5 つのエディションがあります.


図7
実行結果例
  • Enterprise 版ではない
  • Pro 版である

リスト3
Delphi のエディションの判定と Access License の値を取得
//-----------------------------------------------------------------------------
//  Delphi Visual Component Libraray Access Licenseの1番目の値を取得
//-----------------------------------------------------------------------------
function VCLLicense_AL1(const P): LongWord;
asm
  MOV     EDX,DWORD PTR [P]
  XOR     EDX,DWORD PTR [P+4]
  XOR     EDX,DWORD PTR [P+8]
  XOR     EDX,DWORD PTR [P+12]
  MOV     EAX,EDX
end;

//-----------------------------------------------------------------------------
//  Delphi Visual Component Libraray Access Licenseの2番目の値を取得
//-----------------------------------------------------------------------------
function VCLLicense_AL2(const P): LongWord;
asm
  MOV     EDX,DWORD PTR [P]
  ROR     EDX,5
  XOR     EDX,DWORD PTR [P+4]
  ROR     EDX,5
  XOR     EDX,DWORD PTR [P+8]
  ROR     EDX,5
  XOR     EDX,DWORD PTR [P+12]
  MOV     EAX,EDX
end;

//=============================================================================
//  Access Licenseの値の取得と,エディションの判定
//  EXEがDelphiであるか,他の言語で作成されたものかのチェックはしていない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  //Delphi XE(UP1) ProのSysUtils.pas内の定義
  AL1s: array[0..3] of LongWord = ($FFFFFFF0, $FFFFEBF0, 0, $FFFFFFFF);
  AL2s: array[0..3] of LongWord = ($42C3ECEF, $20F7AEB6, $D1C2F74E, $3F6574DE);

var
  FileFullPath : String;
  hModule      : THandle;
  hRes         : THandle;
  pALR_Value   : Pointer;
  AL1_Value    : LongWord;
  AL2_Value    : LongWord;
begin
  if InitDir = EmptyStr then begin
    InitDir := ExpandFileName('../../');
  end;

  OpenDialog1.InitialDir := InitDir;
  OpenDialog1.Filter     := 'ファイル|*.exe;*.dll';

  if OpenDialog1.Execute then begin
    FileFullPath := OpenDialog1.FileName;
    InitDir      := ExtractFileDir(FileFullPath);

    Memo1.Clear;
    Memo1.Update;

    Memo1.Lines.Add(' ' + ExtractFileName(FileFullPath));
    Memo1.Lines.Add(' ');

    hModule := LoadLibraryEx(PChar(FileFullPath), 0, LOAD_LIBRARY_AS_DATAFILE);
    if hModule = 0 then exit;
    try
      pALR_Value := Pointer(LoadResource(hModule,
                      FindResource(hModule, 'DVCLAL', RT_RCDATA)));
      if pALR_Value = nil then exit;
      try
        hRes := FindResource(hModule, 'PACKAGEINFO', RT_RCDATA);
        if hRes = 0 then exit;

        //Access Licenseの値
        //自アプリの場合,1番目の値はGDAL関数で取得可能
        //自アプリの場合,2番目の値を取得する関数はない
        AL1_Value := VCLLicense_AL1(pALR_Value^);
        AL2_Value := VCLLicense_AL2(pALR_Value^);
        Memo1.Lines.Add(' Access License 1 : ' + IntToStr(AL1_Value));
        Memo1.Lines.Add(' Access License 2 : ' + IntToStr(AL2_Value));

        //Enterprise版であるかのチェック
        //DelphiのオンラインヘルプではClient Serverの判定となっている
        //自アプリの場合は,RCS関数でチェック可能
        Memo1.Lines.Add(' ');
        if (VCLLicense_AL1(pALR_Value^) = AL1s[2]) and
           (VCLLicense_AL2(pALR_Value^) = AL2s[2]) then begin
          Memo1.Lines.Add(' Delphi Enterprise licence');
        end else begin
          Memo1.Lines.Add(' Not Delphi Enterprise licence');
        end;

        //Pro版であるかのチェック
        //自アプリの場合は,RPR関数でチェック可能
        if (AL1_Value <> AL1s[1]) and (AL1_Value <> AL1s[2]) then begin
          Memo1.Lines.Add(' Not Delphi Pro licence');
        end else begin
          Memo1.Lines.Add(' Delphi Pro licence');
        end;
      finally
        FreeResource(Integer(pALR_Value));
      end;
    finally
      FreeLibrary(hModule);
    end;
  end;
end;




03_バージョンを特定する文字列の決め方


サンプルでは,Delphi のバージョンを特定するために,EXE の中の文字列を検索しています.
この文字列は,uses 部のユニットに含まれるコンポーネント名,あるいはメソッド名,関数名やプロパティ名です.Delphi がバージョンアップすると,使用可能なコンポーネントや関数等が追加されます.それを Delphi のバージョンの判定に利用します.

もし,EXE や DLL に Delphi のバージョンを特定する情報が埋め込まれていれば,それを判定に利用できます.しかし,筆者が調べた限りでは,そのような情報を知ることはできませんでした.
下のコードは,Delphi XE で,新規にプロジェクトを作成した時のアプリケーションマニフェストのコードです,これは,EXE にリソースとして埋め込まれます.

ここには,version="15.0.3953.35171" という,Delphi のコンパイラの情報が記述されています.この情報は,Delphi の全てのバージョンが埋め込むわけではありません.また,開発者がマニフェストを編集したりすれば,なくなる場合も考えられます.したがって,バージョンの特定に利用するには無理があります.


リスト4
Delphi XE Pro で新規にプロジェクトを作成した時の,アプリケーションマニフェスト
version="15.0.3953.35171" が Delphi XE の製品バージョン
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    type="win32"
    name="CodeGear RAD Studio"
    version="15.0.3953.35171" 
    processorArchitecture="*"/>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        publicKeyToken="6595b64144ccf1df"
        language="*"
        processorArchitecture="*"/>
    </dependentAssembly>
  </dependency>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel
          level="asInvoker"
          uiAccess="false"/>
        </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>


下のコードは,Delphi XE で,新規にコンソールアプリケーションを作成した時のプロジェクトファイルのソースコードです.uses には SysUtils しかありません.
したがって,SysUtils 内の関数やプロパティ等の追加分を調べればバージョンの特定に使用できる可能性はあります.ただし,Delphi のバージョンが上がっても,SysUtils に関数やプロパティ類が追加されていなければ判定することはできないことになります.


リスト5
Delphi XE Por で新規にコンソールアプリを作成した時のプロジェクトファイル
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

begin
  try
    { TODO -oUser -cConsole メイン : ここにコードを記述してください }
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.




04_EXE ( DLL ) のテキスト表示とバージョン判定文字列


サンプルコード中の StrExe という文字列型変数は,EXE のバイナリを文字列にしたものです.
下図は,この文字列,テキストを TMemo に表示した結果です.先頭に,COM フォーマットと区別するためのシグネチャである MZ という文字があります.

その下の図は,リソースとして埋め込んだ文字列部分を表示したものです.最初のサンプルコードは,このリソースの文字列も検索することになります.また,TPopupMenu コンポーネント等のプロパティ等も EXE 内に文字列として埋め込まれます.
したがって,Delphi XE4 以前でアプリケーションを作成したとします.この時,アプリケーションの開発者が,意識的に,TFormatSettings.TEraInfo という文字列を使用していれば,Delphi XE5 という結果になってしまいます.

これらのリソースを検索対象から除外するには,例えば,EXE の .text セクション部分だけを検索する処理を追加する方法が考えられます.そのためには,Delphi のセクションデータの配置構造の情報が必要です.あるいは,リソース部分を検索対象から除外する方法も考えられます.リソースは .rscr セクションに格納場所の情報があります.

コード内で定義した文字列変数や,文字列定数の値は,バイナリに変換されます.したがって,検索対象には含まれません.


図8
EXE のバイナリコードを文字で表示
  • エンコードによって,表示文字 (記号 ?) が変わる
図9
リソースの文字列部分
  • リソースの文字列は,EXE にそのまま埋め込まれる

リスト6
EXE ( DLL ) の内容をテキストで表示
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    InitDir : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  EXEまたはDLL等のバイナリデータをテキストとしてTMmmoに表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  FileFullPath : String;
  fStream      : TFileStream;
  BinReader    : TBinaryReader;
  i            : Integer;
  CharArray    : TCharArray;
begin
  if InitDir = EmptyStr then begin
    InitDir := ExpandFileName('../delphi_exe_sample');
  end;

  OpenDialog1.InitialDir := InitDir;
  OpenDialog1.Filter     := 'ファイル|*.exe;*.dll';

  if OpenDialog1.Execute then begin
    FileFullPath := OpenDialog1.FileName;
    InitDir      := ExtractFileDir(FileFullPath);

    Screen.Cursor := crHourGlass;

    //ファイルストリームを生成
    fStream := TFileStream.Create(FileFullPath, fmOpenRead or fmShareDenyNone);
    //ファイルストリームから読み込む
    BinReader := TBinaryReader.Create(fStream, TEncoding.Default, false);
    try
      //Char型配列に格納
      CharArray := BinReader.ReadChars(fStream.Size);
    finally
      FreeAndNil(BinReader);
      FreeAndNil(fStream);
    end;

    //制御文字を空白文字に変換
    for i := 0 to Length(CharArray) - 1 do begin
      if CharArray[i] < Char($20) then begin
        if (CharArray[i] <> Char($0D)) and (CharArray[i] <> Char($0A)) then begin
          CharArray[i] := ' ';
        end;
      end;
    end;
    //TMemoに表示
    Memo1.Text := Trim(String(CharArray));

    Screen.Cursor := crDefault;
  end;
end;

end.




05_EXE の内容をテキスト表示する必要がない場合


最初のサンプルでは,制御文字を空白文字に変換していますが,上の図のように,TMome 等で表示可能にするために処理しているだけです.
文字列の検索だけであれば,その必要はありません.バイナリコードを文字列に変換する部分のコードは,以下のように処理すれば十分です.
いずれにしても,バイナリファイルの場合は,バイナリデータとして取得してから処理します.

TStringList 等や後述のように,バイナリデータに変換しないで,テキストデータとして取得してしまうと,バイナリファイルの内容にもよりますが,文字列が欠落したり,違う文字になってしまうことがあります.


リスト7
テキスト表示用コントロールに表示しなけば,制御文字等の処理は不要
//-----------------------------------------------------------------------------
//  EXEまたはDLLのバイナリデータをテキストに変換
//-----------------------------------------------------------------------------
function TForm1.GetEXEDLLText(FileName: String): String;
var
  fStream   : TFileStream;
  BinReader : TBinaryReader;
  CharArray : TCharArray;
begin
  Result := '';

  //ファイルストリームを生成
  fStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
  //ファイルストリームから読み込む
  BinReader := TBinaryReader.Create(fStream, TEncoding.Default, false);
  try
    //Char型配列に格納
    CharArray := BinReader.ReadChars(fStream.Size);
  finally
    FreeAndNil(BinReader);
    FreeAndNil(fStream);
  end;
  //目的の文字列に代入
  Result := Trim(String(CharArray));
end;




06_バイト配列から TEncoding を使用して文字列に変換


バイト配列から文字列に変換する部分は,TEncoding クラスを使用すれば簡単になります.
例えば次のようにも書けます.


リスト8
TEncoding を使用して,バイト配列から文字列を取得する
//-----------------------------------------------------------------------------
//  EXEまたはDLLのバイナリデータをテキストに変換
//-----------------------------------------------------------------------------
function TForm1.GetEXEDLLText(FileName: String): String;
var
  fStream   : TFileStream;
  BinReader : TBinaryReader;
  BinArray  : TBytes;
begin
  Result := '';

  //ファイルストリームを生成
  fStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
  //ファイルストリームから読み込む
  //比較するのはASCII文字だけなので,エンコードはDefaultでOK
  BinReader := TBinaryReader.Create(fStream, TEncoding.Default, false);
  try
    //ファイルの内容をバイト配列に格納
    SetLength(BinArray, fStream.Size);
    BinArray := BinReader.ReadBytes(fStream.Size);

    //バイト配列の値を文字列に変換
    Result := TEncoding.Default.GetString(BinArray);
  finally
    FreeAndNil(BinReader);
    FreeAndNil(fStream);
  end;
end;




07_TFileStream からバイト配列に格納


最初のサンプルで,TFileStream を使用しています.
TFileStream にもバイナリデータを読み書きするメソッド類があります.それらを使用して,次のようにファイルの内容をバイト配列に格納することもできます.


リスト9
TFileStream から直接バイトデータを読み出す
//-----------------------------------------------------------------------------
//  EXEまたはDLLのバイナリデータをテキストに変換
//-----------------------------------------------------------------------------
function TForm1.GetEXEDLLText(FileName: String): String;
var
  fStream  : TFileStream;
  BinArray : TBytes;
begin
  Result := '';

  //ファイルストリームを生成
  fStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
  try
    //バイト配列のサイズを設定
    SetLength(BinArray, fStream.Size);
    //バイト配列に読み出す
    fStream.Read(BinArray[0], Length(BinArray));
    //以下のコードでもOK
    //fStream.ReadBuffer(Pointer(BinArray)^, Length(BinArray));
    //fStream.ReadBuffer(BinArray[0], Length(BinArray));
  finally
    FreeAndNil(fStream);
  end;
  //バイト配列の値を文字列に変換
  Result := TEncoding.Default.GetString(BinArray);
end;




08_TMemoryStream からバイト配列に格納


TMemoryStream もバイナリデータを扱います.したがって,例えば,次のようなコードで,バイナリファイルのデータをバイト配列に格納できます.


リスト10
TMemoryStream を使用して,ストリームの内容をバイト配列に格納する例
//-----------------------------------------------------------------------------
//  EXEまたはDLLのバイナリデータをテキストに変換
//-----------------------------------------------------------------------------
function TForm1.GetEXEDLLText(FileName: String): String;
var
  fStream  : TMemoryStream;
  BinArray : TBytes;
begin
  Result := '';

  //メモリストリームを生成
  fStream := TMemoryStream.Create;
  try
    //ストリームから読み込む
    fStream.LoadFromFile(FileName);
    //バイト配列のサイズを設定
    SetLength(BinArray, fStream.Size);
    //バイト配列に読み出す
    fStream.Position := 0;
    fStream.Read(BinArray[0], Length(BinArray));
    //以下のコードでもOK
    //fStream.ReadBuffer(Pointer(BinArray)^, Length(BinArray));
    //fStream.ReadBuffer(BinArray[0], Length(BinArray));
  finally
    FreeAndNil(fStream);
  end;
  //バイト配列の値を文字列に変換
  Result := TEncoding.Default.GetString(BinArray);
end;




09_EXE または DLL のバイナリを文字列に変換 - Delphi 2007 以前用


上のサンプルコードは,Delphi 2007 以前でもコンパイルできます.しかし,結果としての文字列が正常に取得できないため,バージョンの取得は失敗します.

Delphi 2009 で Delphi はユニコード化されました.文字と文字列の扱いが便利になりました.
Delphi 2007 以前では,文字をバイト単位で扱うことがあります.それらのコードは,Delphi 2009 以降では期待通りに動作しません.それは文字コード (文字コードページ) の違いです.
そのため,Delphi 2007 以前では,上のコードでは正常な文字列として取得できません. 

次のコードは,Delphi 2007 以前でも動作するコードです.ファイルストリームのデータをバイト配列に格納してから,文字列に変換します.
このコードは,変換した文字列から,EXE 内の文字列を検索するためだけのコードです.改行等の特殊文字の処理は一切していません.本格的に処理するのであれば,修正が必要です.

Delphi 7 以前には,文字列検索用の関数 ContainsText がありません.別の関数を使用する必要があります.以下のリンクの記事を参考にしてください.Delphi 2005, 2006 については未確認です.
ContainstText 関数は,内部で AnsiPos 関数を使用しています.



図10
実行例
  • 最初の行は,読み込んだファイル名
  • その下が調査結果

リスト11
EXE または DLL のバイナリを文字列に変換 - Delphi 2007 以前用
//-----------------------------------------------------------------------------
//  EXEまたはDLLのバイナリデータをテキストに変換 - Delphi2007以前用
//-----------------------------------------------------------------------------
function TForm1.GetEXEDLLText(FileName: String): String;
var
  fStream  : TFileStream;
  BinArray : TBytes;
  i        : Integer;
begin
  Result := '';

  //ファイルストリームを生成
  fStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
  try
    //バイト配列のサイズを設定
    SetLength(BinArray, fStream.Size);
    //バイト配列に読み出す
    fStream.Read(BinArray[0], Length(BinArray));

    for i := 0 to Length(BinArray) - 1 do begin
      if BinArray[i] >= $20 then begin
        Result := Result + Char(BinArray[i]);
      end;
    end;
  finally
    FreeAndNil(fStream);
  end;
end;


戻り値である文字列のポインタを使用するとすれば,例えば次のように書けます.
この場合は,その文字列の長さをあらかじめ設定しておきます.そうしないと,ポインタが移動しても格納先が不定のままなので,例外が発生します.


//-----------------------------------------------------------------------------
//  EXEまたはDLLのバイナリデータをテキストに変換 - Delphi2007以前用
//-----------------------------------------------------------------------------
function TForm1.GetEXEDLLText(FileName: String): String;
var
  fStream  : TFileStream;
  BinArray : TBytes;
  pStrExe  : PChar;
  i        : Integer;
begin
  Result := '';

 //ファイルストリームを生成
  fStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
  try
    //バイト配列のサイズを設定
    SetLength(BinArray, fStream.Size);
    //バイト配列に読み出す
    fStream.Read(BinArray[0], Length(BinArray));

    SetLength(Result, fStream.Size);
    pStrExe := @Result[1];
    for i := 0 to Length(BinArray) - 1 do begin
      if BinArray[i] >= $20 then begin
        pStrExe^ := Char(BinArray[i]);
        inc(pStrExe);
      end;
    end;
  finally
    FreeAndNil(fStream);
  end;
end;




10_メモリストリーム内の文字列を検索


本ページのサンプルでは,バイナリファイルの内容を文字列にして検索しています.文字列に変換しないで,メモリストリーム内の ASCII 文字列を検索することもできます.以下はその例です.
このコードは,メモリ内だけの比較なので,Delphi 2007 以前の Delphi でも使用できます.

そのためには,検索する文字列の方をバイナリに変換します.下のコードは,検索する文字列をバイト配列に格納します.そして,バイナリファイルをメモリストリームに読み込みます.そのメモリストリーム中の文字列を検索して,対象の文字列がいくつ存在したかを表示します.

本サンプルでは,メモリの先頭から検索しています.対象の文字列の存在だけを検査する場合,この方法は効率がいいとは言えません.高速処理を期待するのであれば,検索のアルゴリズムの採用を検討してください.EXE のようなバイナリデータでも,2 分法 (二分探索) ぐらいは使用可能です.

このコードは,一般的なバイナリデータの検索や読み書きに応用できますが,バイナリデータが,実際にどのような変数の型として格納されているかよって,処理を考える必要があります.
TBinaryReader や TBinaryWriter を使用すると,指定した変数の型での読み書きができます.


図11
実行例
  • 最初の行は,読み込んだファイル名
  • その下が検索結果

リスト12
メモリストリーム内の ASCII 文字列を検索する例
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.dfm}


//=============================================================================
//  フォーム生成時の処理
//  検索する文字列を設定
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'TForm';
end;

//=============================================================================
//  メモリストリーム内の文字列を検索する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  FileFullPath : String;
  SearcText    : AnsiString;
  fStream      : TMemoryStream;
  ByteArray    : TBytes;
  i            : Integer;
  pStream      : PByte;
  CntResult    : Integer;
begin
  if InitDir = EmptyStr then begin
    InitDir := ExpandFileName('../');
  end;

  OpenDialog1.InitialDir := InitDir;
  OpenDialog1.Filter     := 'ファイル|*.exe;*.dll';

  if OpenDialog1.Execute then begin
    FileFullPath := OpenDialog1.FileName;
    InitDir      := ExtractFileDir(FileFullPath);

    Memo1.Lines.Clear;
    Memo1.Update;
    Memo1.Lines.BeginUpdate;

    //検索文字列をバイト配列に格納する
    //AnsiStringにすることに注意
    //Delphi 2007以前では,String型は自動的にAnsiString型となる
    SearcText := AnsiString(Trim(Edit1.Text));
    SetLength(ByteArray, Length(SearcText));
    for i := 0 to Length(ByteArray) - 1 do begin
      ByteArray[i] := Byte(SearcText[i + 1]);
    end;

    fStream := TMemoryStream.Create;
    try
      Memo1.Lines.Add(ExtractFileName(FileFullPath));
      //ファイルの内容をメモリストリームに読み出す
      fStream.LoadFromFile(FileFullPath);

      CntResult := 0;
      fStream.Position := 0;
      pStream := fStream.Memory;
      //メモリストリームの先頭から検索していく
      for i := 0 to fStream.Size - Length(ByteArray) do begin
        //先頭の文字が一致したら...
        if pStream^ = ByteArray[0] then begin
          //文字列全体を比較する
          if CompareMem(pStream, @ByteArray[0], Length(ByteArray)) then begin
            inc(CntResult);
          end;
        end;
        inc(pStream);
      end;
    finally
      FreeAndNil(fStream);
    end;

    Memo1.Lines.Add(' ' + IntToStr(CntResult) + ' 個見つかりました');
    Memo1.Lines.EndUpdate;
  end;
end;

end.


上のコードでは,バイトデータ配列の TByte を使用していますが,任意の型のデータを配列で扱う場合は,TArray やジェネリクス (Generics) の TArray<>, TList<> を使用すると便利です.
ただし,ジェネリクスは Delphi 2009 で実装された機能です.ジェネリクスを使用するには uses に Generics.Collections が必要です.






11_Visual Studio のバージョンの判定


Delphi ではありませんが,マイクロソフトの開発言語である Visual Studio の VC++ で作成された EXE の場合,PE ヘッダのリンカーバージョンから取得できるようです.
下記の [VBScript で PE ヘッダを読み込む方法 ... ] に記事がありました.
[08_EXE ( DLL ) が 32 ビットか 64 ビットかを判定 - PEヘッダを使用] に,PE ヘッダからリンカーのバージョンを取得するサンプルがあります.