この記事は機械翻訳されたものです。
Windows と C++
C++ と Windows API (機械翻訳)
Kenny Kerr
Windows API は、C++ 開発者には挑戦を示します。 API を構成する各種のライブラリはほとんどの部分については、C スタイルの関数とハンドルまたは COM スタイルのインターフェイスとして公開されています。 これらのどちらもで動作する非常に便利ですし、いくつかのカプセル化または間接参照のレベルが必要です。
C++ 開発者のための挑戦のカプセル化のレベルを決定することです。 ライブラリと C++ ライブラリでの展示はパターンですので MFC と ATL のすべてのクラスとメンバー関数は、それを包むために傾斜するかもしれないように育った開発者彼らには長い間依存してきました。 他の開発者は、カプセル化の任意の並べ替えにばかにして、ちょうど raw 機能、ハンドル、インターフェイスを直接使用可能性があります。 間違いなくこれら他の開発者は本当に C++ 開発者が単に C++ 開発者のアイデンティティの問題ではないです。 現代の C++ 開発者のより自然な妥協点があると思います。
ここでの私のコラムを再起動MSDN Magazineは、C を使用する方法を説明します + + 0 x、または C++ 2011 する可能性が高いことという、ネイティブの Windows ソフトウェア開発の暗黒時代の芸術を解除するには、Windows API と共に。 次の数ヶ月の Windows スレッド プール API の拡張のツアーを取るつもりです。 に沿って続くし、ファンシーな新しい言語や複雑なまたは高価なランタイムを必要とせず驚くほどスケーラブルなアプリケーションを作成する方法を発見するでしょいます。 必要がありますは素晴らしいの Visual C コンパイラ、Windows API と欲望あなたの工芸品をマスターすることだけです。
良いプロジェクトと同様に、良いスタートを切るにいくつかの基礎が必要です。 方法については、「Windows API をラップするには」んだ? むしろすべての後続の列のこれらの詳細を動きが取れなくなるよりも、私は私この列と単にビルド推奨このスペル今後つもりです。 次のいくつかの列を必要に応じて won ' t は、私は、当分の間、COM スタイルのインターフェイスの問題を残しておきます。
Windows API には、C スタイルの関数のセットを公開する多くのライブラリのハンドルと呼ばれる 1 つまたは複数の不透明ポインターで構成されています。 これらのハンドルは、通常、ライブラリまたはシステムのリソースを表します。 作成、操作、およびハンドルを使用してリソースを解放する関数が用意されています。 例として、CreateEvent 関数はイベント オブジェクトにハンドルを返す、イベント オブジェクトを作成します。 ハンドルを解放して、システムに通知するには、イベントを使用してオブジェクト、単にパスのハンドルを CloseHandle 関数。 他保留状態のハンドルを同じのイベント オブジェクトがない場合は、システムはそれを破棄します。
auto h = CreateEvent( ...
); CloseHandle(h);
新しい c++
C++ の 2011 年には新しいなら、私は自動キーワード初期化式から変数の型を推測するコンパイラを指示すること指摘してください。 式の型がわからない場合多くの場合に便利ですメタプログラミング、またはちょうど場合いくつかのキーストロークを保存します。
しかし、ほとんどないこのようなコードを書く必要があります。 間違いなく、単一の最も貴重な C++ の機能提供クラスです。 テンプレートはクールだが、標準テンプレート ライブラリ (STL) 魔法ですが、なしクラス C で他の何も意味します。 クラスは、C++ プログラムをものに簡潔で信頼性の高いです。 私は仮想関数と継承と空想の他の機能についての話ではないです。 私はちょうど、コンス トラクターとデストラクターについて話しています。 多くの場合はすべてが必要し、推測何ですか? 何も要しない。 実際には、例外処理、によって課せられたオーバーヘッドに注意する必要があるし、私はこのコラムの最後に説明します。
Windows API を飼いならされた、モダンな C++ 開発者にアクセスできるようにするには、ハンドルをカプセル化するクラスが必要です。 はい、あなた好みの C++ ライブラリ既にハンドルのラッパーがあります C++ 2011 を地上から設計されたか。 確実これらのハンドルは、STL コンテナーに保存し、できますそれら彼らの所有者のトラックを失うことなくがあなたのプログラムの周りを渡すか?
C++ クラスの完全なハンドルです。 私は「オブジェクト」と言うしていないに注意してくださいハンドル オブジェクトのプログラム内で、オブジェクト自体ではよくないことを覚えてください。 ハンドル何羊飼いが必要です — オブジェクトではありません。 時々、1 対 1 関係を Windows API オブジェクトと C++ クラス、便利なことが、別の問題。
ハンドル通常不透明、まだ別の種類のハンドルと多くの場合、微妙な意味は違いにもかかわらず、適切に一般的な方法でハンドルをラップするクラス テンプレートが必要となります。 ハンドルの型と特定の特性またはハンドルの特性を指定するテンプレート パラメーターが必要です。
C++ では、特徴クラスは、通常、特定の型についての情報を提供するために使用されます。 この方法では正規表現をハンドルの 1 つのクラス テンプレートを作成し、さまざまな種類のハンドルは、Windows API の異なる特徴クラスを提供に分類できます。 ハンドルの特徴クラスもハンドル クラス テンプレートに自動的に、必要に応じて解放できるように、ハンドルが解放される方法を定義する必要があります。 など、特徴クラス イベント ハンドルはここです:
struct handle_traits
{
static HANDLE invalid() throw()
{
return nullptr;
}
static void close(HANDLE value) throw()
{
CloseHandle(value);
}
};
多くのライブラリは、Windows API でこれらのセマンティクスを共有するためだけのイベント オブジェクトを使用できます。 このように、特徴クラスの静的メンバー関数だけで構成されます。 コンパイラできますは簡単にインライン コードとオーバーヘッドはありませんが導入、多大な柔軟性を提供しながらになりますメタプログラミング。
無効に、無効なハンドルの値を返します。 通常は、nullptr、C++ 2011 年には、null ポインター値を表す新しいキーワード。 テンプレートと関数のオーバー ロードとも動作するように伝統的な選択肢とは異なり、nullptr 厳密に型指定されます。 特徴クラスに無効な関数を含めることをあるのでが無効なハンドル nullptr、以外のものとして定義されて場合があります。 終了関数は、ハンドルによって終了またはリリース機構をカプセル化します。
特徴クラスの概要を与え、私はすることができます先に行くし、ハンドルのクラス テンプレートの定義内のように起動図 1。
図 1ハンドル クラス テンプレート
template <typename Type, typename Traits>
class unique_handle
{
unique_handle(unique_handle const &);
unique_handle & operator=(unique_handle const &);
void close() throw()
{
if (*this)
{
Traits::close(m_value);
}
}
Type m_value;
public:
explicit unique_handle(Type value = Traits::invalid()) throw() :
m_value(value)
{
}
~unique_handle() throw()
{
close();
}
標準的な unique_ptr クラス テンプレートに似ていますので私はそれ unique_handle つけ。 多くのライブラリも同じハンドルの型を使用して場合、単にハンドルを呼ばれるので、一般的にほとんどの typedef を提供する意味のセマンティクスを使用します。
typedef unique_handle<HANDLE, handle_traits> handle;
イベント オブジェクトを作成して「それを処理」はできます。
handle h(CreateEvent( ... ));
コピー コンス トラクターを宣言したします] をクリックし、コピー代入演算子にプライベート、未実装の左に。 彼らはほとんどのハンドルを適切なこのコンパイラに自動的にそれらの生成から防ぎます。 特定の種類のハンドルをコピーするには、Windows API をできますが、これは非常に異なる概念 C++ コピー セマンティクスです。
コンス トラクターの value パラメーターの既定値を提供するために、特徴クラスに依存しています。 デストラクターの呼び出し、プライベートが必要な場合、ハンドルを閉じるには、特徴クラスに依存するメンバー関数を終了します。 この方法では、スタックに優しい、例外安全なハンドルがあります。
しかし、私はまだ完了ではありません。 Close メンバー関数では、ハンドルを終了する必要があるかどうかを決定するブール型変換の存在に依存しています。 C++ 2011 の明示的な変換関数が導入されていますが、私はそれ以外のコンパイラを許可、恐ろしいの暗黙の型変換を避けるために、一般的なアプローチをブール値に変換を使用できるこのまだ Visual の C++ では、ありません。
private:
struct boolean_struct { int member; };
typedef int boolean_struct::* boolean_type;
bool operator==(unique_handle const &);
bool operator!=(unique_handle const &);
public:
operator boolean_type() const throw()
{
return Traits::invalid() != m_value ? &boolean_struct::member : nullptr;
}
つまり、今単には、有効なハンドルがあるかどうかが見過ごされることに危険な変換を許可することがなくテストできます。
unique_handle<SOCKET, socket_traits> socket;
unique_handle<HANDLE, handle_traits> event;
if (socket && event) {} // Are both valid?
if (!event) {} // Is event invalid?
int i = socket; // Compiler error!
if (socket == event) {} // Compiler error!
明らかの演算子 bool を使用して気付かれずに、最後の 2 つのエラーを許可しただりましょう。 ただし、1 つのソケットを相互に比較すること、— したがっていずれかする必要が明示的に、等値演算子を実装またはプライベートとして宣言し、未実装のまま。
ハンドルは標準的な unique_ptr クラス テンプレートの方法に似ています、unique_handle を所有している方法は、オブジェクトを所有している、そのオブジェクトへのポインターを管理します。 おなじみの取得を提供に意味をリセットし、基になるハンドルを管理するメンバー関数のリリースします。 Get 関数は簡単です。
Type get() const throw()
{
return m_value;
}
リセット機能は少しより多くの仕事ですが、どのような私はすでに議論したビルドします。
bool reset(Type value = Traits::invalid()) throw()
{
if (m_value != value)
{
close();
m_value = value;
}
return *this;
}
リセット機能をわずかに変更するオブジェクトは、有効なハンドルがリセットされているかどうかを示すブール値を返す unique_ptr によって提供されるパターンからの自由を撮影しました。 これは私はすぐに戻るエラー処理に便利です。 リリース関数は現在明らかなべきであります。
Type release() throw()
{
auto value = m_value;
m_value = Traits::invalid();
return value;
}
対をコピーします。 移動
コピーと移動の形式を検討するでしょいます。 私はすでにのハンドルのコピー セマンティクスを禁止したため、移動の形式を許可する意味があります。 これは、STL コンテナーのハンドルを格納する場合に必須となります。 これらのコンテナーは、伝統的なコピーのセマンティクスが C++ の 2011 年の導入に依存している、セマンティクスはサポートされてを移動します。
移動のセマンティクスと右辺値の参照の長い説明にすることなく、がアイデアの 1 つのオブジェクトから別の方法では、開発者を予測可能であり、ライブラリの作者とコンパイラのコヒーレントに渡すオブジェクトの値です。
C++ 2011 年前に、開発者の過剰な思い入れを避けるために複雑なトリックのすべての種類に依頼しなければならなかったが、言語 — と拡張、STL — オブジェクトのコピーをしています。 コンパイラは多くの場合、オブジェクトのコピーを作成し、すぐにオリジナルの破壊します。 オブジェクトは使用できなく、開発者を宣言することができます移動セマンティクスとその値を他の場所で移動はポインター スワップのほとんど。
いくつかの場合では、開発者を明示的に指定し、これを指定する; より頻繁にはコンパイラできます移動対応オブジェクトを利用し、前になかっためちゃくちゃ効率の最適化を実行します。 良いニュースは、独自のクラス移動セマンティクスを有効にする簡単です。 だけをコピーするだけに依存コピー コンス トラクターおよびコピー代入演算子のセマンティクスに依存移動コンス トラクターと移動の代入演算子に移動します。
unique_handle(unique_handle && other) throw() :
m_value(other.release())
{
}
unique_handle & operator=(unique_handle && other) throw()
{
reset(other.release());
return *this;
}
右辺値の参照
C++ 2011 参照右辺値参照と呼ばれる、新しい種類を紹介します。 それを使用して宣言されている・ ・ これは何の unique_handle メンバーは、上記のコードで使用されているです。 参照のような昔の今と左辺値参照呼ばが、新しいの右辺値参照の初期化には多少異なるルールを展示し、オーバー ロードの解決。 今のところ、私はそれにお任せします (私はこのトピックに後で戻る)。 移動のセマンティクスのハンドルのこの段階での主な利点は正しくかつ効率的にハンドル STL コンテナーに格納できることです。
エラー処理
Unique_handle クラス テンプレートには。 最後のトピック今月 — と先の列を準備する — エラー処理です。 私たちの延々 とエラー コードと例外の賛否両論について議論がありますが、標準の C ライブラリを採用する場合にだけ例外を使用する必要があります。 もちろん、妥協が必要なので、Windows API のエラー コードを使用します。
エラー処理に私のアプローチとして、可能と例外安全なコードの記述が例外をキャッチしないようにすることです。 例外ハンドラーがない場合は、死後をデバッグできるように、クラッシュのミニダンプを含むエラー報告 Windows が自動的に生成されます。 のみ予期しない実行時エラーが発生し、エラー コードの他のすべてを処理する場合は、例外をスローします。 例外がスローされたときに、あなたをコード内のバグ、またはコンピューターに降りかかったいくつか大惨事です知っています。
私は例は Windows レジストリへのアクセスです。 レジストリに値を書き込むには、通常賢く、プログラムで処理するは難しいだろう大きな問題の症状です。 これは、例外が発生する必要があります。 レジストリからの値の読み取りに失敗する必要がありますが予想し、正常に処理します。 これは、例外が発生べきではないが、かどうか、またはなぜ値を読み取ることができませんでしたを示す、bool または列挙型の値を返します。
Windows API はそのエラーの処理を特に一貫性のあるではない; 長年にわたって進化して、API の結果です。 ほとんどの部分については、エラーは、BOOL または HRESULT の値として返されます。 いくつかの他、文書化された値の戻り値を比較することによって明示的に処理する傾向があります。
特定の関数呼び出しは引き続き確実に動作する私のプログラムを成功する必要があります決定する場合に記載されている関数の使用図 2の戻り値をチェックします。
図 2戻り値のチェック
inline void check_bool(BOOL result)
{
if (!result)
{
throw check_failed(GetLastError());
}
}
inline void check_bool(bool result)
{
if (!result)
{
throw check_failed(GetLastError());
}
}
inline void check_hr(HRESULT result)
{
if (S_OK != result)
{
throw check_failed(result);
}
}
template <typename T>
void check(T expected, T actual)
{
if (expected != actual)
{
throw check_failed(0);
}
}
これらの関数についての言及する価値には、2 つものの方法があります。 最初は、当然の BOOL への暗黙の変換はできません、ハンドル オブジェクトの妥当性をチェックすることもできますので、check_bool 関数がオーバー ロードされることです。 2 番目は S_OK に対してよりもむしろより共通の SUCCEEDED マクロを使用して明示的に比較する、check_hr 関数です。 これはサイレントことはほとんどないもの、開発者期待は他の怪しげな成功コード、S_FALSE などを受け入れるを回避できます。
これらの書き込みは私の最初試みチェック関数のオーバー ロードのセットでした。 私は、さまざまなプロジェクトでの使用とは、それらすべての仕事とオーバー ロードの作成ができないように Windows API に単にあまりにも多くの結果の型とマクロを定義して気付いた。 したがって、装飾された関数名。 私はエラーが予期しないオーバー ロードの解決のためにキャッチされていたされないいくつかの例が見つかりました。 スローされている check_failed タイプはかなり単純です。
struct check_failed
{
explicit check_failed(long result) :
error(result)
{
}
long error;
};
それすべての種類のエラー メッセージのサポートを追加するようなファンシーな機能を飾ることができますが、ポイントは何ですか? 私は簡単に、死体解剖は、クラッシュしたアプリケーションを実行するとき選択するは、エラー値が含まれます。 を超えて、それだけの方法で取得することです。
これら特定チェック関数はイベント オブジェクトを作成して問題が起きた場合、例外をスロー、信号します。
handle h(CreateEvent( ... ));
check_bool(h);
check_bool(SetEvent(h.get()));
例外処理
その他の問題の例外処理効率を懸念します。 開発者しますはいくつかの前提を保持しないためにはの実録します。
例外処理のコストは、2 つの領域で発生します。 最初の例外のスローされます。 これは、エラー コードを使用するよりも遅くなる傾向にあり、致命的なエラーが発生した場合のみ例外をスローする必要があります理由の 1 つ。 すべてがうまくいけば、この代償を払うことはありませんよ。
パフォーマンスの問題の 2 番目と一般的な原因は、適切なデストラクターと呼ばれる、万一の場合に例外がスローされることを保証する、ランタイム オーバーヘッドを行う必要があります。 コードは追跡するは、デストラクターを実行する必要があります必要です; もちろん、これは、大規模なコードのパフォーマンスに大きく影響することができます、履歴のサイズも増加します。 かどうかは、例外がスローされます実際にはこのコストを支払うこと、だからこれを最小限に良好なパフォーマンスを確保することが不可欠です。
コンパイラが関数の良いアイデアがあることを確保することを意味可能性がある例外をスローできます。 コンパイラを証明することができますが特定の関数からの例外がないこと、それはコードを最適化することができますを定義して、スタックを管理するを生成します。 これは全体のハンドルのクラス テンプレートを装飾し、特徴クラスのメンバー関数、例外の仕様です。 C++ の 2011 年に廃止されましたが、重要なプラットフォーム固有の最適化です。
今月は。 Windows API を使用して信頼性の高いプログラムを書くための主要な成分が今あります。 私は、Windows スレッド プール API を探索を開始私次の月に参加します。
Kenny Kerr はソフトウェアは、職人、情熱をネイティブ Windows 開発。彼に到達kennykerr.ca。