Monday, June 17, 2013

定義@Boost.Log 2.0

Definitions@Boost.Log 2.0 の翻訳

以下は、この文書で広く使用される用語の定義である。

  • ログレコード
    • 利用者のアプリケーションによって集められた一塊の情報で、ログに含まれる。単純なログレコードの例は、ロギングライブラリーによって処理された後のログファイルにおける、1行のテキストによって表現される。
  • 属性
    • 属性は、ログレコードを特徴付けるために使用することができるメタ情報のひとつである。Boost.Logにおける属性は、特定のインターフェースを持つ関数オブジェクトによって表現される。このインターフェースは、それが呼び出されたときに実際の属性値を返すものである。
  • 属性値
    • 属性値は、属性によって獲得された実際のデータである。このデータは、特定のログレコードに付与され、このライブラリーによって処理される。値は異なる型(整数、文字列、およびユーザー定義の型のようなより複雑な型)を持つ事ができる。属性値の例としては:現在時刻、ファイル名、行番号、現在のスコープ名、などがある。属性値はtype erasing wrapperに包まれているために、属性の実際の型はインターフェースからは不可視である。値の実際の(隠された)型は、時折「stored type」と呼ばれる。
  • (属性)値の確認
    • 属性値を処理する方法。この手法は、属性値に適用した関数オブジェクト(visitor)を使用する。visitorは、属性値を処理するために、属性値のstored typeを知っているべきである。
  • (属性)値の展開
    • 呼び出し元が保存された値を得ようとした際に、属性値を処理する方法。呼び出し元は、属性値を取り出すために、属性値のstored typeを知っているべきである。
  • ログ・シンク
    • 利用者アプリケーションから集められた全てのログレコードが送り込まれる場所。シンクは、ログレコードがどこに保存され、どのように処理されるかを定義する。
  • ログ・ソース
    • 利用者アプリケーションがログレコードを生成する場所。単純な例では、オブジェクト(logger)である。このオブジェクトは、利用者の要求に基づいてログレコードを構成するために使用される、一式の属性を管理する。しかしながら、(たとえば、他のアプリケーションのコンソール出力を解析し解釈することによって)何か付帯的なイベントにログレコードを埋め込むソースを作成することもできる。
  • ログ・フィルター
    • ログレコードを受け取り、それを保存するか破棄するかを決定するために使用する述語。通常この述語は、そのレコードに付与された属性値に基づいてその判断を行う。
  • ログ・フォーマッター
    • ログレコードの最終的なテキスト出力を生成する関数オブジェクト。いくつかのシンク、例えば、バイナリー・シンクではこれは必要ないかもしれないが、ほぼあらゆるテキストベースのシンクは、その出力を構成するためにフォーマッターを使用する。
  • ロギング・コア
    • ソースとシンクの接続を管理し、レコードにフィルターを適用するグローバル要素。これは、主にこのロギングライブラリーが初期化されるときに使用される。
  • i18n
    • 国際化。ワイド文字を操作する機能。
  • TLS
    • スレッド・ローカル記憶域。スレッドごとに独立した変数の値を持つという概念。
  • RTTI
    • 実行時型情報。これは、dynamic_castやtypeidオペレーターが正常に動作するために必要とされるC++言語サポートのデータ構造である。

Sunday, June 16, 2013

構成とライブラリーのビルド@Boost.Log 2.0

Configuring and building the library@Boost.Log 2.0 の翻訳

このライブラリーは、個別にコンパイルされる要素を持つ。これは、スタートガイドに記述されたとおりにビルドされるべきである。特筆すべきこととして、もしあなたのアプリケーションが1つ以上のBoost.Logを使用するモジュール(たとえば、exeや1つ以上のdll)から成るのであれば、このライブラリーは共有オブジェクトとしてビルドされなければならない。もし、Boost.Logを使用する、単一の実行ファイルや単一のモジュールであるならば、あなたは、このライブラリーを静的ライブラリーとしてビルドできる。

このライブラリーはいくつかの構成マクロをサポートする:
  • BOOST_LOG_DYN_LINK
    • ユーザーコードの中で定義された場合、このライブラリーは動的にロードされるライブラリー(dllやso)であると前提する。それ以外の場合、ライブラリーは静的に組み込まれるものと前提する。このマクロは、ログをとる利用者アプリケーションのすべてのコンパイル単位で定義されるか、あるいはされないか、どちらか一方でなければならない。このマクロは、自動リンキングをサポートする環境においては、これを補助する。
  • BOOST_ALL_DYN_LINK
    • BOOST_LOG_DYN_LINKと同じだが、すべてのBoostライブラリーに同様に影響する。
  • BOOST_LOG_NO_THREADS
    • 定義された場合、マルチスレッドサポートを無効にする。これは、ライブラリーと利用者コードのコンパイルの両方に影響する。スレッドのサポートが無いことが検出された場合、このマクロは自動的に定義される。
  • BOOST_LOG_WITHOUT_CHAR
    • 定義された場合、charによってログをとる事を無効にする。これは、ライブラリーと利用者コードのコンパイルの両方に影響する。
  • BOOST_LOG_WITHOUT_WCHAR
    • 定義された場合、wchar_tによってログをとる事を無効にする。これは、ライブラリーと利用者コードのコンパイルの両方に影響する。
  • BOOST_LOG_NO_QUERY_PERFORMANCE_COUNTER
    • このマクロはWindowsにおいてのみ機能する。これは、ライブラリーと利用者コードのコンパイルの両方に影響する。定義された場合、タイマー属性において、QueryPerformanceCounter関数のサポートを無効にする。これは時間の読み取りにおける重大な非正確性をもたらす。このマクロは、古いAMD Athlon CPUにおける潜在的な問題(これこれ)を解決する事が意図されている。また、このAPIの正常な動作を失敗させる可能性のあるチップセットがあることも知られている(ここを参照せよ)。
  • BOOST_LOG_USE_NATIVE_SYSLOG
    • ライブラリーのコンパイルのみに影響する。もし何らかの理由でネイティブsyslogのサポートが自動的に検出されなかった場合、これを有効にするためにこのマクロを定義する。
  • BOOST_LOG_WITHOUT_SETTINGS_PARSERS
    • ライブラリーのコンパイルのみに影響する。定義された場合、設定のパースに関連するすべての機能がビルドされない。これはバイナリーのサイズを大きく減らす事ができる。
  • BOOST_LOG_WITHOUT_DEBUG_OUTPUT
    • ライブラリーのコンパイルのみに影響する。定義された場合、Windowsにおけるデバッガー出力のサポートがビルドされない。
  • BOOST_LOG_WITHOUT_EVENT_LOG
    • ライブラリーのコンパイルのみに影響する。定義された場合、Windowsにおけるイベントログのサポートがビルドされない。このマクロを定義する事は、メッセージコンパイラーを不要にする。
  • BOOST_LOG_WITHOUT_SYSLOG
    • ライブラリーのコンパイルのみに影響する。定義された場合、syslogバックエンドのサポートがビルドされない。
  • BOOST_LOG_NO_SHORTHAND_NAMES
    • 利用者コードのコンパイルのみに影響する。定義された場合、いくつかの非推奨の短縮マクロ名が無効になる。
  • BOOST_LOG_USE_WINNT6_API
    • ライブラリーと利用者コードのコンパイルの両方に影響する。このマクロはWindowsにおいてのみ機能する。定義された場合、このライブラリーは、より効率的なコードを生成するために、Windows NT 6(VistaとServer 2008)およびそれ以降のAPIを使用する。加えてこのマクロは、このライブラリーのいくつかの実験的な機能を有効にする。しかしながら、バイナリーはNT 6より古いWindowsでは動作しなくなる。この機能を使用するために、Platform SDK 6.0およびそれ以降が必要とされる。
  • BOOST_LOG_USE_COMPILER_TLS
    • ライブラリーのコンパイルのみに影響する。このマクロは、コンパイラー組み込みのTLS(スレッドローカル記憶域)サポートを有効にする。特定の用法の制限が許容できるならば、このマクロを定義する事でBoost.Logの性能を改善するかもしれない。以下のコメントを参照せよ。
あなたは、以下のようにして、構成マクロをbjamコマンドラインに定義できる:
bjam --with-log variant=release define=BOOST_LOG_WITHOUT_EVENT_LOG define=BOOST_LOG_USE_WINNT6_API stage

しかしながら、このライブラリーと利用者のプロジェクトの両方において自動的に定義するために、"boost/config/user.hpp"に構成マクロを定義するほうがより便利かもしれない。もし何もオプションが指定されない場合、このライブラリーはもっとも包括的な構成を試みる。この構成には、すべての文字型と対象の環境で有効な機能が含まれる。

このログライブラリーは、いくつかの、同様にビルドが必要なBoostライブラリーを使用する。これらは、Boost.FilesystemBoost.SystemBoost.DateTimeおよびBoost.Threadである。これらのビルド手順の詳細については、それぞれの文書を参照すること。

最後に言及すべき事として、このライブラリーは、ライブラリーと利用者コードのコンパイル時に実行時型情報(RTTI)が有効にされていることを要求する。これは通常、あなたのプロジェクトでRTTIのサポートが無効にされていないことを確認する以外には、何も必要としない。

コンパイラー組み込みのTLSについてのノート

広く使用されているコンパイラーの多くは、TLS(このライブラリーのいくつかの場所で使用されている)を管理するための組み込み機能をサポートしている。この機能は、C++11標準にも含まれている。一般的に、これらの組み込み機能は、Boost.ThreadやネイティブOS APIなど、あらゆる代替実装よりずっと効率的なTLSへのアクセスを許可する。しかしながらこの機能はいくつかの注意点を持つ:
  • アプリケーションの実行時に動的にロードされる共有オブジェクトにおいてTLSが定義された場合、いくつかのOSはそれらの組み込み機能の使用をサポートしない。これらのシステムには、LinuxやVistaより古いWindowsが含まれる。Windows Vistaおよびそれ以降では、この問題は存在しない。
  • TLSは、グローバルコンストラクターとデストラクターから安全にアクセスできないかもしれない。少なくとも、WindowsのMSVC 8.0ではこの問題が知られている。
このライブラリーは、この機能を有効にすることを許すBOOST_LOG_USE_COMPILER_TLS構成マクロを提供する。これは、以下の制限を代償としてライブラリーの性能を改善する:
  • 実行ファイルはBoost.Logライブラリーとリンクされなければならない。ライブラリーは実行時に動的にロードされるべきではない。
  • アプリケーションは、ロギングをグローバルコンストラクターとデストラクターから使用してはならない。

ROP攻撃に対するセキュリティ軽減

Security Mitigations for Return-Oriented Programming Attacks のメモ。

ROPの対策技術を解説する、EMET3.5βのROP対策機能において参照された論文。
対策技術は以下の2種類に分類できる。
  • コンパイラレベル
  • バイナリレベル

コンパイラレベル

CALLの検証

  • 概要
    • RETの戻り先がCALLであるかを検証する。ROPはCALLを使用せずにgadgetを実行しRETするので、ROP実行時には戻り先はCALLにならない事が多いのを利用する。
  • 欠点
    • 逆アセンブルが必要になりパフォーマンスが低下する。また逆アセンブルが難しいために検証機能の信頼性に問題がある。

定数埋め込み

  • 概要
    • CALL命令の直後に固定値を埋め込む。さらに関数エピローグに、戻り先(*esp)の内容が前述の固定値であるかを検証するコードを埋め込む。上記の「CALLの検証」と同様に機能することに加え、逆アセンブルに関連する問題が解決される。
  • 欠点
    • 全てのバイナリが同じ固定値を持たなければならない。このためには、すべてのバイナリが同一のルールに従って再コンパイルされる必要があり、現実的に不可能。

命令の改ざん

  • 概要
    • RETが存在しなくなるように命令(バイト列)を変更する。なお、純粋なRET命令以外にも、命令の途中が「RET命令として解釈できる」バイト列が存在することに配慮しつつ変更を行う。

バイナリレベル

スタックのカプセル化

  • 概要&欠点
    • (理解できず)

スタックポインターの監視

  • 概要
    • ROPの実行中は、しばしばスタックポインターがTEBに定義されたスタックの範囲を超えることを利用し、ROPにしばしば利用されるAPIをあらかじめフックしておき、それらのAPIが呼び出されたときにスタックポインターの範囲がTEBに収まっているかを検証する。
  • 欠点
    • それらのAPIが呼び出される前にスタックポインターがTEBの範囲内に戻される可能性がある。

コードのカプセル化

  • 概要
    • 各モジュールを別のコードセグメントに分割する。これによりモジュール間の遷移時に例外が発生するようになるので、この例外をハンドルして、ROPが行われてるか否かを検証する。
  • 欠点
    • 性能の低下、switch文に対する追加の処置が必要、など。一部は理解できず。

おとりコード

  • 概要
    • 各モジュールのコピーをメモリー内に作成し、オリジナルは実行不可属性を付加する。オリジナルを実行しようとすると例外が発生するので、この例外をハンドルして、ROPが行われているか否かを検証する。問題がない場合は、コピー(実行可能)を実行する。
  • 欠点
    • 性能の低下。
    • (コメント)メモリー領域の消耗が半端ないだろ、これ。

インストールと互換性@Boost.Log 2.0

Installation and compatibility@@Boost.Log 2.0 の翻訳。

  • サポートされるコンパイラーとプラットフォーム
  • 構成とライブラリーのビルド

サポートされるコンパイラーとプラットフォーム

このライブラリーは適切に準拠したコンパイラーでビルドされ、利用されるべきである。このライブラリーは以下のプラットフォームで正しくビルドされ、テストされている。
  • Windows XP, Windows Vista, Windows 7. MSVC 8.0 SP1, MSVC 9.0 およびこれ以降。
  • Linux. GCC 4.5 およびこれ以降. これより古いバージョンでも動作するかもしれないが、テストされていない。
  • Linux. Intel C++ 13.1.0.146 Build 20130121。
  • Linux. Clang 3.2。
以下のコンパイラーとプラットフォームはサポートされておらず、このライブラリーのビルドに失敗するだろう。
  • C++11コンパイラーと非C++11標準ライブラリーの組み合わせ(例えば、ClangとGCC 4.2のlibstdc++)。C++11標準ライブラリーをC++11モードで使用すること。
  • MSVC 8.0(SP1未適用)およびこれより古いバージョン。
  • GCC 4.0およびこれより古いバージョン。
  • Borland C++ 5.5.1 (free version)。これより新しいバージョンでは動作するかもしれないし、しないかもしれない。
  • Windows 9x, ME, NT4およびこれより古いバージョンはサポートされない。
Boost.LogはBoostによってサポートされるすべてのハードウェアアーキテクチャーと互換性があるべきである。しかしながら、32bit x86アーキテクチャーにおいては、このライブラリーは最小でもi586のCPUで実行することを要求する。

MinGW、CygwinおよびVisual Studio Express Edition利用者のためのノート

これらのコンパイラーでこのライブラリーをビルドするためには、特別の準備が要求される。まず、MinGWとCygwinの場合には、最新バージョンのGCCがインストールされていることを確認する。このライブラリーはGCC3.xではコンパイルに失敗するだろう。

次に、このライブラリーの一部はメッセージコンパイラー(mc.exe)を要求する。これはMinGWやCygwin、一部のバージョンのMSVC Express Editionでは利用できない。基本的には、このライブラリーのビルドスクリプトは、メッセージコンパイラーがシステムに存在しているか否かを自動的に検出し、もし存在しないなら、このライブラリーのイベントログに関係した部分を無効にする。もしイベントログのサポートが必要で、メッセージコンパイラーがシステムに無いのであれば、この問題を解決するために3種類の方法がある。推奨される方法は、オリジナルのmc.exeを入手する事だ。このツールはWindows SDKから入手できる。これはMicrosoftのサイトから無償でダウンロードできる(たとえば、ここ)。またこのツールはVisual Studio 2010 Express Editionでは利用できる。コンパイルのとき、mc.exeはPATH環境変数のいずれかを通して利用できるべきである。

別の方法は、MinGWやCygwinとともに配布されているwindmc.exeの利用を試みることだ。これはオリジナルのmc.exeの類似物だ。これを行うためには、このチケットに記述されているように、Boost.Buildのファイル(特に、tools/build/v2/tools/mc.jam ファイル)に変更を加えなくてはならないだろう。その後で、このライブラリーをビルドするためにbjamにmc-compiler=windmcオプションを指定できるようになる。

万が一、何らかの理由でメッセージコンパイラーの検出に失敗する場合は、このライブラリーをビルドするときにBOOST_LOG_WITHOUT_EVENT_LOG構成マクロを定義する事によってイベントログバックエンドのサポートを明示的に無効にすることができる。これは、メッセージコンパイラーの必要性を取り除く。より構成オプションについてはこのセクションを参照する事。

Cygwin利用者のためのノート

Cygwinのサポートは非常に予備的である。Cygwinに含まれるデフォルトのGCC(本校執筆時点で4.5.3)はコンパイルエラーのためにこのライブラリーをビルドする事が出来ない。あなたはより新しいGCCをソースからビルドしなければならないだろう。それでも、いくつかのBoost.Logの機能は有効ではない。特に、ソケットベースのsyslogバックエンドは、Boost.ASIO(これはこの環境ではコンパイルされない)に基づいているためサポートされない。しかしながら、ネイティブのsyslogは適切にサポートされる。

Saturday, June 15, 2013

この文書の読み方@Boost.Log 2.0

How to read the documentation@Boost.Log 2.0 の翻訳。

この文書は、新規および経験のあるライブラリー利用者の両方に向けられている。しかしながら、利用者は、shared_ptrやmake_shared(Boost.SmartPtr)、function(Boost.Function)といった一般的に使用されているBoostの要素に馴染みのある事が期待される。この文書の一部は、必要な場合、他のBoostライブラリーに言及する。

もしこれがこのライブラリーの最初の経験であるなら、ライブラリーの機能とアーキテクチャーに目を通すために、設計概要を読む事が推奨される。インストールとチュートリアルのセクションは、このライブラリーを試すための助けとなるだろう。チュートリアルは、ライブラリー機能の概要をサンプルコード例とともに示す。いくつかのチュートリアル手順は2つの形態、「基本」と「高度」によって示される。「基本」は典型的に、物事を行うのに最も一般的で簡単な方法が記述され、新規利用者に読まれることが推奨されている。「高度」は通常、同じ事を行うためでも、詳細な説明と追加のカスタマイズを行うための拡張された方法を示す。これはより経験のある利用者にとって有用で、一般的に、簡単な方法では要件が満たせない場合に読まれるべきである。

チュートリアルに加えて、詳細な機能の記述の章がある。これは、ライブラリーによって提供されるチュートリアルによって扱われなかった他のツールについて記述する。この章は、ケースバイケースで読むのが良い。

最後に、ライブラリーの構成要素について正式な記述を行うリファレンスがある。

この文書のコード片を単純に保つために、以下の名前空間のエイリアスが定義されているものと前提する:

namespace
logging = boost::log; namespace sinks = boost::log::sinks; namespace src = boost::log::sources; namespace expr = boost::log::expressions; namespace attrs = boost::log::attributes; namespace keywords = boost::log::keywords;


動機@Boost.Log 2.0

Motivation@Boost.Log 2.0 の翻訳

今日、アプリケーションは急速に成長し、複雑になるとともに、テストとデバッグが難しくなっている。大半のアプリケーションはリモート環境で動作し、開発者が実行を観察し、実行が失敗した際の原因を解明するチャンスは少なくなっている。加えて、もしアプリケーションの振る舞いが、デバイスの反応や他のプロセスの活動のような非同期的なイベントに強く依存している場合、ローカルでのデバッグも困難となり得る。

これに対してはログをとる事が助けとなる。アプリケーションは、その実行についての重要な全ての情報をログに保存し、何か問題が起きた場合には、この情報はプログラムの実行を分析し必要な修正を行うために使用される。アプリケーションのログをとる事の他の利点として、統計情報の収集やイベントの強調(すなわち、何かの状況やアプリケーションが面している問題を示す事)などがある。これらのタスクは多くの実世界の業務アプリケーションとって非常に重要であることが分かっている。

このライブラリーは、アプリケーション開発者がログをとることを容易にすることを目的にしている。これはライブラリーを拡張するために、広範なすぐに使えるツール群を公開インターフェースとともに提供する。このライブラリーの主要な目標は:

  • 単純性。小さなコード片の例で十分に、ライブラリーの感触を得、基本的な機能を使用できるべきである。
  • 拡張性。利用者は、情報を集めてログに記録するために、ライブラリーの機能を拡張できるべきである。
  • 性能。ライブラリーは、利用者のアプリケーションに対して可能な限り小さな性能上の影響を持つべきである。

Monday, June 10, 2013

/3GBスイッチによってカーネルアドレス空間で起こる事

Kernel address space consequences of the /3GB switch@The Old New Thing の翻訳

/3GBスイッチによる不幸な結果のひとつは、それがカーネルにずっと小さな空間で動作することを強いる事である。

制限されたアドレス空間の最大の被害者がのひとつがビデオドライバーである。ビデオカード上のメモリーを管理するために、ドライバーはそれにアドレスを与えられなければならず、その要求範囲は典型的にかなり大きい。ビデオドライバーが256MBの大きさを要求したとき、その要求は、おそらく、単にこのような大きなアドレス空間を与えることができないために失敗する。

すべてのカーネルのデータは1ギガバイトの中に収まる必要がある。ページテーブル、ページディレクトリ、ビットマップ、ビデオドライバーメモリー。これは非常に厳しい押し込みだが、もしあなたが削減したいのであれば(例えば、このような大きなビデオメモリーを求めないことによって)なんとか達成できるだろう。(後の記事で、減らされたアドレス空間の、別の被害者について議論する)

これは、小さなクローゼットの中で着替えをしようとするようなものだ。それは可能だが大変な苦労だ。あなたは犠牲を払わなければならないし、その結果は、いつもとても素晴らしいというわけではない。

Sunday, June 9, 2013

カーネルハンドルは参照カウントされていない

Kernel handles are not reference-counted@The Old New Thing の翻訳。

ここにしばらく前の質問がある。

私のコードに、同じハンドルで(DeviceIoControl関数を通して)デバイスと会話したい複数のオブジェクトがある。私がオブジェクトを作るときはいつも、ハンドルの参照カウントを増やすためにDuplicateHandle関数を使う。これにより、各オブジェクトがCloseHandle関数を呼んでも、最後のオブジェクトの時だけ実際にハンドルが閉じられる。ところが実際にコードを走らせてみると、最初のオブジェクトがCloseHandle関数を呼ぶや否や、このハンドルはもはや有効ではなく、誰も使用する事が出来なくなる。これを機能させるために、どのようなフラグをCreateFile関数に渡す必要があるのか?

言い換えると、コードはこのようになっている:

// hは新しいCFredオブジェクトによって共有したいハンドル

CFred *MakeFred(HANDLE h)
{
 //  "参照カウントを増やすためにハンドルを複製する"
 //  このコードは正しくない - 以下の議論を見よ
 //  すべてのエラーチェックは説明目的のために削除した
 HANDLE hDup;
 DuplicateHandle(GetCurrentProcess(), h,
                 GetCurrentProcess(), &hDup,
                 0, FALSE, DUPLICATE_SAME_ACCESS);
 return new CFred(h);
}

カーネルハンドルは参照カウントされていない。CloseHandle関数を呼ぶと、それはハンドルを閉じる。物語はこれで終わりだ。

もとの問題の記述から、CFredオブジェクトはそれが削除されたときハンドルを閉じる事が読み取れる。純粋に議論のために、呼び出し元はこのようになっているとしよう:

CFred *pfred1 = MakeFred(h);
CFred *pfred2 = MakeFred(h);
delete pfred1;
delete pfred2;

このコードを実行したとき、実際には何が起きるのだろうか?

最初にMakeFred関数を呼ぶと、私たちはもともとのハンドルhを受け取り、それを複製するが、私たちはもとのハンドルをCFredコンストラクターに渡し、hDupはリークする。もとの投稿者は、ハンドルの複製は、単にハンドルの想像上の参照カウントをインクリメントするものだ、と想定した。つまりh == hDupであると(加えてこれは、もとの投稿者を、そもそもなぜlpTargetHandleパラメーターがあるんだ、と不思議に思わせただろう)。

pfred1が削除されたとき、それはそのハンドルhを閉じる。これはhを閉じ、それを無効にし、CreateFileや他のハンドルを作成する操作によって再利用される事を可能にする。

pfred2が削除されたとき、それもそのハンドルhを閉じる。これは既に閉じられたハンドルを閉じている。エラーだ。もし私たちが敢えてこのハンドルを使用するpfred2のメソッドを呼んだなら、そのハンドルがもはや有効ではないために、それらの操作は同様に失敗するだろう(あー、もし私たちが幸運ならエラーになるだろう。もし私たちが不幸なら、そのハンドルは再利用されていて、私たちは誰かのハンドルでDeviceIoControlを行う結果になるだろう)。

ところで、pfred1が削除されるとき、それはhを閉じるので、呼び出しコードにおけるhのコピーも良くない。

私たちが本当にここでしたい事は、ハンドルを複製し、その複製物を各オブジェクトに渡す事だ。DuplicateHandle関数は、もとのハンドルと同じオブジェクトを参照する新しいハンドルを作成する。この新しいハンドルは、もとのハンドルへの影響なしに閉じる事が出来る。

// hは新しいCFredオブジェクトによって共有したいハンドル

CFred *MakeFred(HANDLE h)
{
 //  "h"と同じオブジェクトを参照する別のハンドルを作成する
 //  すべてのエラーチェックは説明目的のために削除した
 HANDLE hDup;
 DuplicateHandle(GetCurrentProcess(), h,
                 GetCurrentProcess(), &hDup,
                 0, FALSE, DUPLICATE_SAME_ACCESS);
 return new CFred(hDup);
}

この修正は青で強調された1語だ。私たちはCFredオブジェクトに複製されたハンドルを与える。これにより、オブジェクトは独自の、いつでも閉じたいときに自由に閉じる事が出来るハンドルを得、それは他の誰のハンドルにも影響しない。

あなたはDuplicateHandleを、カーネルオブジェクト用のAddRefのようなものとみなすことができる。あなたがハンドルを複製するたびに、カーネルオブジェクトの参照カウントが1つ増え、あなたは新しい参照(新しいハンドル)を得る。あなたがハンドルを閉じるたびに、カーネルオブジェクトの参照カウントが1つ減る。

要するに、ハンドルは参照カウントされたオブジェクトではない。ハンドルを閉じると、それは無くなる。ハンドルを複製すると、あなたは、もとのハンドルを閉じなければならないという義務に加えて、その複製を閉じなければならないという新しい義務を負う。複製されたハンドルはもとのハンドルと同じオブジェクトを参照し、それが参照カウントされたオブジェクトである(なお、カーネルオブジェクトは、ハンドルではないものからの参照を持つ事が出来る。例えば、実行スレッドはスレッドオブジェクトへの参照を保持する。スレッドはそれが動作し続けている限り自身への参照を保持するので、スレッドに対する最後のハンドルを閉じても、スレッドオブジェクトは破棄されない)。

なぜカーネルハンドルは常に4の倍数なのか

Why are kernel HANDLEs always a multiple of four?@The Old New Thing の翻訳。 

あまり良く知られていない事は、カーネルハンドルの下位2ビットが常に0であるという事だ。言い換えると、それらの数値は常に4の倍数である。なお、これはカーネルハンドについてだけ当てはまる。疑似ハンドルやあらゆる他のタイプのハンドル(USERハンドルやGDIハンドル、マルチメディアハンドルなど)には当てはまらない。カーネルハンドルとは、CloseHandle関数に渡す事が出来るものを指す。

この下位2ビットの用途はntdef.hヘッダーファイルの中に埋められている。

//
// ハンドルの下位2ビットはシステムからは無視され、
// アプリケーションからタグビットとして使用される。残りのビットは不透明で、
// シリアルナンバーとテーブルインデックスの格納に使用される。
//

#define OBJ_HANDLE_TAGBITS  0x00000003L

カーネルハンドルの下位2ビットが常に0である事は、GetQueuedCompletionStatus関数(完了ポートの通知を抑制するために、イベントハンドルの下位ビットに値を設定できる)によって暗示されている。これが機能するためには、下位ビットは通常0でなければならない。
この情報はほとんどの(ハンドルを不透明な値として扱うべき)アプリケーション開発者にとって有用ではない。タグビットに興味のあるのは、低レベルクラスライブラリーを実装している者達か、カーネルオプジェクトを巨大なフレームワークの中に包み込んでいる者達だ。