search
LoginSignup
16
Help us understand the problem. What are the problem?

posted at

updated at

[解決] Windows 11 22H2 で〔半角/全角〕キーを押すとアプリケーションがクラッシュする

はじめに

この問題は 2022/11/30 (日本時間) リリースの Windows 11 22H2 Moment 2 (KB5020044 / Build 22621.900) で解決しました。

Windows 11 22H2 において、特定のアプリケーションが〔半角/全角〕キーを押しただけでクラッシュするようです。
image.png
具体的には 浮動小数点例外 (Floating-point exception) を使うようになっているアプリケーションがクラッシュします。Delphi および C++Builder 製アプリの多くがこれに該当します。

後述しますが、Delphi / C++Builder 固有の問題ではありません。

当初は認識していませんでしたが、テキストエディタ『Mery』の作者である Kuro さんのツイートでこの問題を知る事となりました。

『TeraPad』でも同様の問題が発生しているようです。

『Clibor』でも同様の問題が発生しているようです。

Delphi で問題を再現するのは簡単で、フォームにエディットボックスを貼ったアプリケーションを用意するだけです。実行して〔半角/全角〕を押すと浮動小数点例外でアプリケーションがクラッシュします。
image.png
物理キーボードだけではなく、スクリーンキーボード で〔半角/全角〕キーを押してもアプリケーションはクラッシュします。
image.png
〔半角/全角〕キーを押しただけで浮動小数点例外が発生するのは意図された動作だとは思えません。

Windows 10 22H2 ではこの問題は発生しません。

See also:

回避方法

Windows Update を実施してください。以下にあるのは問題が発生する環境での回避方法です。

いくつか回避方法があるようです。

〔半角/全角〕キーを押さずに…例えばタスクバーの Microsoft IME アイコンから切り替えるとアプリケーションは落ちませんが現実的な回避方法ではありません。
image.png
...と思っていたら、Microsoft の回避方法 (2022/11/18 付) は

Workaround: Changing input mode by clicking the IME mode icon in the taskbar should not trigger this issue. This issue occurs only when using keyboard shortcuts.

「タスクバーから切り替えれ!」 ですって。正気ですか?

■ 以前のバージョンの Microsoft IME を使う

[設定] から [時刻と言語 > 言語と地域 > Microsoft IME > 全般] と辿り、[以前のバージョンの Microsoft IME を使う]オンにします。
image.png
Windows 10 以降、新しい Microsoft IME の挙動がしばしばおかしくなるので、私は [以前のバージョンの Microsoft IME を使う] をオンにして使っており、本件に気付きませんでした。

■ Microsoft IME 以外の IME を使う

ジャストシステムの『ATOK』や Google の『Google 日本語入力』を使う事でも問題を回避できます。多くの場合で [以前のバージョンの Microsoft IME を使う] と同じ効果が得られます。

See also:

■ IME On / Off のキーとして〔Ctrl〕+〔Space〕を有効にする

〔Ctrl〕+〔Space〕を変換キーとして追加し、[設定] から 日本語 IME 設定 を検索して出すか、タスクバーの Microsoft IME アイコンを右クリックして [設定] を開きます。

設定のキャプションは [時刻と言語 > 言語と地域 > Microsoft IME] となっていると思いますので [キーとタッチのカスタマイズ] をクリックします。
image.png
各キー / キーの組み合わせに好みの機能を割り当てます にチェックを入れ、Ctrl + SpaceIME-オン/オフ を割り当てます。
image.png
〔Ctrl〕+〔Space〕で IME を On / Off する分には問題は発生しないようです。ただ、〔半角/全角〕キーを押せばやっぱりアプリケーションはクラッシュします。

〔半角/全角〕を無効にする手段は Windows 11 22H2 の設定に用意されていないようですし、以前の Microsoft IME ともキー設定を共有していないようなので、以前の Microsoft IME でキー設定を行って新しい Microsoft IME に戻すという手も使えないようです。

■ オプションの診断データを送信しない

[設定] から [プライバシーとセキュリティ > 診断とフィードバック] と辿り、[オプションの診断データを送信する]オフにします。
image.png

IME の変換はトリガーに過ぎず、不具合の本当の原因はオプションの診断データ送信なのではないかという気も…。

■ アプリケーション側で浮動小数点例外を使わないようにする

Delphi / C++Builder はデフォルトでいくつかの浮動小数点例外を利用するようになっているため、多くの Delphi / C++Builder 製アプリケーションが影響を受けます。

ARM 系 CPU で浮動小数点例外がサポートされていない関係だと思われますが、FireMonkey アプリケーションではデフォルトで浮動小数点例外を使わないようになっており、問題が発生するのは主に VCL アプリケーションという事になります。

加えて、最近の Delphi / C++Builder でコンパイルされたものだけが対象という訳ではなく、古い Delphi / C++Builder でコンパイルされたアプリケーションでも発生しますし、浮動小数点例外を使うようになっていれば、言語を問わず同様の問題が発生すると思われます。

Visual C++ でもこの問題は発生します。ただし、デフォルトではすべての例外がマスクされているため、明示的に浮動小数点例外を使うコードを記述しない限り問題は起きません。ダイアログにエディットボックスを貼り付けて、次のようなコードを記述すると問題を再現できます。

#include "float.h"

...

    unsigned int control_word;
    _controlfp_s(&control_word, _EM_DENORMAL | _EM_UNDERFLOW | _EM_INEXACT, _MCW_EM);

つまり Windows 11 22H2 は 新しい IME を使う限り浮動小数点例外を扱えない という問題を抱えている事になります。

Delphi における対策例

Delphi だと次のコードで浮動小数点例外を使わないようにできます。

uses
  ..., System.Math;
...

  SetExceptionMask(exAllArithmeticExceptions);

古い Delphi だと次のようなコードになります。

uses
  ..., Math;
...

  SetExceptionMask([exInvalidOp..exPrecision]);

浮動小数点例外の種類は列挙型 TArithmeticException にて定義されています。SetExceptionMask() では利用しない (マスクする) 浮動小数点例外を指定します。

exAllArithmeticExceptions は次のすべてのフラグの集合です。

意味
exInvalidOp 無効な演算を行おうとした。
exDenormalized 数値が非ゼロとして格納できるサイズより小さいサイズに切り詰められた。数値が非正規化された。
exZeroDivide ゼロで除算しようとした。
exOverflow 数値がサポートされている正の上限値を超えた。
exUnderflow 数値が、サポートされている負の下限値を超えた。
exPrecision 数値が精度の桁数を超えた。

SetExceptionMask() は Delphi 6 以降で実装されています。それよりも前のバージョンの Delphi 製アプリケーションで問題が発生する場合には Set8087CW() を使って同等の処理を行う必要があります。

Delphi 8 等 (Delphi for .NET) では {$FINITEFLOAT OFF} コンパイラ指令で浮動小数点例外を使わないようにできますが…もう誰も使っていないでしょうね。

See also:

もうちょっと詳しく

浮動小数点例外を全体的にオフにしたい場合は次のようにグローバル変数へデフォルトのマスクを格納しておき、

uses
  ..., System.Math;

...

var
  BackupMask: TArithmeticExceptions;

  ...
  BackupMask := SetExceptionMask(exAllArithmeticExceptions);

浮動小数点例外を使いたい所 (0 除算を try except で処理している所とか) でバックアップしたマスクを設定するといいかと思います。浮動小数点例外は EMathError で捕捉できます。

  SetExceptionMask(BackupMask);
  try
    try
      // 浮動小数点例外が出るかもしれないコード
    except
      on E: EMathError do
        begin
          // 浮動小数点例外が出た時の処理
        end;
    end;
  finally
    SetExceptionMask(exAllArithmeticExceptions);
  end;

VCL がマスクを変更する事があるため、次のコードの方が適当かもしれません。

  var CurrentMask := SetExceptionMask(BackupMask);
  try
    try
      // 浮動小数点例外が出るかもしれないコード
    except
      on E: EMathError do
        begin
          // 浮動小数点例外が出た時の処理
        end;
    end;
  finally
    SetExceptionMask(CurrentMask);
  end;

スレッドセーフではないので、スレッドを使っている時には注意が必要です。

Delphi でのデフォルトのマスクは [exDenormalized, exUnderflow, exPrecision] です。つまり exAllArithmeticExceptions - DefaultExceptionFlags です。

See also:

〔半角/全角〕キーを押しても落ちないけど?

エディットボックス等の入力項目が存在しないのであれば落ちません。

This issue is observed for applications which load certain components of the Text Services Framework (TSF).

「TSF の特定のコンポーネントをロードする」...つまりは入力項目が存在するアプリケーションで問題が発生するという事なのだろうと思われます。

エディットボックス等の入力項目が存在しない、一見この問題の影響を受けないようなアプリケーションであっても、ファイルオープン/セーブダイアログが使われているとファイル名に日本語名を入力しようとしてクラッシュしますのでご注意ください。

Delphi アプリケーションの場合、次のユニットを uses に加えていると浮動小数点例外が使われなくなるため、〔半角/全角〕キーを押しても落ちません。

ユニット バージョン
SHDocVw XE4 以降
(ユニット自体は Delphi 5 から存在)
System.Win.InternetExplorer
(SHDocVw の代替)
XE8 以降
Vcl.Edge 10.4 Sydney 以降
Winapi.EdgeUtils 11 Alexandria 以降
initialization
  FSetExceptMask(femALLEXCEPT);

FSetExceptMask() は XE4 以降で実装されています。femALLEXCEPT は次のすべてのフラグの集合です。

意味
femINEXACT 不正確な演算結果。
femUNDERFLOW 数値が、サポートされている負の下限値を超えた。
femOVERFLOW 数値がサポートされている正の上限値を超えた。
femDIVBYZERO ゼロで除算しようとした。
femINVALID 無効な演算を行おうとした。

当該ユニットが uses に加えられると、意図せず浮動小数点例外の処理が働かなくなる事がある という、また別の心配事も。

See also:

例外処理を伴わない 0 除算の処理

次のような浮動小数点例外を利用したコードがあったとします。0 除算になったら c の値を 1 にするコードです。

program FP_Test;
{$APPTYPE CONSOLE}
uses
  System.SysUtils, System.Math;

var
  a, b, c: Double;
begin
  try
    a := 100;
    b := 0;
    c := a / b;
  except
    on E: EMathError do
      begin
        c := 1;
      end;
  end;
  Writeln(c);
end.

浮動小数点例外で exZeroDivide をマスクした場合、EZeroDivide は発生しなくなるため、別の方法で 0 除算を検出しなくてはなりません。

program FP_Test;
{$APPTYPE CONSOLE}
uses
  System.SysUtils,
  System.Math;

var
  a, b, c: Double;
begin
  SetExceptionMask(exAllArithmeticExceptions);

  a := 100;
  b := 0;
  c := a / b;
  if IsInfinite(c) or IsNan(c) then 
//if c.IsInfinity or c.IsNan then
    c := 1;

  Writeln(c);
end.

0 除算の結果は Infinity になりますが、除数も被除数も 0 の場合には NaN となるため、IsInfinite()IsNan() の両方で判定する必要があります。ただ…

program FP_Test;
{$APPTYPE CONSOLE}
uses
  System.SysUtils,
  System.Math;

var
  a, b, c: Double;
begin
  SetExceptionMask(exAllArithmeticExceptions);

  a := 100;
  b := 0;
  if b = 0 then // または if IsZero(b) then
    c := 1
  else
    c := a / b;

  Writeln(c);
end.

「除数が 0 かどうか?」を先に判定して計算するのが筋だとは思います。どちらがコード書き換えの手間が掛からないか、要検討ですね。

See also:

おわりに

アプリケーションがクラッシュする原因は、「Windows 11 (22H2) で新しい Microsoft IME を使っていてオプションの診断データを送信するようになっていると、〔半角/全角〕キーが押されて IME が ON になったタイミングで浮動小数点例外が発生する」 という Microsoft IME のバグだと思われます。

■ Embaracadero の情報

Delphi / C++Builder の開発元である Embaracadero の Support Wiki にも本件に関する情報があります (2022/10/06 付)。

「Delphi 7 から~」 とありますが、Embarcadero が Delphi 7 以降でしかテストできていないだけで、実際には Delphi 2 ~ Delphi 6 (C++Builder 1 ~ 6) 製のアプリケーションもクラッシュします。

■ Microsoft の情報

一般チャネルで 2022/11/30 (日本時間) にリリースされた Windows 11 22H2 Moment 2 (KB5020044 / Build 22621.900) でこの問題は解決しました。

See also:

■ 浮動小数点例外の修正が必要かどうかの判定

浮動小数点例外で問題が発生する環境は次の通りです。

  • Windows 11 (Version 10.0、Build 22000 以降)
  • 22H2 (Build 22621)
  • UBR (Update Build Revision) が 521 以上 900 未満

これを判定するコードを書いてみました。そこそこ古い Delphi でもコンパイルが通るようになっています。

uses
  ..., Winapi.Windows;

...

function IsNeedDisableFPE: Boolean;
var
  sFileName: string;
  wUBR: WORD;
  dwInfoSize, dwHandle: DWORD;
  pInfo: Pointer;
  pFileInfo: PVSFixedFileInfo;
  arrBuf: array [0..MAX_PATH-1] of Char;
begin
  result := False;
  if (Win32Platform     = VER_PLATFORM_WIN32_NT) and
     (Win32MajorVersion = 10) and
     (Win32MinorVersion = 0) and
     (Win32BuildNumber  = 22621) then
  begin
    GetSystemDirectory(@arrBuf, SizeOf(arrBuf));
    sFileName := IncludeTrailingPathDelimiter(StrPas(arrBuf)) + 'NTOSKRNL.EXE';
    dwInfoSize := GetFileVersionInfoSize(PChar(sFileName), dwHandle);
    if dwInfoSize > 0 then
    begin
      GetMem(pInfo, dwInfoSize);
      try
        GetFileVersionInfo(PChar(sFileName), 0, dwInfoSize, pInfo);
        VerQueryValue(pInfo, PathDelim, Pointer(pFileInfo), dwInfoSize);
        wUBR := LoWord(pFileInfo.dwFileVersionLS);
        result := (wUBR >= 521) and (wUBR < 900);
      finally
        FreeMem(pInfo, dwInfoSize);
      end;
    end;
  end;
end;

UBR の値を Win32 API で取得する方法はなさそうです。レジストリ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion にある UBR から持ってきてもいいのですが、コード例のように NTOSKRNL.EXE のファイルバージョンを確認した方が確実な気がします。
image.png
問題の出る環境を判定して浮動小数点例外のマスクを行えば、最低限の影響で済みます。

  if IsNeedDisableFPE then
    SetExceptionMask(exAllArithmeticExceptions);

…ただ、この問題が Microsoft によって解決されれば無用なコードではあるので、「Windows 11 で 22H2 を適用してしまったのなら、Windows の設定変更でやり過ごして!」って、ドキュメントやヘルプに書くのが現実的な解決法のような気はしますね。

See also:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
16
Help us understand the problem. What are the problem?