第 5 章: Hilo Common Library

Hilo はいくつかの Windows 7 アプリケーションで構成されており、各アプリケーションにはメイン ウィンドウのほかに 1 つ以上の子ウィンドウがあります。これらのウィンドウの作成と管理をサポートし、ウィンドウに送信されるメッセージを処理する目的で、Hilo では軽量のオブジェクト指向ライブラリを実装しています。アプリケーションの機能とその実装に使用されている Windows 7 機能について説明した後続の記事での学習に注力できるよう、この記事では Hilo Common Library の概要について説明します。

Hilo Common Library の概要

Hilo Common Library には、Hilo ソリューションの全プロジェクトに共通のクラスが含まれています。これらのクラスによって、参照カウントの対象となるオブジェクトのアクセスと提供、ウィンドウの作成、および Windows メッセージの処理のための基本的なインフラストラクチャが提供されます。

参照カウント

Hilo は参照カウントを処理する目的で ComPtr<> というテンプレート構造を提供しています。このスマート ポインター クラスは、その名前に反して COM インターフェイス ポインター専用ではありません。ComPtr はオブジェクトが COM によって起動されたかどうか、あるいは COM アパートメント内で動作するかどうかを問わず、IUnknown から派生した任意のインターフェイスに使用できます。このクラスは、カプセル化されたインターフェイス ポインターを減分するデストラクターを実装します。これにより、開発者が IUnknown::Release メソッドの呼び出しを気にかける必要はなく、必要になった時点で ComPtr<> テンプレートがその呼び出しを行ってくれます。

Hilo クラスの多くは、IUnknown から派生したインターフェイスを実装します。Hilo では、オブジェクトを作成するために SharedObject<> というテンプレート クラスが提供されています。これは mixin クラスであり、テンプレート パラメーターとして提供された型から派生するので、mixin クラスはテンプレート パラメーター クラスのパブリック メンバーと保護されたメンバーにアクセスできます。SharedObject<> クラスは、C++ ヒープにクラスのインスタンスを作成する、静的な Create メソッドを提供します。Create メソッドは、オブジェクトで IInitializable というインターフェイスをクエリし、オブジェクトがこのインターフェイスを実装している場合、Create メソッドはそのインターフェイスで Initialize メソッドを呼び出します。Initialize のコードは、オブジェクトを構築した後、そのオブジェクトを初めて使用する前に呼び出します。リスト 1 は、SharedObject<> クラスの使用例です。

SharedObject<>IUnknown::AddRef メソッドと IUnknown::Release メソッドを実装し、オブジェクトの参照カウントを増減します。参照カウントがゼロに達すると、IUnknown::Release メソッドは delete 演算子を呼び出してオブジェクトを削除します。

IUnknown メソッドの最終的なメソッドは QueryInterface です。このメソッドを呼び出して、オブジェクト上の特定のインターフェイスを要求します。このメソッドでは、指定したインターフェイスがクラスで実装されているかどうかをチェックする目的で、基本クラスが QueryInterfaceHelper というヘルパー関数を実装している必要があります。実装されている場合、適切なポインターを返します。

Common Library の設計

Windows アプリケーションの寿命は、エントリポイント関数である WinMain の寿命に左右されます。Windows アプリケーションは、画面上で 1 つのフレームと、[閉じる] ボタンや [最小化] ボタンなどの装飾のあるウィンドウによって表示され、通常メイン ウィンドウを閉じると WinMain 関数が戻り、プロセスが終了します。このプロセスとプロセスによって表示されるウィンドウは、すべて Windows オブジェクトです。

C++ アプリケーションは、プロセスとそのウィンドウの C++ オブジェクトを提供します。C++ オブジェクトの寿命は、new 演算子と delete 演算子を呼び出すタイミング (ヒープ ベースのオブジェクトの場合) や、スタック フレームのスコープ (スタック ベースのオブジェクトの場合) などの C++ の概念に左右されます。Hilo Common Library の場合、C++ オブジェクトを使用してすべてのウィンドウのメッセージ ハンドラーを提供します。したがって C++ オブジェクトの寿命は、ウィンドウの寿命より長くなければなりません。

Hilo Browser のエントリポイント関数をリスト 1 に示します。RunMessageLoop メソッドが標準的なメッセージ ポンプを提供します。このメッセージ ポンプは、Windows からメッセージを取得し、そのメッセージをプロセス内の適切なメッセージ ハンドラー関数にディスパッチする while ループです。このループは、WM_QUIT メッセージを受信するまで実行されます。その時点で RunMessageLoop メソッドが戻り、_tWinMain 関数が完了して、プロセスが終了します。

リスト 1 Hilo Browser のエントリポイント関数

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
ComPtr<IWindowApplication> mainApp;
HRESULT hr = SharedObject<BrowserApplication>::Create(&mainApp);
if (SUCCEEDED(hr))
    {
hr = mainApp->RunMessageLoop();
    }
return 0;
}

この例では、SharedObject<>::Create の呼び出しによって C++ ヒープに BrowserApplication オブジェクトか作成され、mainApp 変数がスコープ外になった時点でオブジェクトが削除されることが ComPtr<> スマート ポインター オブジェクトによって保証されます。BrowserApplication オブジェクトが提供するコードによって、メイン ウィンドウに送信されたメッセージが処理され、その子ウィンドウへのメッセージを処理するオブジェクトが作成されます。

Windows クラスの設計

Window クラスは、IWindow を実装する Windows API 上のシン ラッパーです。このクラスは HWND 値をカプセル化し、たとえばウィンドウのタイトル、位置、サイズにアクセスするといった、基本的な Windows API ウィンドウ関数への単純なアクセスを提供します。メッセージ ハンドラーを提供するクラスは、IWindowMessageHandler インターフェイスを実装します。IWindow インターフェイスは、ウィンドウに対するメッセージを処理するオブジェクトへのアクセスを提供するために、リスト 2 に示すメソッドを提供します。

リスト 2 Windows メッセージ ハンドラーのメソッド

HRESULT __stdcall GetMessageHandler(__out IWindowMessageHandler** messageHandler);
HRESULT __stdcall SetMessageHandler(__in IWindowMessageHandler* messageHandler);

図 1 は、Hilo Common Library に含まれる主なクラスを示す Universal Modeling Language (UML) クラス図です。BrowserApplication クラスは Hilo Browser プロジェクトに固有のクラスであり、AnnotatorApplication クラスは Annotator プロジェクトに固有のクラスです。その他のクラスとインターフェイスは、Hilo のプロジェクト全体で共有され、Common という静的なライブラリ プロジェクトを通じて提供されます。図 1 では、これらのクラスの属性と動作を示しています。

図 1 基本的なウィンドウのクラスを表す UML 図 (網かけのクラスは Common Library により提供)

Ff919194.5e6ec0cb-be5d-4339-95c7-849b8751bf25-thumb(ja-jp,MSDN.10).png

WindowMessageHandler クラスは、ウィンドウに送信された Windows メッセージを処理するための IWindowMessageHandler::OnMessageReceived というメソッドを実装します。その目的で、このクラスは汎用的な処理を提供し、仮想メンバー関数を多形的に呼び出すことにより、その他のコードにアクセスします (図 1)。WM_PAINT メッセージがウィンドウに送信されると、OnMessageReceived メソッドの既定のハンドラー コードがこのメソッドの標準的な処理を実行します。つまり、Windows API 関数 BeginPaint を呼び出してウィンドウの描画を準備し、EndPaint を呼び出してクリーンアップを実行します。これらの関数呼び出しの間に、OnMessageReceived メソッドが仮想メソッド OnRender を呼び出し、派生したクラスによって提供されるその他のレンダリング コードを呼び出します。これを図解したのが図 2 です。このような仮想メソッドの使用によって、WindowMessageHandler クラスが一般的なメッセージに対応する既定のメッセージ処理を提供すると共に、派生したクラスによってその他の処理を提供できます。

図 2 Hilo におけるメッセージ処理

Ff919194.f8f577c0-2d91-4e2d-adc0-0442a55eafbf-thumb(ja-jp,MSDN.10).png

WindowApplication クラスはメッセージ ループを実装し、メインのアプリケーション ウィンドウおよびウィンドウ ファクトリ オブジェクトの 2 つのオブジェクトへのアクセスを提供します。ウィンドウ ファクトリ (後述する WindowFactory クラスによって実装) は、Windows API 関数 CreateWindow の呼び出しによってウィンドウを作成します。メイン アプリケーションのメッセージ処理は非常に基本的なものです。実質的には、メイン ウィンドウに WM_DESTROY メッセージが送信された時点でメッセージ ポンプに WM_QUIT メッセージを供給し、それによって前述のプロセスが終了することを保証しているだけです。

Hilo の各プロセス (Browser および Annotator) のメイン ウィンドウのクラスは、WindowApplication から派生し、メイン ウィンドウ用の機能を追加しています。BrowserApplication には、メイン ウィンドウのクライアント領域を塗りつぶす 2 つの子ウィンドウがあり、これらのウィンドウのレイアウトは、IWindowLayout インターフェイスを実装するオブジェクトによって管理されます。このレイアウト オブジェクトには、これら 2 つの子ウィンドウへの IWindow インターフェイス ポインターがあります。BrowserApplication オブジェクトは、レイアウト オブジェクトからウィンドウにアクセスできるよう、各ウィンドウのインデックスを保持しています。AnnotatorApplication オブジェクトには画像を編集するための 1 つのウィンドウがあり、Windows リボンを使用してユーザーがコマンドにアクセスできるようにします。

Hilo アプリケーションの子ウィンドウ (Browser のカルーセルとメディア ペイン、および Annotator のエディターを実装) は、WindowMessageHandler から派生したクラスによって実装されますが、これらは図 1 には示されていません。

ウィンドウの作成

Windows アプリケーションは通常、CreateWindow 関数の呼び出しによってウィンドウを作成し、登録済みの Windows クラス名を提供します。Windows クラスは、新しく作成されたウィンドウに対する Windows メッセージを処理する関数へのポインターを含む構造です。Hilo Common Library でこれに該当するコードは、WindowFactory と呼ばれる C++ クラスです。アプリケーションのメイン ウィンドウを作成するために、WindowApplication::Initialize メソッドで WindowFactory クラスのインスタンスが作成されます。

図 3 WindowFactory クラスの UML

Ff919194.c3510df2-a0ea-4a3d-adb1-69fddf18e4ac(ja-jp,MSDN.10).png

WindowFactory クラスは、図 3 の UML を見てわかるように非常に単純です。 IWindowFactory::Create メソッドが、指定されたメッセージ ハンドラー オブジェクトを使い、指定されたサイズと位置でウィンドウを作成します。ウィンドウが別のウィンドウの子である場合、Create メソッドにそのウィンドウの IWindow ポインターを渡すことができます。Create メソッドの最初の動作は、プライベート メソッド RegisterClass を呼び出して、静的メソッド WindowFactory::WndProc を Windows プロシージャとして登録することです。つまり、ウィンドウ ファクトリ オブジェクトによって作成されたウィンドウはすべて同じ Windows プロシージャによって処理されます。ただし後述するように、WndProc 関数がそのウィンドウのウィンドウ ハンドラー オブジェクトにメッセージを転送します。

Windows クラスが登録されると、IWindowFactory::Create メソッドは Window オブジェクトの新しいインスタンスを作成します。このインスタンスは、作成される新しいウィンドウの HWND をカプセル化します。次に IWindow::SetMessageHandler の呼び出しにより、この Window オブジェクトがメッセージ ハンドラー オブジェクトを使って初期化されます。最後に、Create メソッドが Windows API 関数 CreateWindow を呼び出して、新しいウィンドウを作成します。CreateWindow メソッドに、lpParam パラメーターを通じて Window オブジェクトがプライベート データとして渡されます。これにより、このオブジェクトが Windows プロシージャで使用可能になります。この時点では、Window オブジェクトに、カプセル化された HWND は割り当てられていない点に注意してください。

リスト 3 WindowFactory Windows プロシージャ

LRESULT CALLBACK WindowFactory::WndProc(
HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
{
LRESULT result = 0;
ComPtr<IWindow> window;
ComPtr<IWindowMessageHandler> handler;

if (message == WM_NCCREATE)
    {
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
window = reinterpret_cast<IWindow*>(pcs->lpCreateParams);
window->SetWindowHandle(hWnd);

::SetWindowLongPtrW(hWnd, GWLP_USERDATA, PtrToUlong(window.GetInterface()));
    }
else
    {
IWindow* windowPtr = reinterpret_cast<IWindow*>(
::GetWindowLongPtrW(hWnd, GWLP_USERDATA));

if (windowPtr)
        {
window = dynamic_cast<IWindow*>(windowPtr);
        }
    }

if (window)
    {
window->GetMessageHandler(&handler);
    }

if (!window || !handler || FAILED(
handler->OnMessageReceived(window, message, wParam, lParam, &result)))
    {
result = ::DefWindowProc(hWnd, message, wParam, lParam);
    }

return result;
}

リスト 3 は、Hilo アプリケーションのすべてのウィンドウに送信されるすべてのメッセージに呼び出される WndProc 関数です。このメソッドには 2 つの目的があります。ウィンドウが最初に作成された時点で、このメソッドに WM_NCCREATE メッセージが渡され、CreateWindow 関数の最後のパラメーターとして渡されたプライベート データ、すなわち新しく作成されたウィンドウの IWindow インターフェイス ポインターへのポインターが lpParam パラメーターで表されます。この関数は lpParam パラメーターから IWindow インターフェイス ポインターを抽出し、IWindow::SetWindowHandle メソッドを呼び出すことにより、新しく作成されたウィンドウの HWNDWindow オブジェクトを初期化します。次に、コードは Windows API 関数 SetWindowLongPtr を呼び出し、ユーザー データとしてウィンドウに IWindow インターフェイス ポインターを追加します。これらの動作で、このメソッドは hWnd パラメーターで表されるウィンドウに、C++ の Window オブジェクト (およびメッセージ ハンドラー オブジェクト) を追加しています。

WndProc 関数の第 2 の目的は、ウィンドウのメッセージ ハンドラー オブジェクトにすべての Windows メッセージを転送することです。WndProc 関数の最初の呼び出し以降、この関数の後続の呼び出しでは、GetWindowLongPtr Windows API の呼び出しにより、HWND からユーザー データを取得して、IWindow インターフェイスにポインターをキャストすることができます。メソッドはこのインターフェイス ポインターからメッセージ ハンドラー オブジェクトにアクセスすることができ、そのメッセージ ハンドラー オブジェクトの OnMessageReceived メソッドに、すべてのメッセージを転送します。

メッセージ ハンドラー オブジェクトは IWindowMessageHandler インターフェイスを実装します。ウィンドウが削除されると、WM_DESTROY メッセージが送信され、IWindowMessageHandler::OnMessageReceived メソッドが 2 つの処理を実行します。まず、仮想メソッド OnDestroy を呼び出します。また、WindowApplication 呼び出しの既定の実装によって PostQuitMessage Windows API 関数が呼び出され、それによってアプリケーション ウィンドウに WM_QUIT メッセージがポストされます。その結果、メッセージ ポンプが打ち切られプロセスが終了します。OnMessageReceived メソッドの WM_DESTROY ハンドラーによるもう 1 つの処理は、IWindow::SetMessageHandler を呼び出して nullptr を渡すことです。これにより、メッセージ ハンドラー オブジェクトの参照が解放され、オブジェクトが削除されます。

まとめ

Hilo アプリケーションは、軽量のライブラリを使用して基本的な Windows メッセージ処理機能を提供します。この記事では、このライブラリの主なクラスと、その相互作用について説明しました。また、これらのクラスによるウィンドウの作成と削除の仕組みと、Windows メッセージを処理する C++ オブジェクトとこれらのオブジェクトの関係を説明しました。次の記事では、Windows 7 の Direct2D API によるウィンドウの描画について探究します。

それらを使って: Browser アプリケーション

リスト 1 は Browser のエントリポイント関数です。メイン ウィンドウが BrowserApplication クラスによって処理されていることがわかります。SharedObject::Create メソッドは、このクラスのインスタンスを C++ ヒープに作成した後、BrowserApplication::Initialize メソッドを呼び出して、オブジェクトの作成後の初期化を実行します。このメソッドは、最初に基本クラスを呼び出してウィンドウ ファクトリ オブジェクトを作成し、このファクトリ オブジェクトを使用してアプリケーションのメイン ウィンドウを作成します。次に Initialize メソッドは InitializeCarouselPane および InitializeMediaPane の 2 つの関数を呼び出して、Browser アプリケーション ウィンドウの子ウィンドウとしてカルーセルおよびメディア ペイン ウィンドウを作成します。図 4 は、これらのクラスによって提供されるウィンドウを示しています。

Browser アプリケーションのメイン ウィンドウには 2 つの子ウィンドウが含まれます。1 つはカルーセルと履歴リスト、もう 1 つは写真を表示するためのウィンドウです。BrowserApplication::Initialize メソッドは、これらの子ウィンドウのサイズと位置を管理するために、WindowLayout クラスのインスタンスを作成します。BrowserApplication::Initialize メソッドによる最後の処理は、シェル API を呼び出し、カルーセルに表示する最初のフォルダーとして Pictures ライブラリを取得することです。

図 4 Hilo Browser の主なクラス

Ff919194.5e578e93-d5e7-4d0d-9e9a-5dbe01c74377-thumb(ja-jp,MSDN.10).png

子ウィンドウの作成

InitializeCarouselPane メソッドは、メインの Browser アプリケーション ウィンドウの子としてウィンドウを作成し、そのウィンドウからのメッセージを処理するために CarouselPaneMessageHandler クラスのインスタンスを作成します。カルーセル ウィンドウが最初に作成された時点で CarouselPaneMessageHandler クラスの OnCreate メソッドが呼び出されます。このメソッドによって、軌道、フォルダー サムネイル、履歴アイテム スタックの描画に使用する Direct2D リソースが初期化されます。

InitializeMediaPane メソッドは、メインの Browser アプリケーション ウィンドウの子としてウィンドウを作成し、そのウィンドウへのメッセージを処理するために MediaPaneMessageHandler クラスのインスタンスを作成します。MediaPaneMessageHandler オブジェクトか最初に作成された時点で Initialize メソッドが呼び出されます。このメソッドにより、このペインに表示するアイテムを管理するための ThumbnailLayoutManager オブジェクトが作成されます。MediaPaneMessageHandler クラスでは OnCreate メソッドは提供されないので、WM_CREATE メッセージに関しては既定の処理だけが実行されます。メディア ペインは Direct2D も使用します。リソースの初期化は、(WM_PAINT メッセージ処理の一環として呼び出される) Redraw メソッドによってリソースが使用される直前に行われます。

レイアウト オブジェクトの使用

Hilo Browser ウィンドウのサイズが変更されると、子ウィンドウのサイズも変更されます。これはレイアウト オブジェクトの機能です。このクラスで最も重要なメソッドは、UpdateLayout です。このメソッドは、Browser メイン ウィンドウのクライアント領域を満たすよう、子ウィンドウのサイズと位置を変更します。レイアウト オブジェクトはカルーセルの高さを使って初期化されます。この高さは、内側の軌道 (フォルダー アイコンとフォルダー テキストを含む) の表示に必要な最大サイズです。UpdateLayout メソッドは、カルーセル ウィンドウを Browser メイン ウィンドウのクライアント領域の高さと幅に設定し、クライアント領域の上にカルーセルを配置します。UpdateLayout メソッドは、カルーセル ペインのすぐ下にメディア ペインを配置し、Browser ウィンドウのクライアント領域と同じ幅で、クライアント領域の残りのスペースの高さになるようサイズを設定します。

メディア ペインに含まれるサムネイル レイアウト マネージャーは、ナビゲーション矢印、サムネイル、メディア ペインのサイズから、矢印と画像の位置を計算します。メディア ペインが大きい場合には、サムネイルを複数の行で表示することができます。

ウィンドウの描画

基本クラス WindowMessageHandler は、仮想メソッド OnRender を呼び出して WM_PAINT メッセージを処理し、派生したクラス BrowserApplication に責任を引き渡します。この派生したメソッドは、レイアウト マネージャーにアクセスして、カルーセルとメディア ペインのメッセージ ハンドラー オブジェクトへのアクセスを取得し、これらのハンドラー オブジェクトに IPane::Redraw メソッドを呼び出します。これにより、ウィンドウを描画する DrawClientArea というメソッドが呼び出されます。カルーセルとメディア ペイン ウィンドウ内のアイテムは、アニメーションが可能です (たとえば、カルーセルの軌道上でのフォルダーの回転、メディア ペインでのサムネイルの移動など)。このアニメーションは、画面更新の間でペイン内でのアイテムの位置を変更することによって実行されます。アニメーションを使用する場合、アニメーション マネージャーから DrawClientArea メソッドによってアイテムの位置を取得します。

マウス移動とキー押下の処理

タッチ スクリーンのタッチは、左マウス ボタンのクリックとして処理されます。また、ユーザーはマウスまたはキーボードを使ってアプリケーションとやり取りすることもできます。マウスとキーボードの操作は、メッセージ ハンドラー クラスの該当する名前の仮想メソッドによって処理されます。

CarouselPaneMessageHandler クラスは、左マウス ボタンのクリックを 3 通りの方法で処理します。1 つ目として、ペインの左上隅がクリックされた場合は、履歴スタックが展開されます。[戻る] ボタンがクリックされると、履歴リストを後ろ向きにナビゲートします。2 つ目として、クリックによってフォルダー (内側の軌道上のフォルダー、または履歴スタックが表示されている場合には、履歴スタック内のいずれかのアイテム) が選択されます。3 つ目として、クリックによってユーザーが内側の軌道上のアイテムをドラッグすることにより、カルーセルが回転します。OnLeftMouseButtonDownOnLeftMouseButtonUp、および OnMouseMove メソッドのコードの大部分は、この目的のために存在しています。ユーザーはマウス ホイールを動かしてカルーセルを回転させることができます。これは CarouselPaneMessageHandler によって OnMouseWheel メソッドで処理されます。キーボードが使われる場合、CarouselPaneMessageHandler クラスは、左矢印キーと右矢印キーをカルーセルの左右への回転、Backspace キーを履歴スタックを上へナビゲートするためのシグナルとして解釈します。

メディア ペインには、一連の画像と、画像リストをスクロールするための矢印が表示されます。MediaPaneMessageHandler クラスは、タッチまたはマウス クリック イベントを処理して画像を左右にスクロールすると共に、マウス ホイール イベントによるリストのスクロールも処理します。ナビゲーション ボタンを左マウス ボタンでクリックすると、該当する方向にリストがスクロールされます。マウス ボタンのダブルクリックは、OnLeftMouseButtonDoubleClick メソッドにより、参照モードとスライド表示モードの切り替えとして解釈されます。参照モードでは、メイン ウィンドウの上半分にカルーセルが表示されますが、スライドショー モードでは、カルーセル パネルが非表示になり、選択中の画像が正しい縦横比を保ちながら、Browser ウィンドウ全体に可能な限り大きく表示されます。この場合、ユーザーはすべての画像を 1 つずつ参照できます。メディア ペインはキーの押下を OnKeyDown で処理します。Page Up キーと Page Down キーは、画像リストを左および右へスクロールします (左右のナビゲーション矢印をクリックした場合と同様)。プラス記号 (+) キーとマイナス記号 (-) キーは、サムネイルのサイズを変更します。

アプリケーションの終了

ユーザーが [閉じる] ボタンをクリックすると、アプリケーション ウィンドウに WM_CLOSE メッセージが送信され、WindowMessageHandler クラスが仮想メソッド OnClose を呼び出してこれを処理します。OnClose メソッドをオーバーライドする Hilo クラスはありません。また、既定の実装では Windows の既定の処理が呼び出されるだけです。WM_CLOSE 呼び出しに対する Windows の既定の処理では、DestroyWindow 関数が呼び出され、それによって WM_DESTROY メッセージがウィンドウに送信されます。WindowMessageHandler クラスが OnDestroy 仮想メソッドを呼び出すことによって、このメッセージを処理します。BrowserApplication::OnDestroy メソッドは、レイアウト オブジェクトに対し Finalize というメソッドを呼び出します。すべての子ウィンドウで WindowLayout::Finalize メソッドが繰り返され、各ウィンドウに Finalize メソッドを呼び出して、各ウィンドウの最終的なクリーンアップが実行されます。最後に BrowserApplication::OnDestroy メソッドは OnDestroy の基本クラス実装を呼び出します。それによって PostQuitMessage Windows API 関数が呼び出され、アプリケーション ウィンドウに WM_QUIT メッセージがポストされます。このメッセージによってメッセージ ポンプの while ループが停止し、処理が終了します。

前へ | 次へ | ホーム