Windowsでリソースについて議論するときの「ハンドル」とは何ですか?彼らはどのように機能しますか?
多くの場合、メモリ、開いているファイル、またはパイプへのリソースへの抽象的な参照値です。
適切、Windowsの場合(および一般的にはコンピューティングの場合)、ハンドルはAPIユーザーから実際のメモリアドレスを隠し、システムがプログラムに対して透過的に物理メモリを再編成できるようにする抽象化です。ハンドルをポインターに解決するとメモリがロックされ、ハンドルを解放するとポインターが無効になります。この場合、ポインターのテーブルへのインデックスと考えてください。システムAPI呼び出しにインデックスを使用すると、システムはテーブル内のポインターを自由に変更できます。
あるいは、APIライターが、APIのユーザーを、アドレスが返したものの詳細から隔離することを意図している場合、実際のポインターをハンドルとして与えることができます。この場合、ハンドルが指すものはいつでも変化する可能性があることを考慮する必要があります(APIバージョン間、またはハンドルを返すAPIの呼び出しから呼び出しまで)。したがって、ハンドルは単に不透明な値として扱われるべきです。 APIにとって意味のあるのみ。
最新のオペレーティングシステムでは、いわゆる「実際のポインター」でさえ、プロセスの仮想メモリ空間への不透明なハンドルであり、O/Sがプロセス内のポインターを無効にすることなくメモリを管理および再配置できるようにする必要があります。
HANDLE
は、コンテキスト固有の一意の識別子です。コンテキスト固有とは、あるコンテキストから取得したハンドルを、HANDLE
sでも機能する他の任意のコンテキストで必ずしも使用できないことを意味します。
たとえば、GetModuleHandle
は、現在ロードされているモジュールに一意の識別子を返します。返されたハンドルは、モジュールハンドルを受け入れる他の関数で使用できます。他の種類のハンドルを必要とする関数には指定できません。たとえば、GetModuleHandle
からHeapDestroy
に返されるハンドルを指定して、適切な操作を行うことはできません。
HANDLE
自体は単なる整数型です。通常、必ずというわけではありませんが、基本的な型またはメモリの場所へのポインタです。たとえば、HANDLE
によって返されるGetModuleHandle
は、実際にはモジュールのベース仮想メモリアドレスへのポインターです。ただし、ハンドルはポインターでなければならないという規則はありません。ハンドルは、単純な整数にすることもできます(おそらく、Win32 APIによって配列へのインデックスとして使用できます)。
HANDLE
sは、内部Win32リソースからのカプセル化と抽象化を提供する意図的に不透明な表現です。このように、Win32 APIは、ユーザーコードに何らかの影響を与えることなく、潜在的にHANDLEの背後にある基本型を変更できます(少なくともそれはアイデアです)。
作成したばかりのWin32 APIのこれら3つの異なる内部実装を検討し、Widget
がstruct
であると仮定します。
Widget * GetWidget (std::string name)
{
Widget *w;
w = findWidget(name);
return w;
}
void * GetWidget (std::string name)
{
Widget *w;
w = findWidget(name);
return reinterpret_cast<void *>(w);
}
typedef void * HANDLE;
HANDLE GetWidget (std::string name)
{
Widget *w;
w = findWidget(name);
return reinterpret_cast<HANDLE>(w);
}
最初の例は、APIに関する内部の詳細を公開します。これにより、ユーザーコードは、GetWidget
がstruct Widget
へのポインターを返すことを知ることができます。これにはいくつかの結果があります。
Widget
構造体を定義するヘッダーファイルにアクセスできる必要があります。Widget
構造体の内部部分を潜在的に変更できます。これらの結果は両方とも望ましくない場合があります。
2番目の例は、void *
のみを返すことにより、この内部詳細をユーザーコードから隠します。ユーザーコードは、Widget
構造体を定義するヘッダーにアクセスする必要はありません。
3番目の例は2番目の例とまったく同じですが、代わりにvoid *
a HANDLE
を呼び出すだけです。おそらく、これはユーザーコードがvoid *
が指しているものを正確に把握しようとすることを思いとどまらせるでしょう。
なぜこの問題を経験するのですか?この同じAPIの新しいバージョンのこの4番目の例を考えてみましょう。
typedef void * HANDLE;
HANDLE GetWidget (std::string name)
{
NewImprovedWidget *w;
w = findImprovedWidget(name);
return reinterpret_cast<HANDLE>(w);
}
関数のインターフェイスは上記の3番目の例と同じであることに注意してください。これは、「舞台裏」実装がNewImprovedWidget
構造体を代わりに使用するように変更された場合でも、ユーザーコードが変更なしにこの新しいバージョンのAPIを使用し続けることができることを意味します。
これらの例のハンドルは、実際にはvoid *
の新しい、おそらくより親しみやすい名前です。これは、Win32 APIのHANDLE
とまったく同じです(ルックアップ MSDNで )。ユーザーコードとWin32ライブラリの内部表現の間に不透明な壁を提供し、Win32 APIを使用するコードのWindowsのバージョン間での移植性を高めます。
Win32プログラミングのハンドルは、Windowsカーネルによって管理されるリソースを表すトークンです。ハンドルは、ウィンドウ、ファイルなどになります。
ハンドルは、Win32 APIを使用して作業する微粒子リソースを識別するための単なる方法です。
そのため、たとえば、ウィンドウを作成して画面に表示する場合は、次のことができます。
// Create the window
HWND hwnd = CreateWindow(...);
if (!hwnd)
return; // hwnd not created
// Show the window.
ShowWindow(hwnd, SW_SHOW);
上記の例では、HWNDは「ウィンドウへのハンドル」を意味します。
オブジェクト指向言語に慣れている場合、HANDLEは、他の関数によってのみ状態を変更できるメソッドを持たないクラスのインスタンスと考えることができます。この場合、ShowWindow関数はウィンドウハンドルの状態を変更します。
詳細については、「 ハンドルとデータ型 」を参照してください。
ハンドルは、Windowsによって管理されるオブジェクトの一意の識別子です。 ポインターのようなですが、ポインターではないは、ユーザーコードによって間接参照されて何らかのデータにアクセスできるアドレスではないという意味です。代わりに、ハンドルが識別するオブジェクトのアクションを実行できる一連の関数にハンドルが渡されます。
ハンドルは、データベース内のレコードの主キー値のようなものです。
編集1:まあ、ダウンキー、主キーがデータベースレコードを一意に識別し、Windowsシステムのハンドルがウィンドウ、開かれたファイルなどを一意に識別する理由、それが私が言っていることです。
したがって、最も基本的なレベルでは、あらゆる種類のハンドルは、ポインターへのポインターまたは
#define HANDLE void **
なぜそれを使用したいのかについて
設定してみましょう:
class Object{
int Value;
}
class LargeObj{
char * val;
LargeObj()
{
val = malloc(2048 * 1000);
}
}
void foo(Object bar){
LargeObj lo = new LargeObj();
bar.Value++;
}
void main()
{
Object obj = new Object();
obj.val = 1;
foo(obj);
printf("%d", obj.val);
}
そのため、objはfooに値で渡され(コピーを作成して関数に渡す)、printfは元の値1を出力します。
Fooを次のように更新すると:
void foo(Object * bar)
{
LargeObj lo = new LargeObj();
bar->val++;
}
Printfが更新された値2を出力する可能性があります。しかし、fooが何らかの形でメモリの破損または例外を引き起こす可能性もあります。
これは、ポインタを使用してobjをメモリ2 MBを割り当てている関数に渡しているため、OSがメモリを移動してobjの位置を更新する可能性があるためです。値でポインタを渡したため、objが移動すると、OSはポインタを更新しますが、関数内のコピーは更新せず、潜在的に問題を引き起こします。
Fooの最終更新:
void foo(Object **bar){
LargeObj lo = LargeObj();
Object * b = &bar;
b->val++;
}
これにより、常に更新された値が出力されます。
コンパイラがポインタにメモリを割り当てると、それらを不動としてマークするので、関数に渡された値が割り当てられたラージオブジェクトに起因するメモリの再シャッフルは、メモリ内の最終的な場所を見つけるために正しいアドレスを指します更新。
特定の種類のハンドル(hWnd、FILEなど)はドメイン固有であり、メモリ破損から保護するための特定の種類の構造を指します。
Windowsのウィンドウは、それを記述する構造体であると考えてください。この構造体はWindowsの内部部分であり、その詳細を知る必要はありません。代わりに、Windowsはその構造体の構造体へのポインターのtypedefを提供します。これが、ウィンドウを保持するための「ハンドル」です。