連載:深入り.NETプログラミング

アンマネージ・コードの型情報と連携する

NyaRuRu
Microsoft MVP Windows - DirectX(Jan 2004 - Dec 2009)
2009/07/21
Page1 Page2

Standard Annotation Language(SAL)とメタデータ

 ヘッダ・ファイルつながりで、Standard Annotation Language(以下、SAL)についても紹介しておこう。

 マイクロソフトが最近公開したC/C++用SDKは、ほぼすべてでSALによる注釈が行われている。SALとは、C/C++のソース・コードにプリプロセッサ・マクロとして埋め込まれたメタ言語である。

 マクロとして埋め込まれた「SAL注釈」は、プリプロセス時にMicrosoft Visual C++の拡張記法(「__declspec」またはC++/CLI用に追加された属性記法)に展開され、最終的にはPREfastやVisual Studioの静的コード分析に利用される。ちなみに、先ほど紹介したP/Invoke Interop Assistantも、部分的ではあるが、SAL注釈のパースに対応している。

 SAL注釈は、バッファ注釈とそのほかの詳細な注釈の2つに分けることができる。

 バッファ注釈は、ポインタとして受け渡しされるメモリのアクセス範囲やデータの論理的な転送方向を補足するものだ。COM(Component Object Model)に慣れ親しんだ人には、IDL(インターフェイス定義言語)で多用されていたのでおなじみだろう。バッファ注釈で追加される情報は、マーシャリング・コードの自動生成やバッファ境界チェックで特に重要である。

 そのほかの詳細な注釈には、関数が失敗を返した場合のチェックを要求する注釈や、どのような戻り値が関数の成功かを定義する注釈がある。歴史的経緯から、Win32 APIの戻り値は成功と失敗を区別する条件が統一されておらず、こういった情報が関数宣言に追加されるのは大変ありがたい。これは単にドキュメントを読む手間が省けるというだけでなく、コンパイル時における動作検証や、ラッパー・コードの自動生成が可能になるという点で有用である。

 次のコードは、Windows SDK 7.0(beta)に含まれるインクルード・ファイルで使用されているSAL(そのほかの詳細な注釈)の例だ。

WINBASEAPI
__success(return < nSize)
__success(return != 0)
DWORD
WINAPI
GetEnvironmentVariableW(
  __in_opt LPCWSTR lpName,
  __out_ecount_part_opt(nSize, return + 1) LPWSTR lpBuffer,
  __in DWORD nSize
  );

typedef struct tagLOGPALETTE {
  WORD        palVersion;
  WORD        palNumEntries;
  __field_ecount_opt(palNumEntries) PALETTEENTRY   palPalEntry[1];
} LOGPALETTE, *PLOGPALETTE, NEAR *NPLOGPALETTE, FAR *LPLOGPALETTE;
Windows SDK 7.0(beta)のインクルード・ファイルで使用されているSALの例
ほぼすべてのインクルード・ファイルにSAL注釈が書き足されている。こういうところにも、セキュア・プログラミングに対するマイクロソフトの本気を垣間見ることができる。

 SALの各文法と意味についてはMSDNライブラリの「SAL 注釈のページ」と、Michael Howard氏のブログ記事「A Brief Introduction to the Standard Annotation Language (SAL)」が参考になる。

 さらに.NET Framework 4.0ではCode Contracts(コード契約)が導入される予定で、これによりC#やVisual Basicのソース・コードに事前条件、事後条件、不変性の定義を埋め込むことが可能になる。

 例えば、SALのopt_注釈が付いていないポインタ型引数は、NULLであってはならない。これをP/Invokeにより呼び出す.NET側のコードに、以下の1文のようなCode Contractsを定義すると、.NETでもコード検証の恩恵が受けられる。

Contract.Requires<ArgumentNullException>(x != null, "x");

 いかがだろう? まだまださまざまなことが自動化できそうな気がしてこないだろうか?

デバッグ・シンボルとメタデータ

 ネイティブ・コードの型情報だが、別のアプローチとして、デバッグ・シンボルから取得する方法もある。例えば、書籍『インサイドWindows第4版 上』の「実験: カーネル構造体の型情報を表示する」では、マイクロソフトのシンボル・サーバからダウンロードしたデバッグ・シンボルを使用して、カーネル内構造体の詳細な情報を得る方法が紹介されている。

 次の表示は、Windowsの標準デバッガ「WinDBG」を使ってマイクロソフトのシンボル・サーバを参照し、カーネル・シンボルを表示させてみた例だ。これらの構造体はドキュメントもヘッダ・ファイルも公開されていないのだが、構造体のメモリ・レイアウトやフィールド名を再構築できている。

lkd> dt -r nt!_KTHREAD_COUNTERS
   +0x000 WaitReasonBitMap : Uint8B
   +0x008 UserData         : Ptr64 _THREAD_PERFORMANCE_DATA
      +0x000 Size             : Uint2B
      +0x002 Version          : Uint2B
      +0x004 ProcessorNumber  : _PROCESSOR_NUMBER
         +0x000 Group            : Uint2B
         +0x002 Number           : UChar
         +0x003 Reserved         : UChar
(以下略)
マイクロソフトのシンボル・サーバを参照しているWinDBGで、カーネル・シンボルを表示させてみた例

 先ほどのC/C++パーサーを用いた方式では、メモリ・レイアウトを推測するのが面倒だ。C/C++コンパイラとまったく同じアルゴリズムでパディングやアライメントを計算しなければならない。その点、デバッグ・シンボルを用いれば計算済みの値を取得するだけでよい。

 シンボル情報の利用について興味があるのであれば、John Robbins氏の書籍『.NET & Windowsプログラマのためのデバッグテクニック徹底解説』は手元に置いておきたい。そんな同書でも推奨されているのが、DbgHelp APIを利用する方法だ。C#からはP/Invoke経由で呼び出すことになる。

【コラム】DbgHelp APIのサンプル・コード

 DbgHelp APIについては、プログラミング言語「Gauche」で有名なpractical-scheme.netに掲載されている記事「practical-scheme.net:デバッガ:デバッグ情報:windows」が参考になる。また、『practical-scheme.net:デバッガ』には、さまざまな言語・環境のデバッガについて紹介されている。

 メモリ・レイアウトが分かっているアンマネージ・データのマーシャリングについて、筆者は次の2つの方法を使い分けている。

 1つは、いわゆるMarshalAs属性によって.NET Frameworkの標準マーシャラを用いる方法だ。この方法を用いるのは、読むに耐えるソース・コードの生成が主目的であったり、データ・フォーマットが固定長で扱いやすかったりするときである。

 もう1つは、BinaryReader/ BinaryWriterクラスを用いてバイナリ・データの読み/書きを行うステート・マシンを生成する方法だ。可変長データや、ヘッダ部分の内容によって後続のフォーマットが変化するデータ構造に向いている。


【コラム】ドキュメント・サービス

 自動生成したP/Invokeコードにドキュメント・コメントを付けたいとする。MSDNライブラリで公開されているドキュメントが一見利用できそうなのだが、ネックが2つほどある。

 1つは検索の問題で、Win32 APIや構造体、定数などの名前から、それについて書かれたドキュメントを探すオフィシャルなサービスが提供されていないこと。

 もう1つの問題は、われわれが得られるのが、フリー・フォーマットのXHTML文章ということだ。どの部分をドキュメント・コメントとして切り出せばよいかがページごとにまちまちなのである。

 前者については、汎用的なWebサーチが提供しているHTTPベースの検索APIで代用は可能である。msdn.microsoft.comに検索対象を絞ることで、精度はかなり高くなるだろう。

 後者については、MSDNライブラリで公開されている文章を、スクレイピング(=必要な部分のみ抽出)しやすい形で取得する方法までは分かっている。MSDN/TechNet Publishing System(MTPS)Content Serviceを用いることで、APIのリファレンスやMSDNマガジンの記事など、msdn.microsoft.comサイトで公開されているたいていの記事をXHTML形式で入手できる。これについては、MSDNマガジンの『Consuming MSDN Web Services』という記事が参考になるだろう。自動化できていないのは、取得したXHTMLの「文章」の中から、必要な説明を切り出すところである。

 最後に注意点だが、MSDNサイトの文章を利用する以上、利用規約に配慮が必要なことを忘れてはならない。

まとめ

 最近のWin32 APIとアンマネージ・プログラミングの世界は、世間で思われている以上にメタデータ・リッチである。そして、C++自体はリフレクションをサポートしなくても、アイデア次第でアンマネージ・コードの型情報を外部から利用できるのだ。

 歯応えがあって、しかも実用性もあるテーマということで、新しいプログラミング言語(例えばF#)の練習ネタとしてでもいかがだろうか?

 なお、本連載は今回で最終回である。このようなニッチな連載にお付き合いいただいた読者の方に海よりも深く感謝したい。End of Article

 

 INDEX
  [連載]深入り.NETプログラミング
  アンマネージ・コードの型情報と連携する
    1.C/C++と.NETの橋渡し/P/Invoke Interop Assistant
  2.Standard Annotation Language(SAL)/デバッグ・シンボル

インデックス・ページヘ  「深入り.NETプログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間