【Delphi】WOW64 のリダイレクトを回避するには？

# はじめに
WOW64 環境下 (64bit Windows で 32bit アプリケーションを実行) においては、特定の条件でパスがリダイレクトされる事があります。

 - [ファイル システム リダイレクター (learn.microsoft.com)](https://learn.microsoft.com/ja-jp/windows/win32/winprog64/file-system-redirector)

このため、明らかにパスが存在するのに「ファイルが存在しません」と言われる事があります。


# リダイレクトの回避

WOW64 によるリダイレクトを回避するには、`Wow64DisableWow64FsRedirection()` API と `Wow64RevertWow64FsRedirection()` API のペアを使います。

以前は `Wow64EnableWow64FsRedirection()` という単一の API でリダイレクトのオンオフを切り替えていましたが、これだとネストさせる事ができないので、現行の仕様に変更された模様です。

**See also:**

 - [Wow64DisableWow64FsRedirection() (learn.microsoft.com)](https://learn.microsoft.com/ja-jp/windows/win32/api/wow64apiset/nf-wow64apiset-wow64disablewow64fsredirection)
 - [Wow64RevertWow64FsRedirection() (learn.microsoft.com)](https://learn.microsoft.com/ja-jp/windows/win32/api/wow64apiset/nf-wow64apiset-wow64revertwow64fsredirection)
 - [Wow64EnableWow64FsRedirection() (learn.microsoft.com)](https://learn.microsoft.com/ja-jp/windows/win32/api/wow64apiset/nf-wow64apiset-wow64enablewow64fsredirection)

## コード

Delphi で WOW64 によるリダイレクトを回避するためのルーチンを用意しました。先述の API のラッパーです。

```pascal:uWOW64.pas
unit uWOW64;

{$IFDEF MSWINDOWS}{$IFDEF WIN32}
{$DEFINE TARGET_WINDOWS}
{$ENDIF}{$ENDIF}

interface

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

function IsWOW64: Boolean;
function DisableWow64FsRedirection(var OldValue: LongBool): LongBool;
function RevertWow64FsRedirection(const OldValue: LongBool): LongBool;

{$IFDEF TARGET_WINDOWS}
var
  IsWow64Process: function(hProcess: THandle; var Wow64Process: BOOL): BOOL stdcall;
  Wow64DisableWow64FsRedirection: function (out OldValue: LongBool): LongBool; stdcall;
  Wow64RevertWow64FsRedirection: function (const OldValue: LongBool): LongBool; stdcall;
  Wow64EnableWow64FsRedirection: function(const Wow64FsEnableRedirection: LongBool): LongBool; stdcall;
{$ENDIF}

implementation

function IsWOW64: Boolean;
{$IFDEF TARGET_WINDOWS}
var
  Flg: BOOL;
begin
  result := False;
  @IsWow64Process := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'IsWow64Process');
  if @IsWow64Process <> nil then
    if IsWow64Process(GetCurrentProcess, Flg) then
      result := Flg;
{$ELSE}
begin
  result := False;
{$ENDIF}
end;

function DisableWow64FsRedirection(var OldValue: LongBool): LongBool;
begin
  result := False;
  if not IsWOW64 then
    Exit;
  {$IFDEF TARGET_WINDOWS}
  @Wow64DisableWow64FsRedirection := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'Wow64DisableWow64FsRedirection');
  if @Wow64DisableWow64FsRedirection <> nil then
    result := Wow64DisableWow64FsRedirection(OldValue)
  else
    begin
      @Wow64EnableWow64FsRedirection := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'Wow64EnableWow64FsRedirection');
      if @Wow64EnableWow64FsRedirection <> nil then
        result := Wow64EnableWow64FsRedirection(False);
    end;
  {$ENDIF}
end;

function RevertWow64FsRedirection(const OldValue: LongBool): LongBool;
begin
  result := False;
  if not IsWOW64 then
    Exit;
  {$IFDEF TARGET_WINDOWS}
  @Wow64RevertWow64FsRedirection := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'Wow64RevertWow64FsRedirection');
  if @Wow64RevertWow64FsRedirection <> nil then
    result := Wow64RevertWow64FsRedirection(OldValue)
  else
    begin
      @Wow64EnableWow64FsRedirection := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'Wow64EnableWow64FsRedirection');
      if @Wow64EnableWow64FsRedirection <> nil then
        result := Wow64EnableWow64FsRedirection(True);
    end;
  {$ENDIF}
end;

end.
```

あるのかどうかはわかりませんが `Wow64EnableWow64FsRedirection()` しか使えない環境にも対応しています。但し、この場合にはネストができません。

## 使い方

使い方は、**uses** に uWOW64 を記述して、`DisableWow64FsRedirection()` と `RevertWow64FsRedirection()` で括るだけです。ステートを保存するための OldValue (LongBool 型) を引数として渡す必要があります。ネストする際には別の変数を用意してください。

`DisableWow64FsRedirection()` と `RevertWow64FsRedirection()` は WOW64 環境下でしか動作しないので、32bit / 64bit 両用のコードであっても条件コンパイル用の指令を記述する必要はありません。

```pascal
uses
  ..., uWOW64;


var
  OldValue: LongBool;

  ...

  DisableWow64FsRedirection(OldValue);
  try
    // リダイレクトが予想される処理 (ShellExecute など)
  finally
    RevertWow64FsRedirection(OldValue);
  end;
```

## リダイレクトの具体的な例
Windows 10/11 に環境に WSL/WSL2 環境があれば、\[ファイル名を指定して実行\] (〔Win〕+〔R〕) で `wsl.exe ~` を実行してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/0863737f-cad8-7aea-1df3-006fe2dbe0a0.png)
普通に WSL/WSL2 が起動したかと思います。では、32bit アプリケーションで次のコードを試してみてください。 

```pascal
uses
  ..., Winapi.ShellAPI;

  ShellExecute(0, 'open', 'wsl.exe' , '~', nil, SW_SHOWNORMAL);
```

WSL/WSL2 は起動しなかったと思います。64bit アプリケーションでは正しく起動できますし、`wsl.exe` を `explorer.exe` にすれば Explorer はちゃんと起動するハズです。

次のようにすれば WSL/WSL2 を 32bit アプリケーションから起動できるようになります。

```pascal
uses
  ..., Winapi.ShellAPI, uWOW64;

var
  OldValue: LongBool;

  ...

  DisableWow64FsRedirection(OldValue);
  try
    ShellExecute(0, 'open', 'wsl.exe' , '~', nil, SW_SHOWNORMAL);
  finally
    RevertWow64FsRedirection(OldValue);
  end;
```

# おわりに

そういえば、`FMX.Helpers.Win` 名前空間 [^1] には `TWow64Redirection` というラッパークラスがあるのですが、何故 `WinApi` 名前空間ではないのでしょうね？VCL / Firemonkey 問わずに使えそうな気がするのですが？

**See also:**

 - [32 ビット アプリケーションの実行 (learn.microsoft.com)](https://learn.microsoft.com/ja-jp/windows/win32/winprog64/running-32-bit-applications)
 - [WOW64 (Wikipedia)](https://ja.wikipedia.org/wiki/WOW64)

[^1]: 2023/04/18 現在ドキュメントがありません。