はじめに
『Windows Millennium Edition (Windows Me)』というコンシューマー向け Windows が前世紀 (2000 年) に発売されました。
巷では散々な評価をされているのですが、原因は Windows Me そのものにあるのではなく、
- 不要なソフトが常駐している
- メーカー製 PC のメモリ搭載量が間違っている
- メーカー製の不要なプリインストールソフト群
自作 PC でクリーンインストールした場合、前バージョンの Windows 98 Second Edition と比べてもそんなに悪いものではありませんでした。
Windows Me 発売の前年に企業向けとして Windows 2000 が、翌年には Windows XP が発売されていますが、NT 系の OS と比べるのはフェアじゃない気がします。
MS-DOS のリアルモードは確かに動作しませんでしたが、リアルモードが必要な人は MS-DOS (Windows 3.1) とデュアルブートにした方が幸せになれた気がします。
Windows Me を不安定にするプログラムを作る
さて、Windows Me を含めた Win9x にはメモリとは別にシステムリソースによる制限があり、このシステムリソースを食い潰すと、メモリが空いていてもシステムが不安定になりました。
リソース
リソースにはウィンドウの情報を保持する USER リソース と、グラフィックス関連の情報を保持する GDI リソースがあり、そのどちらか少ない方がシステムリソースとして扱われました。
リソースの空きはリソースメーター (rscmtr.exe) で確認できました。
Windows Me を不安定にするプログラム
エディットボックスやボタン等のコントロールもウインドウの一種なので、フォームに大量のコントロールを貼り付けるとリソース不足を引き起こしました。
業務系のアプリには、業種によってはとんでもない数の入力項目があったりします。入力項目を分割できる場合にはページを分けてやればいいのですが「1画面で!」と言われたらそういう訳にもいきません。
Windows Me を不安定にするには、フォームに大量にコントロールを貼り付けたアプリを作ればいいのですが、どこまで貼り付ければいいのか加減が分からないので、フォームにコントロールを動的作成するプログラムを作る事にします。
コードとフォーム
Win9x で動作するプログラムを作るために Delphi 7 を使います。
最新版の Delphi を購入すると、旧バージョンの一つとして Delphi 7 を入手できます。
- 【Delphi】Delphi 7 について (Qiita)
- Delphi で生成された EXE が動作する Windows のバージョン (Qiita)
- Delphi 旧バージョン (Embarcadero)
フォームにボタンを貼って、
ボタンを押した時のイベントハンドラで EditBox を 10 個ずつ動的作成し、ランダムな位置に配置します。
procedure TForm1.Button1Click(Sender: TObject);
var
Edit: TEdit;
i: Integer;
begin
for i:=1 to 10 do
begin
// EditBox を新規作成
Edit := TEdit.Create(Self);
Edit.Parent := Self;
Edit.Left := Random(Self.ClientWidth - Edit.Width );
Edit.Top := Random(Self.ClientHeight - Edit.Height);
end;
// ボタンを最前面に持ってくる
Button1.BringToFront;
end;
実行
実行してボタンを押しまくるとリソースが減って行き、ついには Windows Me が不安定になります。
アプリケーションを閉じるとリソースの空きが増えます。
現在の NT 系 Windows でボタンを押しまくってもそう簡単には不安定にはなりません (できるのか?)。
リソースの空きを取得する
Windows 9x のリソースメーターのようにリソースの空きを知りたい場合には、GetFreeSystemResources() API を使います。
16bit アプリケーションの場合は USER.EXE に格納されているものをそのまま使えばいいだけですが、32bit アプリケーションから 16bit モジュールに含まれる API を使うには 16bit サンク (Thunk) という技術が必要になります。
See also:
サンクを使わずにリソースの空きを取得する
実際の所、リソースメーターは RSRC32.DLL の非公開 API (GetFreeSystemResources() のラッパー) を使っているようで、これを使えばサンクを使わずにリソースの空きを取得する事が可能です。RSRC32.DLL は Win9x 環境にしか存在しないため、動的ロードする必要があります。
型や変数を宣言・定義して、
type
TGetFreeSystemResources = function(ResType: UINT): UINT; stdcall;
const
GFSR_SYSTEMRESOURCES = 0; // システムリソース
GFSR_GDIRESOURCES = 1; // GDI リソース
GFSR_USERRESOURCES = 2; // USER リソース
var
Lib: HMODULE;
GetFreeSystemResources: TGetFreeSystemResources;
GetFreeSystemResources() を使う準備として DLL を動的ロードします。ラッパー API のエクスポート名は _MyGetFreeSystemResources32@4 です。
// DLL のロード
Lib := LoadLibrary('RSRC32.DLL');
if Lib <> 0 then
@GetFreeSystemResources := GetProcAddress(Lib, '_MyGetFreeSystemResources32@4');
使い終わったら DLL をアンロードします。
// DLL のアンロード
if Lib <> 0 then
FreeLibrary(Lib);
使い方は次のようになります。システムリソースを取得しています。
var
SysRes: Integer;
begin
// システムリソース使用率を取得
SysRes := 0;
if Assigned(GetFreeSystemResources) then
SysRes := GetFreeSystemResources(GFSR_SYSTEMRESOURCES);
// Caption を更新
Caption := Format('System Resource Test (%d%%)', [SysRes]);
end;
大量のコントロールが必要な場面でリソース不足を解決するには?
そうそう、これを話していませんでした。
大量の入力項目のある業種の業務系アプリを作る事があり、視認性の観点から入力項目は帳票と同じになるようにする事を求められる事がありました。
ざっくりこんな感じの奴ですね。Windows NT4.0 / 2000 等で開発しているので平気でこんなの作るのですが、いざお客さんトコに持って行くと Win9x な PC があってリソース不足で落ちる訳ですよ。で、どうしたかと言うと、
グレーの部分をすべてグラフィックで描画して、アクティブなコントロールだけ本物にしました。これで数百個あったコントロールは数十個にまで減らせました。
画面分割が許されるなら画面分割するのもアリですが、当時の環境ですとコントロールが多いと動的作成にも時間が掛かるので、結局この方法に落ち着きました。
今はこんな事やらなくてよくなったので、デヴィッド・カトラー氏には感謝ですね。
おわりに
余談になりますが、64bit Windows アプリケーションから 32bit DLL を使う32bit サンクなんてものはありません。
32bit DLL をアウトプロセス COM サーバーでラッピングしたもの (EXE) を作れば、64bit Windows アプリケーションから 32bit DLL を間接的に利用する事が可能です...使える場面は限定的かもしれませんが。
32bit の COM サーバーは 64bit アプリケーションから呼び出せますし、逆に 64bit の COM サーバーは 32bit アプリケーションから呼び出せます。
そういえば、この記事を書いている時点での最新版である Delphi 13.1 Florence には Windows on Arm をターゲットとしたコンパイラが搭載されていて、Arm64EC バイナリを吐きます。
EXE は Arm ネイティブで動作し、DLL は x64 のものが使えるというハイブリッドですが、これもサンクの一種です。
See also:








