ウィンドウをきっかけに Windows の内部の仕組みを探る (前半)フレームワークからDLLまでのスケール
しかし,どうしてウィンドウを表示させる事が可能なのだろうか?
何気なく表示されるウィンドウの「実体」は,何なのだろうか。
ウィンドウの正体を追いかけながら,Windows の OS 内部でアプリケーションが実行される仕組みを見てみる。
ウィンドウはどうやって生成されるのか?
書籍「30日でできる!OS自作入門」の中にこのような一節がある。
(バイナリエディタは)究極の最終兵器で,つまり,バイナリエディタで作れないファイルはないのです(おぉ!)。
お店で売っているあのソフトがほしいなぁ,でも買えないなあ,なんていうときは,家でひたすらバイナリエディタで入力していけばいいんです。
これだけでお店に並んでいたものとまったく違いのないものが自分で作れちゃうんです。
「30日でできる!OS自作入門」(川合秀実著)
(突っ込みどころはあるが)これは真実だ。
ところで,Windows上で「ウィンドウを表示させる」という処理も,バイナリエディタだけで作れるのだろうか。
CUI・GUIに関係なく,.exe ファイルの中身は必ずバイナリ(機械語)だ。
だからGUIアプリケーションの場合,プログラム中の「ある特定の機械語命令」が,ウィンドウ生成処理を行なっているはず。
それはどんな機械語なのか?
その機械語をバイナリエディタで打ち込めば,それだけでウィンドウを表示させる事ができるのだろうか?
(1)ウィンドウ作成法: .NETの場合
いきなりマシン語から考えるのではなく,まずは一般的なウィンドウの作成法から見てみよう。
現在,さあGUI作るぞという時には,.NET Framework上で作業を始めるケースが最も多いだろう。
VB.netなどの場合,Windowsフォームの新規プロジェクトを作成すると,始めからすでにウィンドウが1枚用意されている。
新規でウィンドウ(フォーム)を表示したい場合は,プロジェクト→フォームの追加 でフォームクラスを準備してから
' フォームのインスタンス作成 Dim f As New Form2() ' 表示 f.Show()
とするだけ。
似たような命令では,
- Application.Run
- Form.ShowDialog
がある。
参考:Application.RunとForm.ShowDialogの違い
http://dobon.net/vb/dotnet/form/notopenform.html
Visual C++ 2008 でWindowsフォームアプリケーションの雛型を作成した場合も,メインのcppファイルの中味をのぞいてみると
// メイン ウィンドウを作成して、実行します
Application::Run(gcnew Form1());
となっており,上記の命令がここでも使われている。
このように,最近はほぼたった1行で,非常に簡単にウィンドウを表示させる事ができるようになった。
しかし簡単であることの代償として,「中身では何をやっているのか?」「どうして表示できるのか?」は全くわからなくなった。
(2)ウィンドウ作成法: MFCの場合
.NETは2000年に出現した比較的新しいフレームワークだが,それより前はMFC (Microsoft Foundation Class) がよく使われていた。
違いを簡潔に述べると
- .NET:あらゆる言語を共通の中間言語(CIL)にコンパイルして実行するための,環境とクラスライブラリ。(2000年〜)
- MFC:Visual C++での開発効率を上げるための,クラスライブラリ。(1992年〜)
といったところ。
参考:
- MFCの歴史
- .NET Frameworkのバージョン表
MFCでのウィンドウ表示手順は,下記サイトを参照。CFrameWnd クラスを Create してから ShowWindow する。
ウィザードを使わずにMFCを使う:ウィンドウを作ってみる
http://intre.net/?%A5%D7%A5%ED%A5%B0%A5%E9%A5%E0%B8%A6%B5%E6%BD%EA%2F%A1%CEMFC%A1%CF%2002-%A5%A6%A5%A3%A5%F3%A5%C9%A5%A6%A4%F2%BA%EE%A4%C3%A4%C6%A4%DF%A4%EB
コード冒頭の
#include <afxwin.h>
が,MFC のコアを読み込む箇所。
参考:MFCの森 / プリコンパイル
http://www.page.sannet.ne.jp/t1533/program/tips.html
※AFXの意味は,affix(添付物)の略とする説と,application frameworks の略とする説がある。"stdafx"の形で目にする事が多いだろう。
ウィンドウ表示部について見てみると,実装こそ異なっているものの,コードの流れは.NETの場合とあまり変わらない。
なお VC++ 2005/2008 Express ではMFCはデフォルトで未搭載。
Visual Studio 2008 Express Edition FAQ
http://www.microsoft.com/japan/msdn/vstudio/express/faq/2008/default.aspx
# Visual C++ 2008 Express Edition には MFC と ATL は含まれていますか。
いいえ。Visual C++ 2008 Express Edition には MFC と ATL は含まれていません。MFC と ATL は Visual Studio 2008 Standard Edition 以上に含まれています。
そして,VC++を学ぶ人が今からMFCに手を出すというのはちょっと微妙かもしれない。
Vista時代のVisual C++の流儀(前編):Vista到来。既存C/C++資産の.NET化を始めよう!
http://www.atmarkit.co.jp/fdotnet/special/vcppinvista01/vcppinvista01_01.html
多くの.NET Frameworkによるアプリケーションが存在するVistaの中では、Win32ネイティブ・アプリケーションには違和感を覚えることでしょう。
さらにUIの設計と構築についていえば、.NET FrameworkのUIテクノロジ「Windowsフォーム」を使った方がWindows APIやMFCで行うより圧倒的に楽で、ほとんどコードを書くことなく行えます。
(3)ウィンドウ作成法: Windows APIの場合
.NETもMFCも,中身を見てみれば,実は Windows API というものを呼び出している。
32ビットのWindowsなら,名称はWin32API。
http://itpro.nikkeibp.co.jp/article/COLUMN/20070129/259838/
.NET Frameworkの大部分はWin32 APIのレイヤー上に載るプログラミング・インタフェースに過ぎない。.NETプログラミングで問題に遭遇したときに,それがWin32 APIレベルでどう実現されているかを考えることは,問題解決の大きなヒントとなり得る。
MFCは,C++クラスによるWindows APIのラッパーである。
そして,Windows API を使うために必要なファイルとしてまっさきに挙げられるのが「windows.h」である。
Windows API を使ってウィンドウを表示させるような,もっともシンプルなGUIプログラムを作成してみよう。
ソースコードは冒頭で必ず
#include <windows.h>
となる。
今までの(1)(2)と違い,ソースコードはとても長い。下記のいずれかのサイトからコピペする。
●Win32API(C言語)編 第2章 ウィンドウを表示する
http://www.geocities.jp/ky_webid/win32c/002.htmlWin32API>CreateWindowで窓を開く
http://www.sm.rim.or.jp/~shishido/api1.html
作成方法:
Visual C++ で
- 新規プロジェクト
- →空のプロジェクト
- →「ソースファイル」に新規ファイルを追加(test.c のように)
- →追加した test.c にソースコードをコピペ
- →F5キーを押下してデバッグ開始
すると,「シンボルを読み込んでいます…」の表示が長々と流れてコンパイルが行なわれたのち,空のウィンドウが表示される。
最小化・最大化などの基本的な操作は行なえる。立派なGUIアプリケーションである。
WinMain関数中で,ウィンドウを生成するために要となっている部分は,
// ウィンドウを作成する hWnd = CreateWindow( 〜たくさんの引数〜 ); // ウィンドウを表示する ShowWindow( hWnd, SW_SHOW );
の部分。
この CreateWindow ・ ShowWindow という2つの関数が Windows API 内に存在し,それをプログラムから呼び出すことによってウィンドウが生まれる。
.NETでも,MFCでも,内部ではやはり同じく Windows API の CreateWindow / ShowWindow を使っている。これで理解の階層を少しだけ深めることができた。
なお,CreateWindow をスタイル面で拡張できるようにした CreateWindowEx という関数もある。
拡張スタイル(dwExStyle)の定数一覧
http://www.river.sannet.ne.jp/yuui/WinDlg/CreateWindow.html
(4)windows.h を掘り下げる
Windows API を利用するために windows.h をインクルードする,というのはわかった。
では,ウィンドウ生成のための具体的な処理(つまり CreateWindow という関数の中身)が, windows.h の中に書かれているのだろうか。
そうではない。windows.h は,単独では利用できないのだ。
ヘッダーファイル『windows.h』について
http://okwave.jp/qa3602070.html
windowsのアプリケーションを作成するための基本的な宣言(変数、構造体、関数)をしているものです
Windows.hは他にもたくさんのヘッダーファイルを参照しています
PC内で,windows.h は例えば
- C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include
などの場所に置いてある。
これをテキストエディタで開いてみると,
Master include file for Windows applications.
というコメントと共に,
- windef.h
- winbase.h
等さまざまなヘッダがインクルードされている。
- winsock.h
も含まれているが,これはソケット・スレッドプログラミングをした事のある人には馴染み深いファイルかもしれない。
windows.h が置いてあったフォルダ位置に注目すると,Microsoft SDKs\Windows\ となっており,「windows.h は SDK の一部に過ぎない」という事がわかる。
そのSDKとは,Microsoft Windows SDK(旧称 Microsoft Platform SDK ) である。
Microsoft Windows SDK
http://ja.wikipedia.org/wiki/Microsoft_Windows_SDK
Microsoft Windows SDK(マイクロソフト ウィンドウズ エスディーケー)とは、Microsoft Windowsで動作するアプリケーションを作成するためにマイクロソフトが無料で公開しているソフトウェア開発キット(SDK)である。
Windows APIを利用するために必要なヘッダファイル、ライブラリ、ツール、サンプルを含んでいる。
したがって,Windows SDK をインストールしていなかったり,Windows SDK へのパスの設定が間違っていたりすると,コンパイルができないので
: fatal error C1083: include ファイルを開けません。'windows.h' : No such file or directory
のようなエラーメッセージに出会う事になる。
例えば:
- Borland BCC で入門書を読みながらC言語のコンソールアプリケーションの勉強をしていた人
- →あるとき GUI プログラムを作りたいと思って一念発起し,ネット上のソースコードをコピペしてコンパイル実行
- →「windows.h が無い」というエラーに遭遇
- →windows.hというファイルをどこかからダウンロードしようとしてあちこち探す羽目に…
なんていうパターンが頻出のケースだ。(昔の私のことだが)
windows.hだけを入手してもだめで,SDK自体をインストールする必要がある。Visual Studio + .NET Framework が問題なく動作しているなら,恐らく既にインストール済み。
ところで上のwikipediaの解説を見ると,Microsoft Windows SDK は Windows API を「利用する」と書いてある。
つまり,上の(3)において windows.h + CreateWindow でウィンドウを生成したのは
- Windows API そのものを直接使った …というよりはむしろ,
- Windows API を利用するために Windows SDK を使った …という事になる。
つまり,さらに深めるためには,windows.h やら Windows SDK ではなく,それらが呼び出している Windows API そのものに目を向ける必要があるのだ。
ウィンドウを生成している Windows API というものの実体は,いったい何なのだろうか。
(5)Windows API の実体
APIの実体は,DLLである。
Win32 APIの何たるか
http://yokohama.cool.ne.jp/chokuto/win/win32api.html
APIはどこに?
実は、APIはDLL(Dynamic Link Library;ダイナミックリンクライブラリ)と呼ばれる、ファイル名の拡張子が“.dll”となったバイナリファイルの中にあるのです。
特に、以下に示す3つのDLLが提供する機能はWindowsの中核をなすもので、これらのDLLが提供するAPIはWindowsアプリケーションの開発には不可欠です。
- kernel32.dll
- gdi32.dll
- user32.dll
どのDLLがどういう機能を実装しているのかについては,下記ページが詳しい。
Windows API
http://ja.wikipedia.org/wiki/Windows_API
ベースサービス
Windowsの基盤となる機能を提供する。ファイルシステム、デバイス、プロセス、スレッド、レジストリ、例外処理などが含まれる。 Win16ではkernel.exe、krnl286.exe、krnl386.exeに、Win32ではkernel32.dll及びadvapi32.dllに実装されている。
Graphics Device Interface
ディスプレイ・プリンタをはじめとした出力デバイスへの描画機能を提供する。 Win16ではgdi.exeに、Win32ではgdi32.dllに実装されている。
ユーザインタフェース
ウィンドウの処理、ボタンやスクロールバーなどといった基本的なコントロール、マウス・キーボード入力、その他グラフィカルユーザインタフェース (GUI) に関わる機能を提供する。Win16ではuser.exeに、Win32ではuser32.dllに実装されている。ただしWindows XPから基本的なコントロールはコモンコントロール(後述)と共にcomctl32.dllに含まれている。
これで,ウィンドウ生成処理は user32.dll が大もとになっているとわかった。
実は(3)で Windows API からウィンドウ表示するプログラムを作成した際にも,この user32.dll が自動的に利用されていた。
ためしに,Visual C++ 2008 から
- プロジェクト
- →プロジェクトのプロパティ
- →構成プロパティ
- →リンカ
- →コマンドライン
- →すべてのオプション
を見てみると,コンパイル&リンク時のオプション文字列の中に
・・・ /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:PROMPT kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib
のように,ちゃんと user32.lib が含まれており,user32.dll の呼び出しに対応していることがわかる。
※DLLとLIBファイルの違いについては下記サイトを参照。LIBはDLLを参照するための情報が書き込まれたライブラリであり,実行ファイル作成時にリンカが利用する。
http://www.kab-studio.biz/Programing/Codian/DLL_Hook_SClass/01.html
user32.dll がないと困るのを実感するために,下記のプログラムをコマンドラインからコンパイルしてみよう。(Visual Studioからではない。)
#include<windows.h> int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance , LPSTR lpCmdLine , int nCmdShow ) { MessageBox(NULL,"本文","タイトル",MB_OK); return 0; }
これは,ボタン付きのダイアログを一つ表示するだけのプログラム。(dialog.cとする)
コンパイルのために,コマンドプロンプトから
cl dialog.c
と打ちこむと,
D:\temp>cl dialog.c Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. dialog.c Microsoft (R) Incremental Linker Version 9.00.21022.08 Copyright (C) Microsoft Corporation. All rights reserved. /out:dialog.exe dialog.obj dialog.obj : error LNK2019: 未解決の外部シンボル __imp__MessageBoxA@16 が関数 _W inMain@16 で参照されました。 dialog.exe : fatal error LNK1120: 外部参照 1 が未解決です。
のようにエラーになる。ダイアログ表示のために必要な user32.dll を参照できていないからだ。
正しくは
cl dialog.c user32.lib
とする。これで.exeができる。
(6)user32.dll の実体
さて,「ウィンドウを表示するために必要な機械語は?」というテーマでここまで掘り下げてきたわけだが,DLLファイルの段階に到達した。
コアなDLLファイルの置き場は C:\WINDOWS\system32 と相場が決まっており,user32.dll もここにある。
あとはこのDLLを逆アセンブルするだけなのだが・・・
アプリケーションのDLLファイルを開いたり、編集したりすることは技術的に可能ですか?
http://okwave.jp/qa301010.html
重要なのは、メーカーやDLLの配布元から編集・逆コンパイル許諾を得ているかどうか、…
もし制作者の許諾なしに改変する、ソースを見るなどの措置を執れば、機密コードを持つ場合もありますので、不正コピーより厳しい罰則を受けることになりますよ。
(これがWindowsの一部なら、最悪では個人でも半端ではない請求があるはずです)
http://oshiete1.goo.ne.jp/qa237768.html
マ社の発行しているDirectXコンポーネントやWindowsプログラムの一部に利用されているDLLは逆コンパイル及びソースの改変は許可なくできません。使用許諾違反になる恐れというよりなる可能性が極めて高いです。
…内容を確認できるのはオープンソースとして公開している物、著作者に許諾を得て見る場合、もしくは当人が著作権を放棄した場合に限ります)
ということで,このDLLはマイクロソフト社製品の一部だから,逆アセンブルは控えたほうがよい。
ましてや,逆アセンブルの結果をこういうブログ上で公開するなんて事はまずいのでやらない。
と思いきや,やっている方がいた。
Windowsシステムプログラミング Part2
http://07c00.com/text/winsys2/ntdll.dllがエクスポートしているZwCreateFile関数の呼び出し構造は以下。
(〜リバースエンジニアリングの結果〜)
WindowProc の呼ばれ方
http://keicode.com/windows/win01.phpウィンドウプロシージャが呼ばれる際のコールスタック(ルーチンが呼ばれる順番)を追跡
やや温め納豆
http://d.hatena.ne.jp/egggarden/20080316SetWindowLong/SetWindowPosあたりでさっくり作れるかと思ったら、モーダル/モードレスはウィンドウスタイルに依存しないで実現されているっぽい*1。しょうがないのでDialogBox系をフックしてしまおうと Vista(SP1) のコールスタックを取ってみた。
もしかしたら問題ないのかもしれない。(特にコールスタックぐらいなら普通に)
それに,本ブログでは逆コンパイル + 逆アセンブル のための5つの無料ツールといった記事を執筆してきたから,やろうと思えばできる。
しかし,法的に問題の起こる可能性を考え,本記事ではuser32.dllの逆アセンブルについてはノータッチ(不実行)にしておく。
前半のまとめ
ウィンドウの実体は? ウィンドウを作る方法は?
- 最近は.NETで Form.Show
- VC++はMFCで CFrameWnd::Create
- ↑どちらも Windows SDK (CreateWindow / windows.h) に帰着
- 3は Windows API を呼び出している
- 4の実体は user32.dll
user32.dllの中味を見れないとなると,一見,本記事の本来の目的であった「ウィンドウの実体は何か?」「ウィンドウ生成の機械語はあるのか?」というテーマがストップしてしまいそうに思える。
でも心配する事なく,別方面から切り口を変えてさらに理解を深めることが可能だ。
内容は次回(後半)へ続く。