Posts filed under 'アプリケーション開発'

ネイティブコードから C# へのコールバックについて補足

今回は、2012/05/14 のエントリ(「ネイティブコードから C# のメソッドをコールバック」)についての補足を書きます。補足するのは、次の二点です:

・コールバック関数のコーリングコンベンション
・ネイティブコードへのデリゲートオブジェクト渡し

それぞれについて、以下に記します。

■コールバック関数のコーリングコンベンション
2012/05/14 のエントリでは、マネージドコードからアンマネージコードに渡すコールバック関数の関数ポインタを得る API として、GetFunctionPointerForDelegate() を紹介しました。この API で取り出す関数ポインタに対しては、アンマネージコード側からみた場合、留意すべき点があります。リファレンスページの “Remarks” の項にある注意書きです。

 Marshal.GetFunctionPointerForDelegate Method
 http://msdn.microsoft.com/en-us/library/at4fb09f(v=vs.90).aspx

上のページの “Remarks” の項には、次の注意書きがあります:

The delegate d is converted to a function pointer that can be passed to unmanaged code using the __stdcall calling convention.

つまり、アンマネージコード(ネイティブコード)から見た場合、マネージコードから渡される関数ポインタのコーリングコンベンション(呼出規約)は __stdcall になるので、アンマネージコード側では、受け取る関数ポインタの型を、__stdcall 付きで宣言しておく必要がある、というわけです。__stdcall をはじめ、Visual C++ の C/C++ コンパイラにおけるコーリングコンベンションの説明は、リファレンスの次のページをご覧ください。

 Argument Passing and Naming Conventions
 http://msdn.microsoft.com/ja-jp/library/984×0h58(v=vs.90).aspx

デフォルトのコーリングコンベンションは、スタックに積まれた手続きの引数を、手続きを呼び出した側がスタックから取り除く __cdecl であり、呼び出された手続きが取り除く __stdcall とは異なりますので、その点に注意して下さい。

さて、アンマネージコードの既存のライブラリがあり、そのライブラリに対してコールバック関数を設定するマネージドコードを実装しようとした際、既存のライブラリが受け取るコールバック関数が __cdecl であった場合は、どうすればよいのでしょうか?そのままでは、コーリングコンベンションが合っていないため、実行時エラーを引き起こす可能性があります。

ご安心下さい。その問題は、マネージドコードからの呼び出し方を変えることで解決できます。これは、冒頭に示した二点目の補足に関係します。

注意:実は、WinCE/WEC では、後述するようにコーリングコンベンションの問題はありません。この問題を考慮する必要があるのは、フル版の .NET Framework の場合だけなのです。

■ネイティブコードへのデリゲートオブジェクト渡し
2012/05/15 のエントリでは、マネージドコードからアンマネージコードへ渡す関数ポインタを得るために、delegate 実体に GetFunctionPointerForDelegate() を適用する必要があると述べました。しかし、この呼び出しは必須ではなく、delegate 実体をそのまま渡すことができます。MSDN にある .NET Framework プログラミングガイドにある、「アンマネージコードとの相互運用」にあるサンプルコードでも、delegate 実体をそのまま渡しています。2012/05/15 のエントリを書いた時点では、そのことを理解していませんでした。ごめんなさい。

MSDN にあるサンプルコードは、次のページをご覧ください:

 Callback のサンプル(マネージドコード)
 http://msdn.microsoft.com/ja-jp/library/5zwkzwf4(v=vs.90)

 PinvokeLib.dll(アンマネージコード)
 http://msdn.microsoft.com/ja-jp/library/as6wyhwt(v=vs.90).aspx

さて、コーリングコンベンションに話を戻します。.NET Framework には、マネージドコードとアンマネージコードの間での、(アンマネージコードにおける)コーリングコンベンションを表現する UnmanagedFunctionPointerAttribute というクラスがあります。このクラスを使って、delegate のコーリングコンベンションを変更できます。具体的には、delegate を宣言する際、次のように属性記述すれば、delegate のコーリングコンベンションを __cdecl に設定できるようです:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool FPtr( int value );


しかし、この UnmanagedFunctionPointerAttribute は、WinCE/WEC 環境、つまり .NET Compact Framework ではサポートされていません。さらに、UnmanagedFunctionPointerAttribute クラスのコンストラクタ引数である、enumeration 型の CallingConvention は、.NET Compact Framework の場合には、メンバとして Winapi しか持たず、これは __cdecl に対応するとリファレンスに書かれているのです。

 CallingConvention Enumeration
 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.callingconvention(v=vs.90).aspx

このリファレンスには、デフォルトのコーリングコンベンションを示す Winapi は、「Windows 環境」、つまりフル版の .NET Framework では __stdcall に対応し、WinCE/WEC 環境(.NET Compact Framework)では __cdecl に該当するという旨の説明があります。これは一体、どいうことなのでしょう?Win32 API は、WINAPI 付きで宣言されており、__stdcall のコーリングコンベンションを設定されています。一方、UnmanagedFunctionPointerAttribute が __cdecl にしか対応しないのでは、両者が整合しないということです。

実は、WinCE/WEC のコーリングコンベンションは __cdecl だけなのです。このことは、次のヘッダファイルを見れば分かります:
 %_WINCEROOT%/public/common/sdk/inc/windef.h

windef.h には、次の行があり、’__stdcall’ が __cdecl の別名として定義されているのです。

#ifdef UNDER_CE
#define __stdcall __cdecl // Note this doesn't match the desktop definition
#define _stdcall __cdecl // Note this doesn't match the desktop definition
#endif


従って、.NET Compact Framework の場合には、コーリングコンベンションのことを意識する必要は、ありません。とはいえ、.NET Compact Framework 版と .NET Framework 版とで、アンマネージコードを含めてソースコードを共有できるように、可搬なコードを書く場合には、.NET Framework 版でも問題なく動作するよう、コーリングコンベンションを意識する必要があります。

■まとめ
今回は、2012/05/14 のエントリに対して二つの補足を書きました。二つのうち、コーリングコンベンションについては、WinCE/WEC では考慮せずともよいのですが、Win7 や Win8 でも動かせる可搬なコードを書く場合には、考慮する必要があります。

もう一つの、デリゲート渡しについては、わざわざ GetFunctionPointerForDelegate() を呼ぶ必要はない、というのが結論です。これは、2012/05/14 のエントリで、どちらかといえば、不適切な内容だったと思います。同様の見解が、.NET Framework に関する次の記事にも書かれています:

 C# からコールバック関数を使う C の関数を呼ぶ
 http://sgry.jp/pgarticles/cs_pinvoke_callback.html

今回の補足ですが、2012/05/15 のエントリに頂いていたコメントを拝見して書きました。コメントを頂いたのは、2013/05/14 ですから、半年以上の時間が経ってしまったのですが、参考になりました。有難うございます。

Add comment 2014/02/16 koga

WinCE/WEC の Secure Loader

前回(2013/02/10)から、一年ほど間が空いてしまいました。その間に、Windows Embedded Comact の最新版である Windows Embedded Comapct 2013 (WEC 2013) の一般提供も始まりました。この Blog でも、今後 WEC 2013 のことも交えて書く予定です。

ですが、今回は、前回の続編となることを書きます。組込み機器用の OS として WinCE/WEC が備えている、セキュリティを確保するための機能です。

■拡張性と脆弱性
前回は、ActivateDevice[Ex]() を用いてデバイスドライバを動的ロードすることができると説明しました。これは、便利な機能である反面、脆弱性の要因でもあります。

デバイスドライバを動的ロードできるということは、OS イメージに含めていないデバイスドライバを、OS の動作中に追加して、あらかじめ組み込まれてはいない機能を利用できるということです。たとえば、Flash メモリなどに配置した OS イメージを書き換えてアップデートする場合に、Flash メモリ用のデバイスドライバを OS イメージに組み込んでいなくとも、アップデート処理を実行するアプリケーションの .exe と、Flash メモリ用のデバイスドライバの .dll、および新しい OS イメージを USB メモリに入れて WEC デバイスにマウントし、アプリケーションを実行してアップデート処理を行うことが可能です。アップデート処理を実行するアプリケーションは、最初に ActivateDevice[Ex]() を使って、Flash メモリ用のデバイスドライバをカーネルにロードさせ、その後、DeviceIoControl() を使ってデバイスドライバを呼び出すことにより、OS イメージを Flash メモリに書き込む、というわけです。

デバイスドライバ、つまりカーネルモジュールを動的ロードできれば、このように拡張性を得られる一方で、悪意を持ったソフトウェアによる攻撃を容易にするという側面もあります。(悪意を持った)アプリケーションが可能な攻撃は、仮想メモリと実行モードによって制限されていますから、カーネルに対して致命的な障害を与えることは困難です。しかし、(悪意を持った)カーネルモジュールをアプリケーションがロードできてしまえば、カーネルのメモリ空間をアクセスできますし、プロセッサの特権モードで動きますので、カーネルに致命的な障害を引き起こすことは難しくありません。
(WinCE 5.0 までの純粋マイクロカーネルの場合には、デバイスドライバは、ユーザプロセスであるデバイスマネージャにロードされて動きますので、カーネルの保護は、より堅牢だったと言えます。)

■WinCE/WEC の Secure Loader
WinCE/WEC には、悪意を持ったソフトウェアによる攻撃を防ぐ機能として、Secure Loader (Loader Verifier Module) が用意されています。これは、あらかじめ OS イメージに組み込まれているアプリケーションや、認証されたアプリケーション以外は実行できないようにする仕組みです。WEC 7 のリファレンスでは、次のページで説明されています:

 Security Loader (Compact 7)
 http://msdn.microsoft.com/en-us/library/gg155695(v=winembedded.70).aspx

Secure Loader を有効にした OS イメージを作るには、OS Design のカタログ項目において、Loader Verifier Module を選択してビルドします。Loader Verifier Module は、Platform Builder のカタログ項目ビューにおいて、次の場所にあります:

 
  Core OS
   Windows Embedded Comapct
    Security
★    Loader Verifier Module

Secure Loader を有効にすると、OS イメージに組み込まれていないアプリケーションは、実行できなくなります。つまり、上で述べたデバイスドライバの動的ロードの例のように、USB メモリに入れたアプリケーションを実行しようとしても、実行できません。

OS イメージに組み込まれていないアプリケーションの実行を許さない処理は、WinCE/WEC のローダーによって行われます。ローダーが実行ファイル(executable)をロードする際に、ロードして構わないファイルかどうかをチェックして、条件に合わないものが指定された場合はエラーとするのです。

ローダーのソースコードは、
 C:/WINCE700/private/winceos/COREOS/nk/kernel/loader.c
にあります。loader.c で実装されている OpenExecutable() が、実行ファイルをロードする関数ですが、OpenExecutable() は、OS イメージに収録されたファイルに対しては OpenFileFromROM() を、ファイルシステム上のファイルに対しては OpenFileFromFilesys() を呼び出します。OpenFileFromROM() と OpenFileFromFilesys() は、FSOpenModule() という関数を呼び出して実行ファイルの内容をロードしますが、この FSOpenModule() において、ロードして構わないファイルかどうかのチェックが行われるようです。FSOpenModule() のソースは
 C:/WINCE700/private/winceos/COREOS/nk/kernel/fscall.c
にありますが、この関数は、filesys.dll を呼び出します。filesys.dll のソースは開示されていないため、詳細は分かりませんが、OpenFileFromROM() の場合、FSOpenModule() に渡す第二引数のフラグビット列で OPENMODULE_OPEN_ROMMODULE ビットが必ず 1 になるため、このビットを使って、OS イメージに収録されたファイルと、それ以外のファイルを区別して扱っているのだと思われます。

なお、Secure Loader 機能自体は、filesys.dll ではなく、lvmod.dll という DLL で実装されており、filesys.dll が lvmod.dll を呼び出すようです。カタログ項目の Loader Verifier Module を選択すると lvmod.dll が OS イメージに組み込まれることは、
 C:/WINCE700/public/common/oak/files/common.bib
を見ると分かります。Loader Verifier Module に関連付られた SysGen 変数は CE_MODULES_LVMOD ですので、これをキーにして common.bib を検索してみて下さい。

■署名による認証
Secure Loader を有効にすることにより、OS イメージに組み込まれていないアプリケーションを実行できないようになります。これでセキュリティは非常に高まりますが、拡張性は損なわれます。あらかじめ固定された機能しか動作しない、昔ながらの組込み機器であれば、ROM に配置した OS イメージに組み込まれたアプリケーションしか動かせなくても、問題ありません。しかし、サードパーティ製のアプリケーションを動かせるようにしたり、冒頭で述べたアップデート機能など、特定の場合にだけしか使わない機能を OS イメージには組み込まず、必要な時にだけ使いたい、という場合には、 OS イメージに組み込まれたアプリケーションしか実行できないのでは、対応できません。

ご安心下さい。WinCE/WEC の Secure Loader は、そのような場合に対応するための機能も備えています。実行ファイルに対する署名と、署名に対する認証機構により、OS イメージに組み込まれていないアプリケーションでも、認証できるものは実行を許すようになっています。上述した lvmod.dll が、この認証処理を行います。lmvod.dll が行う認証処理は、上述したローダーから呼び出される他、API としても提供されています。Secure Loader の API については、リファレンスの次のページをご覧ください:

 Security Loader Reference (Compact 7)
 http://msdn.microsoft.com/en-us/library/gg155306(v=winembedded.70).aspx

このリファレンスのページを見ると、ファイルを認証する API に加え、ブロックリスト、つまり、署名に対して認証エラーとするファイルの一覧を扱うものがあります。署名されており、署名に対する認証処理が成功するアプリケーション(実行ファイル)であっても、不正な動作をすることが分かった場合に、それをブロック対象として登録し、実行できないようにすることが可能なのです。

このように、不正なソフトウェアが実行されるのを防ぎ、セキュリティを確保するための仕組みを、拡張性と共に提供しているのが WinCE/WEC の Secure Loader というわけです。Secure Loader 用に、実行ファイルに署名を付ける手順は、リファレンスの次のページで説明されています:

 Signing Binaries (Compact 7)
 http://msdn.microsoft.com/en-us/library/gg156011(v=winembedded.70).aspx

 Deploy an Application on an OS with Security Loader (Compact 7)
 http://msdn.microsoft.com/en-us/library/jj200466(v=winembedded.70).aspx

WEC の最新版である WEC 2013 のリファレンスにも、同じ内容の説明があります:

 Signing Binaries (Compact 2013)
 http://msdn.microsoft.com/en-us/library/gg156011.aspx

 Deploy an Application on an OS with Security Loader (Compact 2013)
 http://msdn.microsoft.com/en-us/library/jj200466.aspx

■WinCE/WEC のセキュリティ
組込み機器のセキュリティは、一つの方策だけで確保できるものでは、ありません。機器の特性に応じて、複数の方策を組み合わせる必要があります。WinCE/WEC でのセキュリティの考え方や実現方策については、以下のページが参考になるでしょう。興味のある方は、ご覧になってみて下さい。

 Security for Windows Embedded Compact (Compact 7)
 http://msdn.microsoft.com/en-us/library/ee498894(v=WinEmbedded.70).aspx

 Trusted Environment Creation (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee498576(v=winembedded.60).aspx

 Windows Embedded CE 6.0 Security Model
 http://msdn.microsoft.com/en-us/library/ee497961(v=winembedded.60).aspx

 Security for Windows Embedded Compact Code Samples (Compact 7)
 http://msdn.microsoft.com/en-us/library/hh802407(v=winembedded.70).aspx

Add comment 2014/02/02 koga

WEC 7 付属の gdiplus.dll

■WinCE/WEC と GDI+
Windows XP で導入された 2D 描画用の API である GDI+ は、WinCE では利用できません。しかし、Windows Mobile では、gdiplus.dll という DLL が組み込まれており、この DLL により、GDI+ の API を利用できたようです。

たとえば、MSDN フォーラムの “Visual Basic and C# Projects” に投稿された “How to use GDI+ on Windows Mobile?” という質問に対して、Windows Mobile において非公式 API ではあるものの、GDI+ を利用できるというコメントが寄せられています:

 How to use GDI+ on Windows Mobile?
 http://social.msdn.microsoft.com/forums/en-US/vssmartdevicesvbcs/thread/e01dfedb-9b79-468b-9e89-44f59952b2d8

そのコメントでも紹介されている次のページは、GDI+ の Flat API を .NET CF アプリケーションから使うための wrapper ライブラリを提供しています。

 Using GDI+ on Windows Mobile
 http://community.opennetcf.com/articles/cf/archive/2007/10/31/using-gdi-on-windows-mobile.aspx

御存知の方は多いと思いますが、フル版の .NET Framework では、描画 API の実装に GDI+ が用いられています。たとえば、System.Drawing.Graphics クラスの DrawArc() や DrawBezier() などです。しかし、.NET Compact Framework では、System.Drawing.Graphics クラスには DrawArc() や DrawBezier() メソッドが存在しません。上記の wrapper ライブラリは、GDI+ の Flat API を P/Invoke によりマネージドコードから呼び出すためものです。

上のページでは、gdiplus.dll が提供している GDI+ API は、デスクトップ版のサブセットであり、「未実装」エラーを返す関数が多くあると述べています。しかし、サブセットであっても、GDI の API だけでは不足する 2D 描画機能を利用したいと思うことは、少なくありません。gdiplus.dll を WinCE でも使いたいと思う人は多いようで、’gdiplus.dll WinCE’ などで検索すると、「WinCE には gdiplus.dll が付属していないのか?」という質問のページが多く見つかります。

実は、WEC 7 には、gdiplus.dll が付属しています。

■WEC 7 の gdiex/ ディレクトリ
冒頭でも述べたように、WinCE には gdiplus.dll が付属しておらず、GDI+ の API を利用できません。しかし、WEC 7 では、
 %_WINCEROOT%/public/gdiex/
ディレクトリ配下に gdiplus.dll と gdiplus.lib が収録されているのです。このディレクトリは、WinCE 6.0 にも存在しますが、配下に収録されているのは、Imaging API のヘッダファイルとライブラリファイル(.lib)だけです。

gdiplus.dll に関する説明は、WEC 7 のリファレンスには存在しません。Windows Mobile でそうだったように、非公式な API という位置づけのようです。SKU との対応も不明ですから、もし、gdiplus.dll を組み込んだ OS イメージを製品に搭載することを検討される場合には、ランタイムライセンスの販売代理店経由で Microsoft 社に確認する方が良いでしょう。

WEC 7 に gdiplus.dll が付属していることの理由は不明ですが、もしかすると、一部の開発者からの強い要望を受けて、Windows Mobile 向けに提供していたものを WEC 7 にも収録した、ということなのかも知れません。いずれにせよ、便利な機能が付属しているわけですから、もしライセンス上も支障なければ、利用できると嬉しいところです。

■ベジェ曲線描画の動作確認
というわけで、WEC 7 付属の gdiplus.dll を使った描画について、簡単なテストコードを書いて試してみました。制御点が二つだけの単純なベジェ曲線の描画ですが、Flat API を C/C++ から呼び出すコードを書いて動かしてみたところ、問題なく動作しました。ただし、パフォーマンスは評価・実測していません。

以下に、テストコードの関連部分を引用します:

static void
DrawWithGdiPlus(HDC hdc)
{
#define MAKE_ARGB(a, r, g, b) \
    (((ARGB)(b) << 0) \
    |((ARGB)(g) << 8 ) \
    |((ARGB)(r) << 16) \
    |((ARGB)(a) << 24)) \

    GpGraphics*    graphics = NULL;
    GpPath*    path = NULL;
    GpPen*    pen  = NULL;
    PointF    points[] = {
        PointF(0.0f + 50, 0.0f + 50),
        PointF(40.0f + 50, 20.0f + 50),
        PointF(80.0f + 50, 150.0f + 50),
        PointF(100.0f + 50, 10.0f + 50),
    };

    if (0 != ::GdipCreateFromHDC(hdc, &graphics)) {
        return;
    }
    if (0 != ::GdipCreatePath(FillModeAlternate, &path)) {
        goto finish;
    }
    if (0 != ::GdipAddPathBeziers(path, points, 4)) {
        goto finish;
    }
    if (0 != ::GdipCreatePen1(MAKE_ARGB(255, 255, 0, 0), 1.0, UnitPixel, &pen)) {
        goto finish;
    }
    if (0 != ::GdipDrawPath(graphics, pen, path)) {
        goto finish;
    }

finish:
    if (NULL != pen) {
        (void)::GdipDeletePen(pen);
    }
    if (NULL != path) {
        (void)::GdipDeletePath(path);
    }
    (void)::GdipDeleteGraphics(graphics);
}


上記のテストコードを動かした画面キャプチャが、次の図です。

bezier-spline

なお、上の画面キャプチャは、2012/7/4 に書いたエントリ(「デバイスエミュレータで WEC 7 を動かす」)で紹介した、デバイスエミュレータに移植した WEC 7 でテストコードを動かした様子です。興味のある方は、ご自分でテストコードを書いて試してみて下さい。

上のテストコードについて、二点補足しておきます。

  1. GdiPlus.h のインクルードが必要。さらに、上記のコードの場合、Gdiplus と Gdiplus::DllExports に対する using ディレクティブが必要。
  2. GdiPlus.h を始め、GDI+ のヘッダファイルは WEC 7 に付属しないので、別途入手が必要。
  3. アプリケーションの初期化処理と終了処理で、それぞれ、GDI+ の初期化関数と解放関数を呼び出さなければならない。

上記のテストコードのビルドと動作確認を行う際、GDI+ のヘッダファイルは、次のページで公開されているライブラリ(LibGdiplus)に収録されているものを使いました。

 GDI+ for Windows Mobile
 http://www.ernzo.com/LibGdiplus.aspx

このライブラリは、Windows Mobile に収録されている gdiplus.dll 用の wrapper です。冒頭で紹介した wrapper ライブラリとは異なり、マネージドコードではなく、C/C++ 用の wrapper です。

GDI+ の初期化関数と解放関数は、GdiplusStartup() と GdiplusShutdown() です。詳細は、GDI+ のリファレンスをご覧下さい:

 GDI+ Reference > Functions
 http://msdn.microsoft.com/en-us/library/windows/desktop/ms534055(v=vs.85).aspx

GDI+ の Flat API のリファレンスは、次のページに記載されています。

 GDI+ Flat API
 http://msdn.microsoft.com/en-us/library/windows/desktop/ms533969(v=vs.85).aspx

■GDI+ の代替ライブラリ
WEC 7 には gdiplus.dll が付属しており、それを OS イメージに組み込むことで GDI+ の API(のサブセット)を利用できることを、上で紹介しました。では、WinCE 6.0 で GDI+ の API を利用したい場合には、何か方法はないのでしょうか?

簡単な代替策は、ありません。前の節で紹介した LibGdiplus のページ("GDI+ for Windows Mobile")では、Mono プロジェクトの LibGdiplus を紹介していますが、(その紹介でも述べられているように)そのまま使うことは、できません。Mono プロジェクトの LibGdiplus は、X Window System に依存した実装だからです:

 Libgdiplus
 http://www.mono-project.com/Libgdiplus

Mono プロジェクトの Libgdiplus は、Cairo という 2D 描画ライブラリを利用しているようですが、WinCE 6.0 で使えるようにするためには、X Window System に依存した部分の実装を、WinCE へ移植する必要があるでしょう。その移植作業は、移植対象の API 関数を一部に絞ったとしても、それなりに大きな手間だと思われます。

なお、GDI+ が提供する 2D 描画 API のうち、ベジェ曲線(Bezie-spline 曲線)の描画だけであれば、自前で実装したサンプルコードが CodeProject サイトの次のページで公開されています。

 Drawing Qubic Bezier-splines on Pocket PC
 http://www.codeproject.com/Articles/9862/Drawing-Qubic-Bezier-splines-on-Pocket-PC

このサンプルコードの動作確認は行っていませんが、おそらく、WinCE/WEC でも問題なく使えるんじゃないかと思います。

■まとめ
今回は、WEC 7 に付属している GDI+ の DLL(gdiplus.dll)について紹介し、単純なベジェ曲線であれば、問題なく動作することを述べました。gdiplus.dll が、先日発表のあった WEC の次のバージョン(Windows Embedded Compact 2013)でも提供されるのかどうかは、現時点では不明です。しかし、WEC 7 に限定すれば、GDI だけでは不足する 2D 描画機能を実現するために gdiplus.dll の利用を検討することは、価値があるかも知れません。

なお、OS やミドルウェアベンダーの近年の動きを見ていると、高機能な 2D 描画は、3D 描画のグラフィクス機能に統合されるのが一般的な方向性のように思われます。Windows の場合でも、GDI/GDI+ を置き換えていくものとして Windows 7 で導入された Direct2D は、Direct3D の上に実装されているそうです:

 Introducing Direct2D
 http://msdn.microsoft.com/en-us/magazine/dd861344.aspx

今後、Windows Embedded Compact におけるグラフィクス機能が、どのように改良ないしは変革されていくのか分かりません。WEC 7 において gdiplus.dll が追加されたことは、もしかすると、Windows Embedded Compact の歴史における、一過性のものだとして捉えておく方が確実かも知れません。

とはいえ、WEC 7 は、2011年のリリースから7年後の、2018年までは、無償サポートが継続する予定です。従って、開発のターゲットを WEC 7 に絞れば、その他の API やミドルウェアと同様に、gdiplus.dll はアプリケーション開発の有力なツールとなり得るでしょう。この点は、WinCE/WEC の Long Term Suppport の利点だと思います。

Add comment 2013/02/05 koga

IP アドレス設定をアプリケーションから変更するには

WEC 7 (Windows Embedded Compact 7) や WinCE 6.0 (Windows Embedded CE 6.0) で、IP アドレス設定を変更するには、コントロールパネルの “Network and Dial-up Connections” を使います。これは、WinXP と同様です。WEC 7/WinCE の通常の使い方、つまり、ユーザが画面操作で IP アドレスを設定できる場合は、それで全く問題ありません。

しかし、ヘッドレスのデバイスの場合、つまり、画面操作ができない場合は、どうでしょうか?WEC 7/WinCE 6.0 には、IP アドレスをコマンドラインで設定できるユーティリティは付属していませんので、自前で作る必要があります。その方法、つまり、IP アドレス設定をアプリケーションから変更する方法について、以下に説明します。

■コントロールパネルのソースコードを見てみる
IP アドレス設定をアプリケーションから変更するには、どうすればよいでしょう?それを調べる最も確実な方法は、同じことをやっているモジュールのソースコードを読んでみることです。つまり、コントロールパネルの “Network and Dial-up Connections” アプレットが、どうやって IP アドレスの設定変更を処理しているかを見ればよい、というわけです。コントロールパネルの、このアプレットを起動する部分のソースコードは、
 %_WINCEROOT%/public/wceshellfe/oak/ctlpnl/connpnl/
にあります。

“Network and Dial-up Connections” アプレットを起動する処理は、connpnl.cpl という、コントロールパネル用の DLL で実装されています。上のディレクトリを見ると、ソースファイルは connpnl.cpp 一つだけです。connpnl.cpp の内容を見ると、DllEntry() と CPlApplet() が実装されています。IP アドレスの設定がどのように処理されるのかを追跡するには、CPlApplet() の中にブレークポイントをセットして、カーネルデバッガを使うのが簡単です。CPlApplet() の本体は、message 引数に対する switch 文だけですが、アプレットを起動する処理は、コントロールパネルのアイコンがダブルクリックされた場合、つまり、CPL_DBLCLK に対する case 節にあります。そこを見ると、”\\Windows\\connmc.exe” を第一引数として CreateProcess() を呼び出しており、connmc.exe がアプレットの実体であることが分かります。

この connmc.exe のソースコードは、WEC 7 のソースツリーを探してみると、
 %_WINCEROOT%/public/COMMON/oak/drivers/netsamp/connmc/
にあることが分かります。connmc.exe が、”Network and Dial-up Connections” アプレットの実体です。

“Network and Dial-up Connections” アプレットで、ネットワークアダプタのアイコンをダブルクリックして IP アドレスを設定するダイアログ(プロパティシート)を表示する処理は、実は、上のディレクトリにあるソースファイルでは実装されていません。その実装は、
 %_WINCEROOT%/public/COMMON/oak/drivers/netui/
にある network.c で実装されています。network.c に入っている AdapterIPProperties() が、IP アドレスのプロパティシートを表示して、IP アドレス設定を行う関数なのです。

connmc.exe から、netui/ ディレクトリにある network.c の関数が呼び出される、というのは、ソースコードを見ただけでは、分かりません。こういう場合は、カーネルデバッガを使って追いかけるのが簡単です。connmc.exe のメインウィンドウのウィンドウプロシージャは、connmc.cpp で実装されている ConnMCWndProc() ですから、この関数にブレークポイントをセットしてカーネルデバッガでステップ実行すれば、AdapterIPProperties() まで辿りつくことができるでしょう。参考までに、ConnMCWndProc() から AdapterIPProperties() までの呼び出し連鎖を記します。

 connmc.exe
  ConnMCWndProc()		: connmc.cpp
  ConnMCHandleCommand()		: connmc.cpp
  LanConnInfo::showProperties()	: lanconninfo.cpp
  CallAdapterIPProperties()	: %_WINCEROOT%/public/common/oak/inc/netui.h
  CallUAdapterIPProperties()	: %_WINCEROOT%/public/common/oak/inc/netui_user.h
 netui.dll
  AdapterIPProperties()		: network.c


ちなみに、CallUAdapterIPProperties() による AdapterIPProperties() の呼び出しでは、netui.dll を LoadLibraryW() でロードして GetProcAddressW() により関数ポインタを取得する、という方法で行っています。そのため、connmc.exe のリンクライブラリを見ても、netui.lib が入っておらず、connmc.exe が netui.dll で実装されている関数を呼び出すということは、分からないでしょう。やはり、カーネルデバッガを使って追いかけるのが簡単です。

■IP アドレス設定を変更する処理手順
さて、本題に戻ります。”Network and Dial-up Connections” アプレット、つまり connmc.exe が、ネットワークアダプタの IP アドレスを設定する処理は、ソースを追ってみると、次の手順であることが分かります:

  1. レジストリの [HKEY_LOCAL_MACHINE\Comm\<アダプタ名>\Parms\Tcpip] キー配下の各キー値を設定する。
  2. ネットワークスタック(NDIS)に対して、アダプタの設定変更動作を DeviceIoControl() で要求する。

レジストリの [HKEY_LOCAL_MACHINE\Comm\<アダプタ名>\Parms\Tcpip] キー配下の各キー値を設定するのは、上で述べた、netui.dll の AdapterIPProperties() です。そして、DeviceIoControl() を使ってアダプタの設定変更動作を NDIS に要求するのが、connmc.exe の LanConnInfo::showProperties() です。ここで、NDIS のデバイス名は “NDS0″、アダプタの設定変更動作を要求する ioctl コードは IOCTL_NDIS_REBIND_ADAPTER です。詳細は、lanconninfo.cpp と、NDIS 関連の定数を定義しているヘッダファイル(%_WINCEROOT%/public/common/sdk/inc/ntddndis.h)をご覧になってみて下さい。

というわけで、アプリケーションから IP アドレス設定を変更するには、レジストリを書き換えた後に NDIS を DeviceIoControl() で呼び出して設定変更を反映させればよいことが分かりました。IP アドレス設定を変更するコマンドラインユーティリティを作る場合は、この方法で試してみて下さい。

Add comment 2012/07/22 koga

ネイティブコードから C# のメソッドをコールバック

今回は、ネイティブコード(アンマネージコード)から C# のメソッド、つまりマネージドコードをコールバックする方法について紹介します。

■C# のコールバック実装
ネイティブコードから C# のメソッドをコールバックさせる場合の手順を説明する前に、C# でコールバック関数を実装する方法について、簡単に説明します。C#/CLR の delegate についてご存じの方は、以下の説明は飛ばして、次の「ネイティブコードからコールバックさせる場合の手順」をご覧になって下さい。

C# でコールバック関数を実装する場合は、delegate を定義して、コールバック関数として用いるメソッドを、その delegate に割り当てます。delegate については、C# のプログラミングガイドの、次のページで説明されています。

 delegate (C# Reference)
 http://msdn.microsoft.com/en-us/library/900fyy8e%28v=VS.80%29.aspx

 Delegates (C# Programming Guide)
 http://msdn.microsoft.com/en-us/library/ms173171(v=vs.80).aspx

上のページで説明されているように、delegate は、メソッドに対する参照型(reference type)です。delegate は、delegate キーワードを使って、通常のメソッドと同じように宣言しますが、delegate と同じシグネチャを持つメソッドを、その実体として割り当てることができます。この時、宣言された delegate に対して、System.Delegate クラスの派生クラスがコンパイラによって生成され、インスタンス化されます。Delegate クラスのリファレンスは、次のページにあります。

 Delegate Class
 http://msdn.microsoft.com/en-us/library/system.delegate(v=vs.90).aspx

なお、delegate に割り当てることのできるメソッドは、static である必要はなく、インスタンスメソッドを割り当てることが可能です。たとえば、System.Threading の Thread クラスは、コンストラクタの引数に ThreadStart という delegate を受け取り、その delegate に割り当てられたメソッド実体を、インスタンス内部で生成するスレッドによって実行します。ここで、インスタンスメソッドを delegate に割り当てることができるので、C++ の場合とは違い、クラスのインスタンスは渡す必要がない、というわけです。

■ネイティブコードからコールバックさせる場合の手順
さて、本題です。ネイティブコードから C# のメソッドをコールバックさせる手順ですが、実は簡単です。必要な手順は、次の三つです:

  1. コールバックさせるメソッドに対応する delegate を定義する。
  2. 定義した delegate にメソッドを割り当てたのち、System.Runtime.InteropServices.GCHandle 構造体の Alloc() メソッドを適用して、delegate 実体に対する GCHandle インスタンスを生成する。
  3. delegate 実体に、System.Runtime.InteropServices.Marshal クラスの GetFunctionPointerForDelegate() を適用して、その戻り値を、関数ポインタとしてネイティブコードに渡す。

C のネイティブコード API で、コールバック関数の関数ポインタを受け取るものを使ってコールバックする場合ですと、関数ポインタの引数を IntPtr として P/Invoke の宣言を行い、System.Runtime.InteropServices.Marshal クラスの GetFunctionPointerForDelegate() の戻り値(IntPtr 型)を、関数ポインタの引数として渡せばよいのです。ただし、それだけですと、その関数ポインタに関連づけられた delegate の実体、つまり C# のメソッド(インスタンスメソッドの場合のインスタンスおよび、クラス)が、ガベージコレクション(GC)によって解放されてしまう可能性があります。そのため、メソッドを割り当てた delegate 実体に対して GCHandle のインスタンスを生成し、GC の対象外にする、というわけです。

GCHandle 構造体および、System.Runtime.InteropServices.Marshal クラスの GetFunctionPointerForDelegate() については、リファレンスの次のページをご覧ください。

 GCHandle Structure
 http://msdn.microsoft.com/en-us/library/khk3k17t(v=vs.90)

 Marshal.GetFunctionPointerForDelegate Method
 http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.getfunctionpointerfordelegate(v=vs.90)

ここで述べた、ネイティブコード(アンマネージコード)からマネージドコードを呼び出す手順については、Bojan Resnik という人の Blog でも紹介されています:

 Passing C++/CLI delegate to native code
 http://resnikb.wordpress.com/2009/05/18/passing-ccli-delegate-to-native-code/

この Blog エントリでは、C# ではなく、C++/CLI の場合の例を説明していますが、C# の場合も同様です。上の説明と合わせ、参考にしてみて下さい。

■おまけ: .NET CF のガベージコレクション
上の説明で、ガベージコレクション(GC)について述べましたので、ついでに、.NET CF の GC について留意点を書いておきます。

まず、.NET CF の GC は、フル版の .NET Framework の GC とは実装が異なります。フル版の .NET Framework では(より正確には、フル版の .NET Framework の CLR では)、世代別 GC かつ、コンカレントまたはバックグラウンド GC が実装されています。詳しくは、次のページをご覧ください。

 Fundamentals of Garbage Collection
 http://msdn.microsoft.com/en-us/library/ee787088

一方、.NET CF では、コンパクション付きのマーク&スイープ GC です。これについては、.NET CF チームの Steven Pratschner という人が MSDN Blog に書いたエントリが参考になります:

 An Overview of the .Net Compact Framework Garbage Collector
 http://blogs.msdn.com/b/stevenpr/archive/2004/07/26/197254.aspx

 The Design of the .Net Compact Framework CLR, Part III: GC Heap Management
 http://blogs.msdn.com/b/stevenpr/archive/2005/12/14/503818.aspx

.NET CF の GC は、コンカレント GC ではなく、また、世代別 GC でもないため、GC が起きた際の、GC 実行中におけるマネージドコード全スレッドの停止と、停止期間について留意する必要があります。一般論としては、マネージドコードでは、リアルタイム処理に関わる動作を行わず、リアルタイム処理は全てアンマネージコードに任せるか、または、頻繁に GC が起きないように設計する方が良いと思います。GC が頻繁に起きないようにする方策としては、マネージドコードでも、バッファやオブジェクトのプールを実装し、過度に GC に頼らないようにすることが考えられます。もちろん、GC に頼らないということにこだわり過ぎて、全てのオブジェクトのメモリ管理を自前で行おうとして複雑な設計や実装をするのは、本末転倒です。とはいえ、数百KB以上のサイズのオブジェクトやバッファ領域を頻繁に使用する場合には、それらを都度 new で生成して GC に回収させるのではなく、あらかじめ必要な個数を生成しておき(プーリング)、それらを使いまわすプール方式で実装する方が良い場合は、少なくないと思います。

GC の場合も、チューニングを行う場合には、定性的な評価・予測に頼るのではなく、実測に基づく、定量的な評価・分析が重要です。たとえば、「GC.Collect() をアプリケーションが呼ぶのは間違いか?」という意味のタイトルの次の質問に対する回答でも、「それが有効な場合は、レアケースとして考えられる。ただし、十分に実測して、そのうえで効果を判定すべし。」と書かれています:

 What’s so wrong about using GC.Collect()?
 http://stackoverflow.com/questions/118633/whats-so-wrong-about-using-gc-collect

2 comments 2012/05/14 koga

.NET CF で Win32 API を P/Invoke する時の注意点

WEC/WinCE で、.NET CF (.NET Compact Framework) のクラスライブラリでは提供されていない API を使う場合は、CLR (Common Language Run-time) の P/Invoke を利用する必要があります。P/Invoke の仕組み自体は、.NET Framework と同じですが、.NET CF における制約や、WEC/WinCE での Win32 API の細かな違いがあり、.NET Framework と全く同じにはできない場合があります。

以下では、それらの注意点のうち、二点紹介します。

■Win32 API の「実際の」名前が違う場合
P/Invoke を使って Win32 API を呼び出す場合に、.NET Framework では動作したコードが、.NET CF では動作しない、という場合があります。つまり、Windows 7 などでは動作した C# のコードを、WEC/WinCE 用にビルドして動かそうとすると、例外送出が起きて動作しない、という場合があるのです。これは、その API の実際の名前が、WEC/WinCE では違っていることが原因です。

例1)InternetConnect()
wininet.dll で提供されている InternetConnect() は、マクロであり、ネイティブコード(アンマネージコード)の ANSI ビルドでは InternetConnectA(), Unicode ビルドでは InternetConnectW() の呼び出しに置換されます。ここで、.NET Framework の場合には、ANSI ビルド用の InternetConnectA() も使うことができますが、WEC/WinCE は Unicode のみであるため、InternetConnectA() は提供されておらず、使うことが出来ません。従って、InternetConnectW という名前を指定しなければいけません。

例2)SetEvent()
Windows 7 などでは、SetEvent() は kernel32.dll で提供されている一方、WEC/WinCE では、SetEvent() は coredll.dll で提供されています。DLL の名前が違うことに加え、.NET CF で SetEvent に対して P/Invoke 呼び出しを行うと、そのような関数は実装されていないというエラーになります(例外が起きます)。実は、WEC/WinCE では、SetEvent() という名前の API 関数の実体は、存在しないのです。何が違うのかは、両者のリファレンスから辿って、関数宣言のヘッダファイルを調べてみると分ります:

 ・デスクトップ版の SetEvent()
 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686211(v=vs.85).aspx 

 ・WEC/WinCE 版の SetEvent()
 http://msdn.microsoft.com/en-us/library/ee488785.aspx

上の二つのページで、”Requirements” の項にある表の、”Header” を見て下さい。デスクトップ版では、WinBase.h ですが、WEC/WinCE 版では kfuncs.h となっています。WEC/WinCE のソースツリーにある winbase.h を見てみると(%_WINCEROOT%/public/COMMON/sdk/inc/ にあります)、SetEvent() の宣言は、#ifndef UNDER_CE … #endif で囲われており、WEC/WinCE では無効になっています。そして、それらの関数は、kfuncs.h で宣言/定義されているというコメントがあるのです。そこで、kfuncs.h を見てみると、SetEvent() は、_inline 修飾子を使ってインライン関数として定義され、EventModify() という関数を呼び出していることが分かります。EventModify() は、二つの引数を取り、SetEvent() の場合は EVENT_SET という定数(値は3)を第二引数に渡します。つまり、WEC/WinCE では、SetEvent() という名前の関数の実体は、存在しないのです。SetEvent() の呼び出しは、EventModify() の呼び出しに展開されますので、P/Invoke する場合は、EventModify という名前を指定しなければいけません。

このように、同じ名前の Win32 API であっても、WEC/WinCE では実体が異なる場合があるため、.NET Framework では動いたコードが、.NET CF では動かない、という場合があるのです。Win32 API を P/Invoke する場合には、リファレンスを見て、WEC/WinCE では実体が異なっていないかどうか確認してみて下さい。リファレンスだけでは分かりづらいですから、見落とす場合もあると思います。従って、Win32 API を P/Invoke する場合は、必ず単体テストして、期待通りに動作するかどうかを確認し、エラーが起きる場合には、ヘッダファイルをチェックしてみるのが良いでしょう。

■P/Invoke のマーシャリングに関する制約
P/Invoke における、関数引数のマーシャリングは、.NET Framework と .NET CF で一部異なっています。次のページに、その違いがまとめられています:
 An Introduction to P/Invoke and Marshaling on the Microsoft .NET Compact Framework
 http://msdn.microsoft.com/en-us/library/aa446536.aspx

ここで述べられている違いの中で、Win32 API を P/Invoke する際に注意が必要なのは、.NET CF の場合、構造体メンバ中に参照型(reference type)があっても、マーシャリングされない、という点です。上のページの、”Passing Structures” という項に書かれています。これは、文字列型のメンバを持つ構造体を引数に受け取る API 関数を P/Invoke する際に、問題となります。

たとえば、Windows ファイルサーバ(CIFS サーバ)との接続などを行う、WNetAddConnection3() は、NETRESOURCE 構造体を引数に受け取りますが、この NETRESOURCE 構造体は、文字列型のメンバを持ちます。なお、WNetAddConnection3() の実体は、WNetAddConnection3W() です。

ここで、関数の引数が文字列を受け取る場合は、.NET CF の場合、string オブジェクトをそのまま渡せます。前述した InternetConnectW() の引数や、WNetAddConnection3W() の引数 lpPassword と lpUserName が、その例です。しかし、構造体のメンバが文字列の場合には、マーシャリングが行われないので、特別な対応が必要です。この詳細については、次のページで説明されています。

 Advanced P/Invoke on the Microsoft .NET Compact Framework
 http://msdn.microsoft.com/en-us/library/aa446529.aspx

上のページでは、いくつかの方策が示されていますが、真ん中ほどにある “Using a Managed String Pointer” という項で説明されている方策が、お手頃ではないかと思います。この方策は、IntPtr 型のメンバを持つ CLR の struct(構造体)を定義して、マネージドコードの文字列(string)とアンマネージコードの文字列の間の変換(マーシャリング)を自前で行う、というものです。上のページの例では、文字列マーシャリング用の StringPtr という CLR の構造体と、StringPtr が使う Memory というクラスを VB .NET で定義しています。同様のことを、C# でもう少し単純に定義すると、次のようにも書けます:

using System;
using System.Runtime.InteropServices;

public struct NativeString {
    private IntPtr szString;

    // constructor
    public NativeString(string strObj) {
        int len = (strObj.Length + 1) * 2;

        szString = Marshal.AllocHGlobal(len);
        Marshal.Copy(strObj.ToCharArray(), 0, szString, strObj.Length);
        Marshal.WriteByte(szString, len - 2, 0);
        Marshal.WriteByte(szString, len - 1, 0);
    }

    // release the resource
    public void    Dispose() {
        if (IntPtr.Zero != szString)
        {
            Marshal.FreeHGlobal(szString);
            szString = IntPtr.Zero;
        }
    }
}


この NativeString を使って、NETRESOURCE を次のように定義できます。

public struct NETRESOURCE {
    public int  dwScope;
    public int  dwType;
    public int  dwDisplayType;
    public int  dwUsage;
    public NativeString lpLocalName;
    public NativeString lpRemoteName;
    public IntPtr lpComment;
    public IntPtr lpProvider;

    // constructor
    public NETRESOURCE(string localName, string remoteName) {
        dwScope       = 0;
        dwType        = 1;  // RESOURCETYPE_DISK
        dwDisplayType = 0;
        dwUsage       = 0;
        lpLocalName   = new NativeString(localName);
        lpRemoteName  = new NativeString(remoteName);
        lpComment     = IntPtr.Zero;
        lpProvider    = IntPtr.Zero;
    }

    // release the resource
    public void Cleanup() {
        lpLocalName.Dispose();
        lpRemoteName.Dispose();
    }
}

なお、NativeString は、クラスではなく、構造体(struct)でなければならないことに注意して下さい。NativeString は、Win32 API、つまりアンマネージコードと同じメモリレイアウトを持った NETRESOURCE 構造体のメンバとして使いますので、参照型であるクラスではなく、値型(value type)である構造体として定義する必要がある、というのがその理由です。

以上、.NET CF から Win32 API を P/Invoke する場合に注意すべき点を二つ紹介しました。.NET CF からのアンマネージコードの呼び出しについては、以下の Technical Articles セクションに、他にも記事がありますので、興味のある方は、ご覧になってみて下さい。

 Native Interoperability
 http://msdn.microsoft.com/en-us/library/aa145821.aspx

上の記事の中には、.NET CF 2.0 を扱ったものもあり(※現時点での .NET CF の最新版は、3.5 です)、少し古く感じられるものもありますが、今でも通用する内容がほとんどだと思います。

Add comment 2012/05/02 koga

.NET CF アプリケーションとカーネルランドを一緒にデバッグ(2/2)

前回の続きです。アプリケーションとカーネルランドを一緒にデバッグするやり方に関連して、いくつか補足します。

■カーネルデバッガと一緒に動かす場合の設定(※ネットワークインタフェースが一つだけの場合)
さて、カーネルデバッガとアプリケーションデバッガを一緒に動かす場合、一つ注意しなければいけません。それは、WinCE/WEC に固定 IP アドレスを設定する場合のレジストリキーです。前回の「ターゲットボードとホスト PC を直結する場合」の説明で、IP アドレスを設定する場合のレジストリキーが
 [HKEY_LOCAL_MACHINE\Comm\<アダプタ名>\Parms\Tcpip]
だと書きました。カーネルデバッガを動かす場合、この「アダプタ名」が、動かさない時と違う場合があるのです。それは、ターゲットボードにネットワークインタフェースが一つしかなく、アプリケーションデバッガの Ethernet 接続と、カーネルデバッガの Ethernet 接続が、一つのネットワークインタフェースを共有する(つまり、Ethernet ケーブルを一本しか使わない)場合です。

その場合、カーネルデバッガを動かす時は、アダプタ名として “VMINI1″ を指定しなければいけません。VMINI1 というのは、前回の例で示した “FEC1″ のような、Ethernet コントローラを直接制御するデバイスドライバではなく、仮想のネットワークデバイス(論理デバイス)のアダプタ名です。この仮想ネットワークデバイス(VMini)は、リファレンスの次のページで説明されています:

 Ethernet Debugging Services (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee478406(v=winembedded.60).aspx

WEC 7 のリファレンスには、VMini について説明した個所が見当たりません。もしかすると、WEC 7 からは、VMini の使用を推奨しておらず、そのため、説明を省略しているのかも知れません。なお、KITL についてご存じの方は、ここと、次の説明(「KITL について、もう少しだけ」)は飛ばして下さい。

以下は、KITL について詳しくない方のための説明です。

上のページの図を見ると、”VBridge” というモジュールが、”Vmini.dll” と “EDBG” の二つのモジュールに繋がっており、”Vmini.dll” は、NDIS インタフェースを介して TCP/IP スタックと繋がっています。この “VBridge” が、カーネルデバッガとアプリケーションデバッガが一つのネットワークインタフェースを共有するためのモジュールで、”Vmini.dll” が VMini のことです。VMini は、ネットワークインタフェース、つまり Ethernet コントローラを直接制御せず、VBridge に委譲して間接的に制御します。VBridge は、VMini に加えて、”EDBG”、つまり、カーネルデバッガが使用する Ethrnet 通信機能のモジュールに対してもネットワークインタフェース機能を提供します。

この VBridge の仕組みから分かるように、カーネルデバッガは、Ethernet 経由でホスト PC と通信しますが、TCP/IP スタックは使用せず、独自のプロトコルで通信します。TCP/IP スタックは、カーネルの上位にありますから、カーネルデバッガが TCP/IP スタックを利用することは、できないのです。カーネルデバッガがホスト PC と通信するプロトコルは、KITL (Kernel Independent Transport Layer) と名付けられており、上のページの一つ上のドキュメント階層で説明されています。

 Kernel Independent Transport Layer (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee479323(v=winembedded.60).aspx

 Kernel Independent Transport Layer (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee479323.aspx

KITL の説明は、WEC 7 のリファレンスにもあり、どちらも同じ図が載っています。この図の “KITL” の、”Device hardware” に近い側の部分(下側の部分)に先ほどの図の “VBridge” があり、そして、”Kernel debugger” に近い側の部分(上側の部分)に “EDBG” があると考えて下さい。

本題に戻ります。ネットワークインタフェースが一つしかなく、それを KITL と TCP/IP スタックで共有する場合、通常の Ethernet コントローラのドライバでは対応できません。そのため、通常の Ethernet コントローラのドライバ(先の例の “FEC1″)は使われず、VMini が代わりに OS イメージに組み込まれ、VBridge の働きにより、TCP/IP スタックと KITL がネットワークインタフェースを共有する、というわけです。そのため、固定 IP アドレスを設定する場合、レジストリキーで指定する TCP/IP スタックのアダプタ名を、VMini のアダプタ名である “VMINI1″ にしなければいけないのです。

■KITL について、もう少しだけ
ところで、KITL は Ethernet 接続でしか使えないかといえば、そうではありません。Kernel *Independent* Transport Layer という名前の通り、Ethernet 以外の様々な伝送路に対応できます。そして、Ethernet の KITL を使用して、TCP/IP スタックと Ethernet コントローラを共有する場合は、TCP/IP スタックが Ethernet コントローラを占有する場合に比べて、Ethernet による通信のパフォーマンスが低下します。これは、同時に二種類の通信が同じ伝送路を使うせいでもでありますし、また、VMini と VBrige、および、VBridge が呼び出す Ethernet コントローラの制御ルーチン(※この制御ルーチンは、OAL の一部として実装されます)の組み合わせによる動作は、通常の Ethernet コントローラのドライバの単体動作に比べてオーバーヘッドが大きいため、パフォーマンスを出しづらい、という事情もあると思われます。

そのため、WinCE/WEC のネットワークのパフォーマンス評価を行う場合は、VMini を無効にした状態で行うようにという注意書きがリファレンスに書かれています。上の KITL の説明ページにも、KITL と OS(TCP/IP スタック)がハードウェアを共有するとパフォーマンスに悪影響がある、という但し書きがあります。

年々、組み込み機器用のプロセッサは高機能化が進み、多くの周辺機器のコントローラを内蔵するようになってきています。そのため、カーネルデバッガ/KITL と TCP/IP スタックが一つのネットワークインタフェースを共有しなくても、たとえば KITL にはシリアルや USB での接続を割り当て、Ethernet コントローラは TCP/IP スタックに占有させる、といった構成は可能です。逆に、プロセッサに内蔵された Ethernet コントローラはデバッグ専用にして KITL に割り当て、TCP/IP スタック用には、USB や SD の WiFi モジュールを使う、というような構成や、あるいは、安価な Ethernet コントローラを外付けして、そちらをデバッグ専用に使う、という方策も考えられます。

いずれにせよ、VMini による、TCP/IP スタックと KITL の共存は、デバッグ専用のものであり、通常構成で使うものではありません。もちろん、Ethernet コントローラのドライバをデバッグする場合には、VMini は利用できません(※Ethernet コントローラのドライバをデバッグしようとすると、同じコントローラを使う KITL の動作に影響を与えるから・・・ではなく、そもそも、デバッグしたいドライバの代わりに VMini が動作するので、デバッグする方法がありません)。

■ソースコードも、見てみましょう
上で述べた VBridge は、KITL (kitl.dll) に組み込まれるモジュールとなっており、そのソースコードは、WEC 7 と WinCE 6.0 のソースツリーの、それぞれ次の場所にあります:

 ・WEC 7
  %_WINCEROOT%/platform/common/src/common/kitl/

 ・WinCE 6.0
  %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/ETHDBG/VBRIDGE/

WinCE 6.0 では、VBridge は vbridge.lib という static library でしたが(※ソースファイルが vbridge.c 一つだけのライブラリ)、WEC 7 では、oal_kitl.lib という static library にまとめられています。oal_kitl.lib は、WinCE 6.0 にもありますが、VBridge は別のライブラリになっていました。WEC 7 になって、VBridge(vbridge.c) が oal_kitl.lib にまとめられた、というわけです。

ちなみに、カーネルデバッガの OS 側モジュール(kd.dll)のソースコードは、WEC 7 も WinCE 6.0 も、次の場所にあります:

 %_WINCEROOT%/private/winceos/COREOS/nk/kdstub/

興味のある方は、どのような実装になっているのか、ソースコードをご覧になってみると面白いんじゃないかと思います。

■おまけ:ネイティブコードのアプリケーションの場合
さて、.NET CF アプリケーションではなく、ネイティブコード(アンマネージドコード)のアプリケーションをデバッグする場合は、どうなのでしょうか?

Visual Studio の「スマートデバイス」プロジェクトで作成したネイティブコードのアプリケーションをデバッグする手順は、.NET CF アプリケーションの場合と同様です。前回と今回で説明した手順を使えば、ネイティブコードのアプリケーションも、カーネルランドと一緒にデバッグできます。試してみて下さい。

ここで、注意深い方であれば、「あれ?」と思われたかも知れませんね。前回の説明では、冒頭で次のように書いたからです:

 皆さんご存じの通り、ネイティブコードのアプリケーションの場合には、アプリケーションもカーネルランドも、どちらもカーネルデバッガでデバッグできます。

これは、何だったのでしょうか?前回の冒頭で書いたのは、OS Design のサブプロジェクトとしてアプリケーションを作成した場合のことを念頭に置いていました。つまり、OS イメージをビルドする際に、OS と一緒にビルドして、デフォルト設定では OS イメージに組み込まれるようにした場合のことを指していたのです。その場合は、カーネルデバッガを使って、カーネルランドと同時にアプリケーションをデバッグできます。

では、OS Design のサブプロジェクトで作成したアプリケーションではなく、Visual Studio の「スマートデバイス」プロジェクトで作成したネイティブコードのアプリケーションは、カーネルデバッガを使ってデバッグできないのでしょうか?実は、できます。

Visual Studio の「スマートデバイス」プロジェクトで作成したネイティブコードのアプリケーションを、カーネルデバッガでデバッグするには、アプリケーションの実行ファイル(.exe)とプログラムデータベースファイル(.pdb)を、カーネルデバッガが認識できる場所に置けばよいのです。OS Design のカタログ項目で、”Target Control Support(Shell.exe)” か “Release Directory File System” を選択して OS イメージをビルドしておき、Flat Release Directory に、デバッグしたいアプリケーションの .exe と .pdb をコピーする、というのが一番お手軽だと思います。こうしておけば、わざわざアプリケーションデバッガを別に動かさなくとも、カーネルデバッガだけで、アプリケーションとカーネルランドを一緒にデバッグできます。

たとえば、OS を開発するチームとアプリケーションを開発するチームが分かれていて、アプリケーションで不具合が起きた場合に、不具合の要因がアプリケーション側にあるのか OS 側にあるのか切り分け調査を行うなどの場合、この方法は便利かも知れません。アプリケーション開発チームは、OS Design のサブプロジェクトではなく、Visual Studio の「スマートデバイス」プロジェクトでアプリケーションを作成するのが一般的だと思います。そのような場合に、カーネルランドとアプリケーションを同時または一緒にデバッグする方策は、次のものが考えられます:

  1. カーネルデバッガとアプリケーションデバッガを各々動かして、カーネルランドとアプリケーションを一緒にデバッグする。
  2. 「スマートデバイス」プロジェクトを OS Design のサブプロジェクトに作り直し、アプリケーションを OS Design に組み込んだうえで、カーネルデバッガを使ってカーネルランドとアプリケーションを一緒にデバッグする。
  3. 「スマートデバイス」プロジェクトは、そのままにして、アプリケーションの .exe と .pdb を Flat Release Directory にコピーしてからカーネルデバッガを動かす(上述した方法)。

上の1は、前回と今回の主題である、.NET CF アプリケーションをカーネルランドと一緒にデバッグするのと同じ方策です。2と比べると、3の方が手間が少ないのは明らかです。1と3を比べても、カーネルデバッガだけで作業できるという点で、3の方が、より手間が少ない方法です。

■おまけ2:その他の参考資料
前回と今回では、カーネルランドと一緒(または同時に)アプリケーションをデバッグするにはどうすればよいか、ということを説明しました。「スマートデバイス」プロジェクトで作ったアプリケーションだけをデバッグする場合については、Visual Studio のリファレンスに説明があります。いくつかのトピックが取り上げられていますので、ご覧になってみて下さい:

 Debugging Device Projects
 http://msdn.microsoft.com/en-us/library/ms180772(v=vs.90).aspx

ネイティブコードのアプリケーションに関しては、MFC や ATL を使ったアプリケーションをビルド・デバッグする場合の注意点や手順についても、解説されています。

 Building and Debugging Visual C++ Device Projects
 http://msdn.microsoft.com/en-us/library/c5fc53wa(v=vs.90).aspx

1 comment 2012/04/13 koga

.NET CF アプリケーションとカーネルランドを一緒にデバッグ(1/2)

今回は、マネージドコードのアプリケーション、つまり、.NET CF のアプリケーションとカーネルランドを、Ethernet 経由で一緒にデバッグする方法を説明します。「一緒にデバッグする」というのは、”「同時にデバッグする」ことはできないが、アプリケーションデバッガとカーネルデバッガを各々動かすことにより、アプリケーションとカーネルランドを、それぞれのデバッガでデバッグする”という意味です。

皆さんご存じの通り、ネイティブコードのアプリケーションの場合には、アプリケーションもカーネルランドも、どちらもカーネルデバッガでデバッグできます。たとえば、アプリケーションからデバイスドライバを呼び出した場合、デバイスドライバのソースコードにブレークポイントを設定してブレークした時、アプリケーションからデバイスドライバまでのコールスタック(「呼び出し履歴」)を見ることができます。アプリケーションがデバイスドライバを呼び出す場合、システムコールを経由しますが、カーネルデバッガは、その経路を把握して、あたかも通常の関数呼び出しのように、コールスタックを表示してくれるのです。僕は、このように、一つのデバッガだけでアプリケーションとカーネルランドをデバッグすることを指して「同時にデバッグする」と表現しています。

一方、マネージドコード(.NET CF)のアプリケーションの場合には、一つのデバッガだけでアプリケーションとカーネルの両方をデバッグすることは、できません。アプリケーションのデバッガとカーネルデバッガを各々動かす必要があるのです。このことを指して、ここでは「一緒にデバッグする」と表現します。

■Ethernet 経由でのアプリケーションデバッガ接続
Ethernet 経由で WinCE/WEC のアプリケーションデバッガを接続する場合は、ActiveSync は使用しません。ActiveSync は、2005年にリリースされた 4.0 から TCP/IP 接続をサポートしていませんから、使えないのです。ActiveSync を使わずに、TCP/IP で WinCE/WEC に接続する手順は、Visual Studio のリファレンスのページで説明されています:

 How to: Connect to Windows CE Device Without ActiveSync
 http://msdn.microsoft.com/en-US/library/ms228708(v=vs.90).aspx

上のページは、Visual Studio 2008 のリファレンスです。日本語の説明を読みたい場合は、Visual Studio 2005 ですが、ユニダックス社の「CE6.0 技術情報」に日本語の説明があります:

 11. アプリケーションのリモートデバッグ
 http://www.unidux.co.jp/embedded/techinfo/ce6/000437.php

上のページで説明されているように、Visual Studio に付属している次の5つのファイルを、WinCE/WEC の /Windows ディレクトリへコピーして、Visual Studio のデバイス設定を行うことにより、TCP/IP でアプリケーションデバッガを接続できます。

 Clientshutdown.exe
 ConmanClient2.exe
 CMaccept.exe
 eDbgTL.dll
 TcpConnectionA.dll

WEC 7 で、AMR9 コアの CPU のボードに接続する場合ですと、上記のファイルは、次の場所にあります。

 C:\Program Files\Common Files\microsoft shared\CoreCon\1.0\Target\wce400\armv5\

これらのファイルを、WinCE/WEC が起動した後に、/Windows ディレクトリに USB メモリなどを使ってコピーするか、または、OS Design の project.bib ファイルを編集して OS イメージに組み込めば、使えます。

または、OS Design のカタログ項目で、”Target Control Support(Shell.exe)” か “Release Directory File System” を選択している場合は、Flat Release Directory(環境変数 _FLATRELEASEDIR が指すディレクトリ)に上記5つのファイルを置いても、使えます。Target Control Support を有効にしてカーネルデバッガを使っている場合には、これが一番お手軽でしょう。

■OS Design から作った SDK との関連づけ
マネージドコードのアプリケーションも、ネイティブコードのアプリケーションも、WinCE/WEC のアプリケーションは、Visual Studio の「スマートデバイス」プロジェクトで作成します。それらのアプリケーションをデバッグする場合は、上で紹介したページで説明されている手順で、WinCE/WEC と接続する必要があります。

Visual Studio 2008 日本語版と WEC 7 の場合ですと、以下のような手順になります。

  1. 上記の5つのファイルを、WEC 7 の /Windows ディレクトリへコピーする。
  2. WEC 7 を DHCP 有効で動かしている場合は、コマンドプロンプトで ipconfig を実行して、WEC 7 の IP アドレスを調べる。
  3. Visual Studio 2008 の [ツール] > [オプション...] メニューを選択し、「オプション」ダイアログを開く。
  4. 「オプション」ダイアログの左端にあるツリービューで、[デバイスツール」を展開し、その下にある「デバイス」を選択する。
  5. 「オプション」ダイアログの右端に表示された「デバイス」画面にある、「デバイス(V):」というリストボックスから、アプリケーションのビルドに使った SDK のデバイスを選択する。
  6. 「デバイス(V):」リストボックスの右側にある「プロパティ」ボタンをクリックして、選択したデバイスのプロパティダイアログを開く。
  7. プロパティダイアログの「トランスポート(R):」というドロップダウンリストから、「TCP 接続トランスポート」を選択する。
  8. 「トランスポート(R):」ドロップダウンリストの右側にある「構成(C)...」ボタンをクリックして、「TCP/IP トランスポートの構成」ダイアログを開く。
  9. 「TCP/IP トランスポートの構成」ダイアログにある二つのラジオボタンのうち、「特定の IP アドレスを使用(S):」を選択する。
  10. 「特定の IP アドレスを使用(S):」の下にあるエディットフィールド兼ドロップダウンリストに、WEC 7 の IP アドレスを入力して設定する。
  11. 「TCP/IP トランスポートの構成」ダイアログ、プロパティダイアログ、および「オプション」ダイアログの OK ボタンを順にクリックして、設定内容を確定する。
  12. WEC 7 で、ConmanClient2.exe と CMaccept.exe を順に実行する。
  13. Visual Studio 2008 の [ツール] > [デバイスへの接続...] メニューを選択する。

■ターゲットボードとホスト PC を直結する場合
さて、上の手順では、WinCE/WEC を DHCP 有効で動かしている場合(デフォルトでは、DHCP 有効です)、WinCE/WEC を起動するたびに IP アドレスを調べて Visual Studio の「TCP/IP トランスポート構成」の設定に反映しなければいけません。DHCP 有効ではなく、固定 IP アドレスで動かせば、WinCE/WEC を起動するたびに設定する必要がなくなり、少しだけ手間を減らせます。

あるいは、WinCE/WEC とホスト PC をハブ経由で接続せず、クロスの Ethernet ケーブル(*)で直接接続する場合は、DHCP サーバにアクセスできませんから、WinCE/WEC とホスト PC 共に、固定 IP アドレスを割り当てる必要があります。

※最近のネットワークインタフェースは、ストレートの Ethernet ケーブルで直接接続しても、自動認識してくれるものが多くなっています。従って、WinCE/WEC とホスト PC を Ethernet ケーブルで直接接続する際、クロスケーブルがなければ、ストレートケーブルで接続しても大丈夫な場合があります。手元にクロスケーブルがない場合は、試してみて下さい。
 なお、Ethernet 経由でリモートデバッグする場合、WinCE/WEC とホスト PC をハブ経由で接続するよりも、直接接続する方が、一般的には通信速度が上がりますので、より快適にデバッグできます。

Visual Studio を動かすホスト PC、つまり、Windows 7 や Vista/WindowsXP に固定 IP アドレスを設定する手順は、皆さんご存じだと思いますので、ここでは、WinCE/WEC に固定 IP アドレスを設定する方法を述べます。

WEC 7 と WinCE 6.0 の IP アドレス設定のレジストリは、リファレンスの以下のページで説明されています:

 TCP/IPv4 Configurable Registry Settings (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee494881.aspx

 TCP/IPv4 Configurable Registry Settings (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee494881(v=winembedded.60).aspx

上のページの中ほどにある、”Adapter-specific Values” という表を見て下さい。ここに、”EnableDHCP”, “DefaultGateway”, “IpAddress”, “Subnetmask” が載っています。これらのレジストリ項目を使って、WinCE/WEC に固定 IP アドレスを設定できます。レジストリキーは、これらのページに書かれている通り、
 [HKEY_LOCAL_MACHINE\Comm\<アダプタ名>\Parms\Tcpip]
です。たとえば、弊社が提供している Armadillo-400 シリーズ用の BSP であれば、以下の行を .reg ファイルに書いて OS イメージをビルドすると、クラス C の固定 IP アドレス 192.168.0.50 が設定されます:

[HKEY_LOCAL_MACHINE\Comm\FEC1\Parms\Tcpip]
    "EnableDHCP"=dword:0
    "DefaultGateway"="192.168.0.1"
    "IpAddress"="192.168.0.50"
    "Subnetmask"="255.255.255.0"


ここまでの設定は、Visual Studio のスマートデバイスプロジェクトのアプリケーションを Ethernet 経由でデバッグするための手順です。次に、今回の本題である、マネージドコード(.NET CF)のアプリケーションをカーネルランドと一緒にデバッグする方法を説明します。

■カーネルデバッガも一緒に動かす方法
今回の冒頭で、「.NET CF のアプリケーションとカーネルランドを一緒にデバッグするとは、アプリケーションデバッガとカーネルデバッガを各々動かすことにより、それぞれをデバッグすること」だと述べました。アプリケーションデバッガとカーネルデバッガを各々動かすというのは、どういうことかと言えば、Visual Studio を二つ起動して、一方でアプリケーションデバッガ、もう一方でカーネルデバッガを動かす、ということです。

以下に、もう少し具体的な手順を述べます。以下のようにすれば、アプリケーションデバッガとカーネルデバッガを各々動かして、.NET CF のアプリケーションとカーネルランドを一緒にデバッグできます。

  1. Visual Studio を起動して、OS Design のプロジェクトを開く。
  2. OS Design のプロジェクトをビルドしてできた OS イメージを、ホスト PC と Ethernet 接続したターゲットボードに転送し、カーネルデバッガを有効にして WinCE/WEC を起動する。
  3. Visual Studio をもう一つ起動して、デバッグしたいアプリケーションのプロジェクトを開く。
  4. 上で述べた手順を使って、アプリケーションのプロジェクトを開いた Visual Studio から WinCE/WEC に接続する。
  5. アプリケーションのデバッグを開始する。

このようにすれば、二つ起動した Visual Studio のうち、OS Design のプロジェクトを開いた方でカーネルデバッガが動き、アプリケーションのプロジェクトを開いた方でアプリケーションデバッガが動きます。なお、アプリケーションデバッガで設定したブレークポイントでアプリケーションが停止した場合、OS(WinCE/WEC)は動作し続けていますが、カーネルデバッガで設定したブレークポイントで停止した場合は、OS 全体が停止しますので、アプリケーションの動作も停止します。これは、アプリケーションデバッガとカーネルデバッガで違う点です。

以上で、アプリケーションとカーネルランドを一緒にデバッグする方法の説明は、一応終わりです。ただし、いくつか補足が必要なので、次回に続けます。もし、上の手順を試してみたが、アプリケーションデバッガとカーネルデバッガを一緒に動かせない、という方は、次回の説明もあわせて読んでみて下さい。

Add comment 2012/04/03 koga

.NET CF アプリケーションからデバッグメッセージ出力

C# や Visual Basic で WinCE/WEC のアプリケーションを開発する際に、デバッグメッセージを出力する方法は、いくつかあります:

  • System.Console クラスの Write(), WriteLine() を呼び出す
  • System.Diagnostics.Debug クラスの Write()/WriteIf(), WriteLine()/WriteLineIf() を呼び出す
  • P/Invoke を使って NKDbgPrintfW() を呼び出す

上に挙げた三つの方法について、それらがどう違うのか、以下に述べます。なお、ここでは、.NET CF (.NET Compact Framework) の 3.5 について記します。

■System.Console クラスを使う場合
このクラスのメソッド一覧は、次のリファレンスページに載っています:

 Console Methods
 http://msdn.microsoft.com/en-us/library/system.console_methods(v=vs.90).aspx

System.Console クラスの Write() や WriteLine() を呼び出すと、コンソールウィンドウが開いて、引数に渡した文字列が表示されます。これは、ネイティブコードの場合に、printf() などを使って標準出力へ出力した場合と同じ振る舞いです。クラス名の通り、コンソールデバイスに出力する API というわけです。

■System.Diagnostics.Debug クラスを使う場合
このクラスのメソッド一覧は、次のリファレンスページに載っています:

 Debug Methods
 http://msdn.microsoft.com/en-us/library/system.diagnostics.debug_methods(v=vs.90).aspx

System.Diagnostics.Debug クラスの Write() や WriteLine() を呼び出すと、アプリケーションをデバッガから実行している場合には、デバッガの出力ウィンドウに文字列が出力されます。デバッガから実行していない場合(通常実行の場合)には、何も起きません。この振る舞いは、OutputDebugString() を呼び出した場合と同様です。

このクラスの Write() や WriteLine() の呼び出しが、OutputDebugString() の呼び出しと同様になるのは、System.Diagnostics.DefaultTraceListener クラスの働きによるものです。DefaultTraceListener クラスの説明は、次のリファレンスページに載っています。

 DefaultTraceListener Class
 http://msdn.microsoft.com/en-us/library/system.diagnostics.defaulttracelistener(v=vs.90).aspx

System.Diagnostics.DefaultTraceListener のインスタンスは、System.Diagnostics.Debug クラスの Listeners プロパティのデフォルト要素であり、そのため、Write() や WriteLine() によるデバッグメッセージ出力は、通常は System.Diagnostics.DefaultTraceListener の Write(), WriteLine() に渡されます。そして、System.Diagnostics.DefaultTraceListener は、Write() や WriteLine() に渡された文字列を引数にして OutputDebugString() を呼び出す、というわけです。

ただし、ネイティブコードで OutputDebugString() を直接呼び出した場合とは異なり、アプリケーションをデバッガから実行していないと、System.Diagnostics.Debug の Write() や WriteLine() を呼び出しても、一切出力されません。一方、ネイティブコードから OutputDebugString() を呼び出した場合は、アプリケーションデバッガから実行していなくても、WinCE/WEC のカーネルデバッガを実行していれば、カーネルデバッガの出力ウィンドウに文字列が出力されます。.NET CF のアプリケーションから P/Invoke で直接 OutputDebugString() を呼び出した場合にどうなるかは、確認していませんが、おそらく、ネイティブコードから OutputDebugString() を呼び出した場合と同じになるのではないかと思います。興味のある方は、試してみて下さい。

System.Diagnostics.Debug クラスの使い方については、次のサポートページも参考になるでしょう:

 How to trace and debug in Visual C#
 http://support.microsoft.com/kb/815788/en-us

■NKDbgPrintfW() を P/Invoke で呼び出す場合
さて、ネイティブコードの場合、デバッグメッセージの出力は、DEBUGMSG() マクロを使うのが普通です。C# や Visual Basic では、このマクロは使えませんので、その代わりに、DEBUGMSG() マクロが使っている NKDbgPrintfW() を、P/Invoke で直接呼び出す必要があります。

たとえば、次のページにあるフォーラムの質問でも、そのような回答がなされています:

 DebugMsg macro in C#
 http://us.generation-nt.com/answer/debugmsg-macro-c-help-51715972.html

.NET CF アプリケーションから、P/Invoke で NKDbgPrintfW() を直接呼び出した場合は、アプリケーションをデバッガから実行していない場合でも、WinCE/WEC のカーネルデバッガを実行していれば、カーネルデバッガの出力ウィンドウに文字列が出力されます。.NET CF アプリケーションを、ネイティブコードのアプリケーションやライブラリと一緒にデバッグしていて、追跡したい個所の呼び出しタイミングの関係を知りたい場合には、NKDbgPrintfW() を使うと、デバッグメッセージをカーネルデバッガの出力に渡すことができます。つまり、.NET CF アプリケーションのデバッグメッセージも、ネイティブコードのデバッグメッセージも(さらには、デバイスドライバのデバッグメッセージも)同じ出力に出せますので、それらを同時に追跡したい場合には、便利でしょう。

なお、P/Invoke で NKDbgPrintfW() を直接呼び出すと、ネイティブコードで DEBUGMSG() マクロを使った場合とは異なり、リリースビルドでもデバッグメッセージが出力されてしまいます。その点は、注意して下さい。デバッグビルドの時にだけデバッグメッセージが出力されるようにするには、System.Diagnostics.ConditionalAttribute を使ってデバッグビルドに対してのみ有効にするか、または、#if … #endif を使って条件コンパイル指定して下さい。ちなみに、System.Diagnostics.Debug クラスの Write() や WriteLine() では、System.Diagnostics.ConditionalAttribute を使っています。

Add comment 2012/03/29 koga

Windows Embedded Compact 7 で OpenMP

Visual Studio 2005 以降の Visual C++ は、OpenMP 2.0 をサポートしています。OpenMP は、共有メモリ型の並列処理を記述するための仕組みで、C/C++ と Fortran に対する仕様が、OpenMP Architecture Review Board (OpenMP ARB) によって策定・公開されています。OpenMP の詳細は、OpenMP ARB の Web サイト、および、Visual Studio のリファレンスをご覧ください:

 Open MP
 http://openmp.org/

 OpenMP C and C++ Application Program Interface
 http://msdn.microsoft.com/en-us/library/8y6825×5(v=vs.90).aspx

なお、OpenMP 仕様の最新版は、2011年の 3.1 ですが、Visual C++ がサポートしているのは 2.0 です。

さて、この OpenMP 2.0 は、Windows Embedded Compact 7 (WEC 7) で対応が追加されました。OpenMP のカタログ項目は、Platform Builder の Catalog Items View に表示されるツリーの、以下の位置にあります:

 Core OS
  Windows Embedded Compact
   Application and Services Development
    C Libraries and Runtimes
★    OpenMP Runtime

対応する Sysgen 変数は、SYSGEN_OPENMP です。これについては、リファレンスの次のページに書かれています:

 C Libraries and Runtimes Catalog Items and Sysgen Variables (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/gg155191.aspx

ただし、デスクトップ版の Windows(Windows7 など)とは異なり、WEC 7 の OpenMP 対応では、C/C++ コンパイラの pragma に制限があり、以下のものは使えません。

 omp dynamic
 omp threadprivate

これについては、リファレンスの次のページをご覧ください:

 C/C++ Libraries for Windows Embedded Compact (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee479345.aspx

さらに、WEC 7 の場合は、SMP 対応のボード、つまり、カーネルがマルチコアに対応していなければ、OpenMP を使用できないということが、先ほど述べたカタログ項目のリファレンスのページに書かれています。

マルチコアのプロセッサを搭載した WEC 7 対応のボードをお持ちの方は、試してみると面白いかも知れませんね。Visula C++ で OpenMP を使う場合は、以下のリファレンスページが参考になるでしょう:

 OpenMP in Visual C++
 http://msdn.microsoft.com/en-us/library/tt15eb9t(v=vs.90).aspx

Add comment 2012/02/29 koga

Previous Posts


Categories

Links

Posts by Authors

Recent Posts

Calendar

2017年3月
« 9月    
 1234
567891011
12131415161718
19202122232425
262728293031  

Posts by Month

Posts by Category

Meta