64 ビット Windows プログラミング ガイド for Delphi 64 ビット コンパイラ

 Delphi で 64 ビットプログラミングを行う際の Tips です。カテゴリ分けは元ネタの "64 ビット Windows プログラミング ガイド (MSDN)" を踏襲しています。

 MSDN の各トピックに関連する "Delphi の情報を補足" という形で記述してありますので、まずは元ネタのトピックをお読みください。



64 ビット版 Windows での使用に向けた準備 [ MSDN ]

 "64 ビット版 Windows" と言っても 2 種類あります。

 これらはアーキテクチャの異なる 2 種類の CPU で動作します。

 Win32 アプリケーションは x86 (i386) バイナリであれば、どちらも "WOW64 (Windows 32-bit On Windows 64-bit)" という機構の上で動作しますが、x64 バイナリは IA-64 で動作せず、その逆もまた動作しません。Delphi の 32bit コンパイラが吐くバイナリは x86 で、64bit コンパイラが吐くバイナリは x64 となります。つまり、"Delphi の 64bit コンパイラが吐いたバイナリは IA-64 用 OS 上では動作しない" という事です。

アプリケーション 16bit Windows 32bit Windows 64bit Windows
x86 x86 x64 IA64
Win64 (IA64) アプリケーション
(Delphi コンパイラでは生成不可)
× × ×
Win64 (x64) アプリケーション
(Delphi 64bit コンパイラで生成可能)
× × ×
Win32 (x86) アプリケーション
(Delphi 32bit コンパイラで生成可能)
×
(WOW64)

(WOW64)
Win16 (x86) アプリケーション
(Delphi 16bit コンパイラで生成可能)

(WOW32)
× ×

 ここでは Delphi の 64bit コンパイラが吐いたバイナリが動作する "Windows x64 (Win64)" に絞って話を進めます。

抽象データ モデル [ MSDN ]

 Win32 では "integer = 32bit"、"long = 32bit"、"ポインタ = 32bit" でした。Win64 では "integer = 32bit"、"long = 32bit"、"ポインタ = 64bit" となっています。これを LLP64 (IL32P64) と呼びます。OS によって採用されている抽象データ モデルは異なり、例えば Unix 系の OS では "integer = 32bit"、"long = 64bit"、"ポインタ = 64bit" な LP64 を採用しています。

抽象データ モデル short int long ポインタ
LLP64 (IL32P64) - Windows x64 16bit 32bit 32bit 64bit
LP64 (I32LP64) - Unix 系 16bit 32bit 64bit 64bit

 これを Delphi のコンパイラに当てはめてみると以下のようになります。

コンパイラ SmallInt Integer LongInt Pointer
Delphi 64bit コンパイラ 16bit 32bit 32bit 64bit
Delphi 32bit コンパイラ 16bit 32bit 32bit 32bit
Delphi 16bit コンパイラ (なし) 16bit 32bit 16bit

 また、上記とは別にプラットフォーム (OS) 依存でサイズが変化する整数型 (NativeInt / NativeUint) も用意されています。

OS NativeInt NativeUInt
Windows x64 (Windows 64bit) 符号あり 64bit 整数 符号なし 64bit 整数
Windows x86 (Windows 32bit) 符号あり 32bit 整数 符号なし 32bit 整数

 Delphi がマルチプラットフォームに対応した際には、この "抽象データ モデル" が厄介な事になりそうですが、とりあえずは LLP64 だけ覚えておけばいいでしょう。

新しいデータ型 [ MSDN ]

 Delphi の新しいデータ型については DocWiki の "64 ビット データ型と 32 ビット データ型の比較" を参照して下さい。Delphi に於いては C++ 程にポインタに神経質になる必要はありません。この点は Delphi の利点だと思います。ただ、何らかの移植を伴う際には C++ の型を意識しなくてはならない場合があります。そのような場合には "Delphi 型と C++ 型のマッピング (DocWiki)" を参考にして下さい。

環境 [ MSDN ]

 非常に悩ましいトコロですが、開発環境をコンパクトにまとめたいのなら実機の 64bit OS 上に Delphi をインストールした方がよさそうです。

 仮想 PC を構築するなら、ゲスト OS を 32bit にすべきです。64bit のゲスト OS を構築するには制限があります。

Virtual PC VMware Player VirtualBox
32bit ホスト OS 64bit ホスト OS 32bit ホスト OS 64bit ホスト OS 32bit ホスト OS 64bit ホスト OS
32bit ゲスト OS
64bit ゲスト OS × × △ (AMD-V / Intel-VT が必須) △ (AMD-V / Intel-VT が必須) △ (AMD-V / Intel-VT が必須) △ (AMD-V / Intel-VT が必須)

 ゲスト OS で扱えるメモリ量はホスト OS が扱えるメモリ量に依存しますので、32bit ホスト上の 64bit ホストというのはテスト環境としての使い勝手がよくないでしょう。加えて、ゲスト OS に 64bit Windows を利用できる仮想 PC であっても、"必ず VT が必要" になります。AMD の CPU はほぼ間違いなく VT に対応していますが、Intel の CPU は個別に VT の有無を調べる必要があります

 また、Intel の Atom プロセッサ、AMD の Turion Neo / Athlon Neo / Fusion C-50 等はメモリ上限が 4GB 以下なため 64bit CPU であっても、64bit OS の利点をあまり活かすことができません。

 開発用とデバッグターゲット用の OS が異なる場合にはリモートデバッガを使まなくてはならない場合があります。デバッグの組み合わせのパターンを環境別に図解してみます。

[32bit OS で仮想 PC を利用しない場合]

Win32 アプリを作成 Win64 アプリを作成

従来通りです。

デバッグを行うためには Win64 アプリを実行させるための別の実機が必要となります。デバッグはリモートデバッガを介して行われます。

[64bit OS で仮想 PC を利用しない場合]

Win32 アプリを作成 Win64 アプリを作成

従来通りです。但し WOW64 上での動作となるため、32bit Windows 上の動作には一部制約があります。例えば、Windows 7 には 32bit 版 Explorer が存在しないため、シェルエクステンション等のテストは行えません。

従来通りです。

[32bit OS 上の仮想 64bit OS を利用して Win64 アプリをデバッグする場合]

Win64 アプリを作成

VT が必須となります。仮想 64bit のゲスト OS は 32bit ホスト OS の影響を受けるため、利用できるメモリが極端に少なくなります。

"仮想 1" とほぼ同じですが、ホスト<->ゲスト間のファイルの転送を伴わないので幾分こちらの方が楽だと思います。Delphi は仮想 OS にインストールします。

[64bit OS 上の仮想 32bit OS を利用して Win32 アプリをデバッグする場合]

Win32 アプリを作成

デバッグはリモートデバッガを介して行われます。あまり意味はないように思えるかもしれませんが、仮想 PC とは言え "WOW64 上での実行でない" 事が重要です。

従来の方法を仮想 PC 上に構築しただけとなります。Delphi は仮想 OS にインストールします。

 64bit OS 上に Delphi をインストールするのが最も無難なようです。なお、リモートデバッガを介してデバッグする場合、"プロジェクトオプション" の "リモートデバッグシンボルを含める" にチェックが入っていないとブレークポイントが効かなくなりますのでご注意ください。

ツール [ MSDN ]

 Delphi では条件シンボルとして以下のような定義がなされています。

[CPU アーキテクチャの条件シンボル]

条件シンボル 説明
CPUX86 アプリケーションターゲット CPU は x86 (IA-32) である。
X86ASM という条件シンボルも同時に定義されます。
CPUX64 アプリケーションターゲット CPU は x64 (AMD64 / Intel 64) である。
X64ASM / PUREPASCAL という条件シンボルも同時に定義されます。

[OS の条件シンボル]

条件シンボル 説明
MSWINDOWS アプリケーションターゲット OS は Microsoft Windows である。
LINUX アプリケーションターゲット OS は Linux である。
POSIX アプリケーションターゲット OS は Unix 系である。
MACOS アプリケーションターゲット OS は Apple MacOS である。

[Windows の条件シンボル]

条件シンボル 説明
WIN32 アプリケーションターゲット Windows は Windows x86 (32bit Windows) である。
WIN64 アプリケーションターゲット Windows は Windows x64 (64bit Windows) である。

 物理的に 64bit CPU が搭載されていても、CPUX64 が定義される訳ではありません。これはコンパイラがターゲットとする CPU の条件シンボルだからです。その他の条件シンボルについては "3.異なるバージョンの Delphi でソースを共有する" を参照して下さい。

ポインタの使用規則 [ MSDN ]

 Delphi ではそんなに意識する場面はないはずです。ポインタを Integer 等でキャストしている場合の修正方法は DocWiki の "ポインタ操作 (32 ビット Delphi アプリケーションの 64 ビット Windows への変換)" を参考にして下さい。

仮想アドレス領域 [ MSDN ]

 PE ヘッダ の "IMAGE_FILE_LARGE_ADDRESS_AWARE" の話だと思います。このフラグは 32bit アプリケーションではデフォルトで OFF で、64bit アプリケーションでは ON です。

 ポインタと 32bit 整数のキャストを山盛り使っていてソース改変しようにも収集がつかないような状態のアプリケーションを 64bit に移行しなければならない場合には、このフラグを OFF にする事によって、アプリケーションに割り当てられる仮想メモリを 2GB 以下に制限します。「こうすれば、キャストでポインタのアドレスが削れても大抵オッケーだよね...HaHaHa」 という、後ろ向きな対処方法です。

 Delphi の 64bit コンパイラ (/ リンカ) では、このフラグをオフにする事はできませんが、Delphi の 32bit コンパイラ (/ リンカ) では、逆にこのフラグをオンにする事ができます。詳細は後のトピック "WOW64 環境でのパフォーマンスとメモリ消費量" で解説します。

整列エラー [ MSDN ]

 ワードアライメント等の話だと思いますが、Delphi ではそんなに意識する場面はないはずです。

プロセスの相互運用性 [ MSDN ]

 64bit Windows では一般のアプリケーションが利用できる "サンク" 機構がないので、64bit DLL を Win32 アプリケーションから利用したり、32bit DLL を Win64 アプリケーションから利用する事はできません。

 このトピックでは 32bit / 64bit プロセス間のリモート プロシージャ コール (RPC) が可能な事を応用して、DLL を相互運用する方法が説明されています。DLL を アウトプロセス COM サーバー でラッピングしたものを作れば、擬似的に DLL を相互運用できるという事のようです。

 こんな感じのラッパー (サンプルは UNLHA32.DLL のラッパー) を作れば、

unit LHAWrap;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  ComObj, ActiveX, LHASVR_TLB, StdVcl, Windows, Classes, SysUtils, Dialogs, Forms;

type
  TLHAWrapper = class(TAutoObject, ILHAWrapper)
  protected
    procedure Execute(const cmd: WideString); stdcall;
  end;

implementation

uses
  ComServ, FileCtrl;

function Unlha(hwnd: HWND; lpszCmdLine: PAnsiChar; lpszOutput: PAnsiChar;
  wSize: DWORD): Integer; stdcallexternal 'UNLHA32.DLL';
function UnlhaGetRunning: Boolean; stdcallexternal 'UNLHA32.DLL';

procedure TLHAWrapper.Execute(const cmd: WideString);
var
  buf: array [0..MAX_PATH * 2 + 15 - 1of AnsiChar;
  dCmd: AnsiString;
begin
  if UnlhaGetRunning then
    ShowMessage('"UNLHA32.DLL" is already runnning...')
  else
    begin
      dCmd := AnsiString(cmd);
      Unlha(Application.Handle, PAnsiChar(dCmd), buf, SizeOf(buf));
    end;
end;

initialization
  TAutoObjectFactory.Create(ComServer, TLHAWrapper, Class_LHAWrapper,
    ciMultiInstance, tmFree);
end.

 こんな感じで 32bit DLL の機能を 64bit アプリケーションから利用できるという事になります。

uses
  ..., LHASVR_TLB;

procedure TForm1.Button1Click(Sender: TObject);
var
  LHAWrapper: ILHAWrapper;
begin
  LHAWrapper := CoLHAWrapper.Create;
  LHAWrapper.Execute(Edit1.Text);
end;

ボタンを押すと動画が再生されます。

 ...が、ShellExecute() は 32bit アプリを起動できるのですから、ごく簡単な呼び出しの DLL であれば単純に EXE でラッピングして引数をコマンドラインパラメータで渡せばいいような気もします。

ドライバ [ MSDN ]

 このトピックは省略します。


64 ビット互換インターフェイスの設計 [ MSDN ]

 このセクションは省略します。

既存のインターフェイスの変更 [ MSDN ]

 このトピックは省略します。

情報の非公開の回避 [ MSDN ]

 このトピックは省略します。

ポリモーフィズムの回避 [ MSDN ]

 このトピックは省略します。

IDL ファイルにおける新しいデータ型の使用 [ MSDN ]

 このトピックは省略します。

64 ビット版の Windows 用アプリケーションの準備 [ MSDN ]

 このトピックは省略します。


32 ビット アプリケーションの実行 [ MSDN ]

 Win32 アプリケーションが WOW64 で動作しているのかどうかを判定するには以下のユニットを利用します。

unit WOW64;

interface

{$IFDEF MSWINDOWS}
uses
  Windows;
{$ENDIF}

function IsWOW64: boolean;

{$IFDEF MSWINDOWS}
{$IFDEF WIN32}
var
  DLLWnd: THandle;
  IsWow64Process: function(hProcess: THandle; var Wow64Process: Boolean): Boolean stdcall;
{$ENDIF}
{$ENDIF}

implementation

function IsWOW64: Boolean;
{$IFDEF MSWINDOWS}
{$IFDEF WIN32}
var
  Flg: Boolean;
{$ENDIF}
{$ENDIF}
begin
  result := False;
  {$IFDEF MSWINDOWS}
  {$IFDEF WIN32}
  DLLWnd := LoadLibrary('Kernel32.dll');
  if DLLWnd > 0 then
    begin
      try
        @IsWow64Process := GetProcAddress(DLLWnd, 'IsWow64Process');
        if @IsWow64Process <> nil then
          if IsWow64Process(GetCurrentProcess, Flg) then
            result := Flg;
      finally
        FreeLibrary(DLLWnd);
      end;
    end;
  {$ENDIF}
  {$ENDIF}
end;
end.

 WOW64 上で動作していれば IsWOW64() は True を返します。

procedure TForm1.Button1Click(Sender: TObject);
begin
  if IsWOW64 then
    ShowMessage('WOW64')
  else
    ShowMessage('Native Win32');
end;

WOW64 環境でのパフォーマンスとメモリ消費量 [ MSDN ]

 基本的にアプリケーション側からはどうにもならない話が多いのですが、知識として知っておくことは無駄ではないと思います。

 IMAGE_FILE_LARGE_ADDRESS_AWARE の所に "画像" という記述がありますが、画像とは全く関係なく、またもや "PE ヘッダ" の話です。

uses
  Windows,
  ...

{$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE}

 このように *.dpr に記述すると WOW64 上で動作する 32bit アプリケーションに 4 GB の仮想アドレス領域が割り当てられる という事です。指定しなかった場合の仮想アドレス領域は 2GB です。なお、{$SetPEFlags} コンパイラ指令は少なくとも Delphi 7 またはそれ以降で利用可能です。但し、定数が Windows.pas に定義されていない場合があります (64bit Windows 登場以前の環境ですから)。

 PE ヘッダのフラグに関する Delphi のトピックとしては DocWiki の "PE (portable executable) header flags (Delphi) (DocWiki)" に詳細があります。

WOW64 実装の詳細 [ MSDN ]

 Delphi で環境変数を参照する事はあまりないかもしれませんが、

procedure TForm1.Button1Click(Sender: TObject);
var
  Env: String;
begin
  Env := GetEnvironmentVariable('PROCESSOR_ARCHITECTURE');
  ShowMessage(Env);
end;

 GetEnvironmentVariable() で取得する事ができます。

 "64 ビット プロセス -> 32 ビット プロセス (WOW64)" または "32 ビット プロセス (WOW64) -> 64 ビット プロセス" のような WOW64 を介した処理が行われる場合にはその生成されたプロセスに対して異なる環境変数が設定されるという事になります。

レジストリ リダイレクタ [ MSDN ]

 OS の仕様なので、Delphi 製アプリケーションにも関連します。

ファイル システム リダイレクタ [ MSDN ]

 OS の仕様なので、Delphi 製アプリケーションにも関連します。

メモリ管理 [ MSDN ]

 Delphi では意識する必要はないと思います。

プロセッサの関係 [ MSDN ]

 CPU のコアの数を取得したり設定する際の注意事項です。普段はあまり意識する必要はないと思いますが、例えば BDE を利用するアプリケーションの場合、プロセッサの数を制限しないと問題が発生する事があります。詳細は "53.今更ながら BDE (Borland Database Engine)" のトラブルシューティングの項目を参照して下さい。

プロセス間通信 [ MSDN ]

 32bit アプリケーションと 64bit アプリケーション間で情報をやりとりするための方法論です。OS の仕様なので、Delphi 製アプリケーションにも関連します。

アプリケーションのインストール [ MSDN ]

 Delphi 付属の InstallAware は MSI ベースのインストーラなので、InstallAware を使う限りあまり意識する必要はないでしょう。InstallAware の使い方については "52.InstallAware" を参考にして下さい。

WOW64 のデバッグ [ MSDN ]

 基本的に、デバッガが Win64 アプリケーションの場合の話ですので、Delphi のデバッガ (Win32 アプリケーション) には当てはまりません。64bit ネイティブデバッガ (Win64 アプリケーション) で WOW64 上の Win32 アプリケーションをデバッグする手法 ("環境" の説明の逆) が記載されています。


移行に関するヒント [ MSDN ]

 Delphi に関する資料は DocWiki にあります。

 ここに書かれていない 64bit アプリケーションでの制限事項としては、  というものがあります。

移植に関する一般的なガイドライン [ MSDN ]

 Delphi にもほぼそのまま当てはまります。大量のメモリ (4GB 以上) を積んでみてバグが出ないかテストする事は必要だと思います。

64 ビット値の格納 [ MSDN ]

 ULONG_PTR は Delphi の場合、NativeUInt のエイリアスとなっています。

一般的なコンパイラ エラー [ MSDN ]

 Extended 型絡みのメッセージに "H1066 Extended 型の浮動小数値の精度が失われます。Double 型に丸めました" というものがあります。Delphi の場合、64bit 絡みのメッセージは "ポインタのキャスト" によるものか、"インラインアセンブラ" によるものが大半を占めると思われます。

その他の考慮事項 [ MSDN ]

 条件コンパイルに関しては "3.異なるバージョンの Delphi でソースを共有する" を参照して下さい。


参考資料


 BACK