HWND型を知りたい

[戻る]
パンダ 2004/09/02(木) 18:43:18
HWND型を調べると、たいてい、ウインドウハンドルとかんたんにかいてあります。ウインドウ
を数字で管理するためにつかうもでしょうから整数型ととらえてよいもでしょうか。だれかもう
すこし詳しくおしえてください。

Ban 2004/09/02(木) 18:50:16
windows のヘッダファイルの中を読んでみれば定義が書いてあります。

STRICT マクロの有無で実体が変わったりしますが、基本的にはHWND専用の
構造体のポインタとして実装されており、少なくともただの整数ではありません。

将来的に変更されないとも限りませんし、あくまで HWND という型だと
考えるべきだとは思います。

RAPT 2004/09/03(金) 00:31:38
Banさんの投稿に関連して。

大雑把に言うと、HANDLE型の一種ともいえるかもしれません。

  int型  long型  UINT型  ULONG型  などのように、
HANDLE型  HWND型  HMENU型  HFLE型など。

Ban 2004/09/03(金) 02:27:45
> 大雑把に言うと、HANDLE型の一種ともいえるかもしれません。

そうですね。

ちなみに、
HWND 以外の、HANDLE 互換の型たちもほぼ全て専用の構造体のポインタです。
HWND -> HANDLE は暗黙で、HANDLE -> HWND は明示的にキャスト出来ることからも
推測できるかもしれませんが、HANDLE型自体の内部実装は void* になってます。

パンダ 2004/09/05(日) 17:53:22
回答ありがとうございます。
50%くらいわかりました。
つまり、ウインドウのコントロール用の構造体があって、それへのポインタが
HWND型ということですね。[void *]の」ようなものと考えてよいのでしょうか。
その構造体の中味はどこかにかいてあるのでしょうか。
HWND型を詳しく書いた本がないのも何か理由があるのでしょうか。   

シャノン 2004/09/06(月) 10:06:31
VisualStudio でデバッグ中に、ウォッチウィンドウで HWND 型の変数を見ればわかると
思いますが、HWND 構造体には unused というメンバが一つだけあります。
名前からわかるとおり、このメンバは使われていない、あるいは使ってはいけない
メンバです。
何故そんなものがあるのかと言えば、メンバのない構造体は作れないためでしょう。
では、そもそも何故使えないメンバを用意してまで構造体にする必要があるのか。
それは、HWND 型は HWND 型以外の何者でもないということを明示するためであると思わ
れます。

C / C++ では、typedef は型に別名をつけるものでしかありません。
すなわち、

typedef int myInteger;

としたとき、int と myInteger は同じ型につけられた別名であるとみなされてしまいま
す。
同じように、もし仮に

typedef HANDLE HWND;

とすると、HWND と HANDLE は名前が違うだけで、本質は同じものになってしまいます。
これだけならまだいいかも知れませんが、

typedef HANDLE HMENU;

もあると、HWND == HANDLE == HMENU になってしまい、都合が悪いのではないでしょう
か(動作に支障はないでしょうが)。

つまり、俺の意見としては
・構造体のポインタという形をとっているのは、他のハンドルと型を区別するため
・メンバは、構造体であるために必要なだけであって、意味を持たない
ということです。

また、ウィンドウハンドルはウィンドウの情報を保持した構造体へのポインタである、
という見方もできます。
どういうことかというと…

struct HWND_Internal
{
  LPTSTR lpszClass;
  LPTSTR lpszTitle;
  int style;
  int x;
  int y;
  int width;
  int height;
};

のような(これはあくまで一例です)構造体が実はあって、HWND はこれへのポインタな
のかもしれません。
しかし、この中身は OS によって変わることが予想されますので、このメンバに直接ア
クセスさせたくありません。
そこで、このメンバを非公開とし、代わりに OS に依存しない API でアクセスさせた
い…と考えると、例えば

// 非公開関数
HWND_Internal * WINAPI CreateWindow_Internal( ... )
{
  // ウィンドウを作成し、その情報を保持している
  // HWND_Internal 構造体のポインタを返す

  return hWnd;
}

// 我々が使う CreateWindow API 関数
HWND WINAPI CreateWindow( ... )
{
  // 非公開関数を用いてウィンドウを作成する
  HWND_Internal *pHWnd = CreateWindow_Internal( ... );

  // 内部情報を非公開にするために、別の型にして返す
  return reinterpret_cast< HWND * >( pHWnd );
}

こんな実装になっているかもしれません。

というわけで、ここまで長々と書いてきたことをまとめて疑問にお答えしますと

> その構造体の中味はどこかにかいてあるのでしょうか。
HWND 構造体には unused というメンバが一つだけあります。

> HWND型を詳しく書いた本がないのも何か理由があるのでしょうか。
OS によって変わることが予想されますので、このメンバに直接アクセスさせたくありま
せん。

ということなのではないか、と予想できます。

シャノン 2004/09/06(月) 10:31:42
訂正

> // 我々が使う CreateWindow API 関数
> HWND WINAPI CreateWindow( ... )
> {
>   // 非公開関数を用いてウィンドウを作成する
>   HWND_Internal *pHWnd = CreateWindow_Internal( ... );
> 
>   // 内部情報を非公開にするために、別の型にして返す
>   return reinterpret_cast< HWND * >( pHWnd );
> }

return reinterpret_cast< HWND__ * >( pHWnd );
あるいは
return reinterpret_cast< HWND >( pHWnd );
の間違いです。

補足

いい例が思いつかなかったのですが、例えば GetWindowText について。

Win95 では、ウィンドウ情報構造体は実はこう

struct HWND_Internal_Win95
{
  char *pszText;
  // 以下略
};

かも知れません。
対して WinNT では実は Unicode

struct HWND_Internal_WinNT
{
  wchar_t *pszwText;
  // 以下略
};

かも知れません(あくまで「かも知れません」です)。

これに直にアクセスさせると、ユーザ側で変換を行わなければなりませんが、
直にアクセスさせずに API を通せば…

Win95 の GetWindowTextA
int WINAPI GetWindowTextA( HWND hWnd, LPSTR lpString, int nMaxCount )
{
  HWND_Internal_Win95 * pHWnd =
    reinterpret_cast< HWND_Internal_Win95 * >( hWnd );

  strncpy( lpString, pHWnd->pszText, nMaxCount );
  return min( nMaxCount, strlen( pHWnd->pszText ) );
}

Win95 の GetWindowTextW
int WINAPI GetWindowTextW( HWND hWnd, LPWSTR lpString, int nMaxCount )
{
  HWND_Internal_Win95 * pHWnd =
    reinterpret_cast< HWND_Internal_Win95 * >( hWnd );

  return MultiByteToWideChar(
    CP_ACP, 0, pHWnd->pszText, -1, lpString, nMaxCount );
}

WinNT の GetWindowTextA
int WINAPI GetWindowTextA( HWND hWnd, LPSTR lpString, int nMaxCount )
{
  HWND_Internal_WinNT * pHWnd =
    reinterpret_cast< HWND_Internal_WinNT * >( hWnd );

  return WideCharToMultiByte(
    CP_ACP, 0, pHWnd->pszwText, -1, lpString, nMaxCount, NULL, NULL );
}

WinNT の GetWindowTextW
int WINAPI GetWindowTextW( HWND hWnd, LPWSTR lpString, int nMaxCount )
{
  HWND_Internal_WinNT * pHWnd =
    reinterpret_cast< HWND_Internal_WinNT * >( hWnd );

  wcsncpy( lpString, pHWnd->pszwText, nMaxCount );
  return min( nMaxCount, wcslen( pHWnd->pszwText ) );
}

もしもこうなっていれば、ユーザ側は内部構造体がどんな形をしているかを意識せずに
プログラミングすることが可能ですよね。

>> HWND型を詳しく書いた本がないのも何か理由があるのでしょうか。
> OS によって変わることが予想されますので、このメンバに直接アクセスさせたく
> ありません。

ってのは、こういう意図もあるのではないでしょうか。
あくまでも予想ですけど。

シャノン 2004/09/06(月) 10:49:02
あれれ?
メンバのない構造体が宣言できちゃった。
規格ではできるのか…

でも、HWND をはじめとするハンドル型はメンバ名が unused なので、結局
> このメンバは使われていない、あるいは使ってはいけないメンバです。
であることに変わりはないと思います。

シャノン 2004/09/06(月) 11:21:25
HANDLE 型と HWND 型の詳細とか。
http://www.tietew.jp/cppll/archive/8076

ただし、Tietew さんには申し訳ないですが、これを信用しない方がいいと思います。
理由は前述の通り、未来永劫こうであるという保証はないから。

PATIO 2004/09/06(月) 11:37:00
知識として知っておくのはよいと思うんですが、
知っていても実際のコーディング上に活かせる知識ではないと思うので
あんまり深入りしてもどうかなぁと思ったりします。
そのOSでの管理方法とかそういう物が知りたいと言う話なら
良いと思いますけれどね。

とある掲示板でHANDLEは何らかのポインターだからと言う認識で
WaitForSingleObjectに実際にはHANDLEではないポインターを渡して
うまく行かないと悩むプログラマーの話がありましたけれど、
HANDLEはあくまでも対象を識別するための識別子であるという認識で
よいと思います。
確かに実装上はそれを特定するための情報が入った構造体へのポインタであったり、
認識用のシーケンス番号だったりするんでしょうけれど、
それをあてにしたコーディングはOSの仕様が変更になったり、
使用するプラットホームが変更になったりした時点で無意味になります。
実装はどうあれ、HANDLE型はHANDLE型であり、それ以外の何者でもない
という認識でコードを書くのが正解だと思います。

まあ、豆知識ぐらいの意識でいた方が良いと思いますね。
内容的にはちっとも豆知識ではないと思いますけれど。
内容的には深い内容になりますしねぇ。

シャノン 2004/09/06(月) 11:50:38
> HANDLEはあくまでも対象を識別するための識別子であるという認識で
> よいと思います。

あながち間違いでもないんですけども。
細かいことを言えば「ハンドルとは対象のオブジェクトを操作するためのもの」だと思
います。
車で曲がるときに、タイヤを直接触るのではなくハンドルを回すように。
タイヤを間接的に操作するためのものが車のハンドルであるのと同じく
オブジェクトを間接的に操作するのがオブジェクトハンドルであると思います。

なぜ識別子ではないのかと言うと…
ウィンドウハンドルなんかでは、ハンドルが識別子も兼ねているようですが
プロセスやスレッドだと、プロセスIDとプロセスハンドルが別々にあるからです。

IDは識別するためのもの、ハンドルは操作するためのもの。
ハンドルがIDを兼ねる場合もあります、という理解が理想ではないかと思います。

PATIO 2004/09/06(月) 13:16:56
ですね。

IDが識別子で、HANDLEが操作するための物ですね。

パンダ 2004/09/06(月) 17:07:48
皆さん、ありがとうございます。
OSによってかわる、ということと、初心者にふれさせたくないものという説明で少し納得しまし
た。
説明すればきりが無く、また難しくなるので、本などでは深入りしていないのでしょう。
車などでも慣れれば内部の修理の必要にせまられたりしますのでメカニズムもしらべたりしま
す。その時、ある程度わかれば安心なんです。それと同じ気持ちでしらべてみたのです。
ハンドル型(HWNDも含めて)はそれぞれ特有の構造体をもっていて、それえのポインタであると
考えるのが妥当なところなのでしょうか。

επιστημη [E-Mail] [HomePage] 2004/09/06(月) 17:28:39
FILE* fp = fopen(...); の FILE と同じですよ。
FILEの詳細は知らなくていいし触るものじゃないし触ってはいけない。

シャノン 2004/09/06(月) 18:04:59
オブジェクト指向では、情報隠蔽という考え方が結構重要視されます。
製作者以外が知らなくていいことは他人には教えない、ということです。
HWND についても、これと同じことが言えます。

> OSによってかわる、ということと、初心者にふれさせたくないものという説明で
> 少し納得しました。

初心者に触れさせたくないのではありません。
部外者に触れさせたくないのです。
いくら熟練したプログラマであろうと、それは知らなくていいことなのです。
HWND 構造体の詳細を知っている必要があるのは、Win32 API の設計者くらいのもので
す。

> 車などでも慣れれば内部の修理の必要にせまられたりしますのでメカニズムも
> しらべたりします。その時、ある程度わかれば安心なんです。それと同じ気持ちで
> しらべてみたのです。

確かに専門の修理工やエンジニアほどの知識がないアマチュアでも、車の改造くらいは
できるでしょうし、している方も少なくないでしょう。
しかし、こと HWND についての場合、OS についての深い造詣が必要になります。
おそらく、車のエンジンを載せ替えるようなレベルではなく、エンジンを設計するよう
なレベルの知識を要求されるのではないでしょうか。

> ハンドル型(HWNDも含めて)はそれぞれ特有の構造体をもっていて、それえのポインタ
> であると考えるのが妥当なところなのでしょうか。

いいえ。
今は実体は確かに何らかの構造体へのポインタであるのかもしれませんが、繰り返し言
っている通り、それが将来も何らかのポインタである保証はどこにもありません。

HWND 型に要求されることは、それがウィンドウを一意に識別し、それを通じてウィンド
ウの操作や情報取得ができるということだけです。
この要件さえ満たせば、何らかの構造体のポインタである必要はありません。

実際にはポインタであるのかもしれませんし、ポインタは所詮整数でしかありません。
しかし、ポインタを整数としては扱わない(乗除算はしない)ように、HWND 型もポイン
タでも整数でもなく、ただ HWND 型(より一般的な言い方をするなら「ハンドル」ある
いは「識別子」)として扱うのが、もっとも無難であると思います。

古いスレッドには追加発言できません。