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 comment2012/07/22 koga

Device2Cloudオープンセミナ2012

8/3(金)に、D2C コンテスト実行委員会の主催するオープンセミナが、東京エレクトロンデバイス社の西新宿オフィスで開催されます:
 http://kokucheese.com/event/index/43134/
時間は 13:00~17:00 で、定員40名です。無料ですので、興味のある方は、是非いらして下さい。以下、セミナーの講演タイトル(4件)を記します。

  • 新世代M2Mコンソーシアム
  • 「デバイスとクラウド ~普通のITとの融合でこそ活きる組込み機器」
  • 「IoT(Internet of Things)の世界実現に向けて(仮)」
  • 「リアルとバーチャルの融合がこれからの世界を変える~NTTデータのM2Mクラウドの取り組み~(仮)」

Add comment2012/07/20 koga

WEC 7 の Meiryo フォント

WEC 7 (Windows Embedded Compact 7) で、日本語フォントに Meiryo が追加されたのをご存じの方は、少なくないと思います。WEC 7 に標準で付属するフォントについては、リファレンスの次のページをご覧下さい。

 Fonts Catalog Items and Sysgen Variables (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee489874

上のページに書かれていますが、WEC 7 の Meiryo フォントは、Windows 7 のものと同じバージョンだそうです。Windows 7 と同様に、ClearType を有効にすることで、従来の MS ゴシックに比べて、より綺麗な文字表示が可能となります。ディスプレイドライバに対して ClearType の有効/無効を設定するためのレジストリキーは、次のページにある “ClearType Registry Keys” をご覧下さい。

 Display Driver Registry Settings (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee482851

さて、Meiryo フォントを使う場合に、注意しなければいけないことがあります。それは、”Monotype Imaging AC3 Font Compression” です。以前のエントリ(「圧縮フォント」)で書いたように、このカタログ項目を選択すると、MS ゴシックなどのアジア言語圏用のフォントファイルとして、通常の .ttc ではなく圧縮版の .ac3 が OS イメージに組み込まれます。ところが、OS イメージが小さくなるようにしたいと思って、Meiryo フォントを組み込む場合にも、この “Monotype Imaging AC3 Font Compression” を選択すると、ウィンドウのタイトルバーやメニュー項目、ボタンのラベルなど、標準の GUI 部品の日本語文字列が表示されなくなるのです。

そうなってしまう理由は、%_WINCEROOT%/public/COMMON/oak/files/ ディレクトリにある common.reg と common.bib を見ると分かります。まず、common.reg で “[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\FontLink\SystemLink]” を検索してみて下さい。すると、このキーに対して、MS UI Gothic を代表的なフォントに関連づけるブロックがあって、その次に、Meiryo UI を、代表的なフォントおよび、MS ゴシック系統のフォントファミリに関連づけるブロックがあります。以下に、当該個所を引用します。


; @CESYSGEN IF FONTS_MSGOTHIC || FONTS_MSGOTHIC30 || FONTS_MSGOTHIC30_1_19 || FONTS_MSGOTHIC_1_50 || FONTS_MSGOTHIC_1_60 || FONTS_MSGOTHIC_1_70 || FONTS_MSGOTHIC_1_80 || FONTS_MSGOTHIC_1_90 || FONTS_MSGOTHIC_1_48
[HKEY_LOCAL_MACHINE\SYSTEM\GDI\GLYPHCACHE]
    "limit"=dword:5000
[HKEY_LOCAL_MACHINE\SYSTEM\GDI]
   "FontLinkMethods"=dword:1
; @CESYSGEN IF !FONTS_AC3_VERSIONS
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\FontLink\SystemLink]
      "Arial"="\\Windows\\msgothic.ttc,MS UI Gothic"
      "Courier New"="\\Windows\\msgothic.ttc,MS UI Gothic"
      "Segoe UI"="\\Windows\\msgothic.ttc,MS UI Gothic"
      "Symbol"="\\Windows\\msgothic.ttc,MS UI Gothic"
      "Tahoma"="\\Windows\\msgothic.ttc,MS UI Gothic"
      "Times New Roman"="\\Windows\\msgothic.ttc,MS UI Gothic"
; @CESYSGEN ENDIF !FONTS_AC3_VERSIONS
; @CESYSGEN IF FONTS_AC3_VERSIONS
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\FontLink\SystemLink]
      "Arial"="\\Windows\\msgothic.ac3,MS UI Gothic"
      "Courier New"="\\Windows\\msgothic.ac3,MS UI Gothic"
      "Segoe UI"="\\Windows\\msgothic.ac3,MS UI Gothic"
      "Symbol"="\\Windows\\msgothic.ac3,MS UI Gothic"
      "Tahoma"="\\Windows\\msgothic.ac3,MS UI Gothic"
      "Times New Roman"="\\Windows\\msgothic.ac3,MS UI Gothic"
; @CESYSGEN ENDIF FONTS_AC3_VERSIONS
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\FontLink\SkipTable]
      "Tahoma"="005c,00a5,007e,0391-03c9,2026,2116,221a,25a0-25ff"
; @CESYSGEN ENDIF FONTS_MSGOTHIC

; @CESYSGEN IF FONTS_MEIRYO
[HKEY_LOCAL_MACHINE\SYSTEM\GDI\GLYPHCACHE]
    "limit"=dword:5000
[HKEY_LOCAL_MACHINE\SYSTEM\GDI]
        "FontLinkMethods"=dword:1
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\FontLink\SystemLink]
      "Arial"="\\Windows\\meiryo.ttc,Meiryo UI"
      "Courier New"="\\Windows\\meiryo.ttc,Meiryo UI"
      "MS Gothic"="\\Windows\\meiryo.ttc,Meiryo UI"
      "MS PGothic"="\\Windows\\meiryo.ttc,Meiryo UI"
      "MS UI Gothic"="\\Windows\\meiryo.ttc,Meiryo UI"
      "Segoe UI"="\\Windows\\meiryo.ttc,Meiryo UI"
      "Symbol"="\\Windows\\meiryo.ttc,Meiryo UI"
      "Tahoma"="\\Windows\\meiryo.ttc,Meiryo UI"
      "Times New Roman"="\\Windows\\meiryo.ttc,Meiryo UI"
; @CESYSGEN ENDIF FONTS_MEIRYO

この記述があるために、MS ゴシックと Meiryo のカタログ項目を両方選択した場合、デフォルトのフォントとして Meiryo が設定されます。ここで、FONTS_AC3_VERSIONS が設定されているか否か、つまり、”Monotype Imaging AC3 Font Compression” が選択されているか否かに関わらず、meiryo.ttc が設定されることにも注意して下さい。実は、msgothic とは違い、meiryo には、.ac3 ファイルが存在せず、meiryo.ttc しかありません。興味のある方は、%_WINCEROOT%/public/COMMON/oak/files/ ディレクトリをご覧になってみて下さい。

なお、レジストリの [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\FontLink\SystemLink] キーについては、リファレンスの次のページに説明があります:

 Fonts Linking Registry Settings (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee489864

さて、MS ゴシックと Meiryo のカタログ項目を両方選択した場合、common.reg の記述により、Meiryo がデフォルトのフォントとして設定されることが分かりました。次は、common.bib を見てみましょう。common.bib で “; @CESYSGEN IF FONTS_AC3_VERSIONS” を検索してみて下さい。この行の以下、つまり、FONTS_AC3_VERSIONS が設定されている場合の条件ブロック内には、Meiryo に関する行が存在しないのです。FONTS_AC3_VERSIONS が設定されていない場合の条件ブロック(ELSE ブロック)には、meiryo.ttc が記述されています。

これで、お分かりでしょうか。OS Design で、MS ゴシックと Meiryo のカタログ項目を両方選択すると、Meiryo (meiryo.ttc) がデフォルトのフォントとして設定される一方、”Monotype Imaging AC3 Font Compression” が選択されていると、common.bib の記述により、meiryo.ttc が OS イメージに組み込まれないのです!

というわけで、Meiryo フォントを OS イメージに組み込む場合は、カタログ項目の “Monotype Imaging AC3 Font Compression” を選択しては、いけません。

さて、”Monotype Imaging AC3 Font Compression” については、Silverlight for Windows Embedded アプリケーションの場合も注意が必要です。リファレンスの次のページにある、”Monotype Imaging AC3 Font Compression” の説明をご覧になってみて下さい。

 Japanese Catalog Items and Sysgen Variables (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee491212.aspx

上のページには、AC3 フォント圧縮が、Silverlight for Windows Embedded ではサポートされておらず、そのため、AC3 フォント圧縮を有効にした OS Design では、非圧縮版のフォントファイルをアプリケーションに埋め込む必要がある、と書かれています。ちなみに、WinCE 6.0 のリファレンスにある AC3 フォント圧縮のカタログ項目の説明には、このような注意書きが、見当たりません(※もし、僕が見落としているのであれば、教えて下さい):

 Japanese OS Design Development (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee491212(v=winembedded.60)

WEC 7 において、Meiryo フォントおよび Silverlight for Windows Embedded アプリケーションが AC3 フォント圧縮をサポートしていない理由は、分かりません。もしかすると、Meiryo フォントが AC3 フォント圧縮に向かない仕組みなのかも知れません。Silverlight for Windows Embedded アプリケーションでは、表示を綺麗にするために Meiryo フォントを使うようになっており、そのため、上述したように、AC3 フォント圧縮が有効になっていると日本語が表示されない(meiryo.ttc が OS イメージに組み込まれないため)、ということなのかも知れません。

Add comment2012/07/13 koga

デバイスエミュレータで WEC 7 を動かす

最初にことわっておきますが、今回の内容は、WinCE 6.0 (Windows Embedded CE 6.0) と WEC 7 (Windows Embedded Compact 7 ) を両方お使いの方が主な対象です。WinCE 6.0 しか使っていらっしゃらない方には、特に関係ない内容ですし、また、WEC 7 だけをお使いの方にも、必要性は低い内容だと思います。もし、あなたが、WinCE 6.0 の ARM ベースのデバイスエミュレータをご利用になっていて、同じものを WEC 7 でも使えないだろうか?と思ったことがあるのなら、今回の内容は、あなたのためのものです。お急ぎなら、途中を飛ばして、今回のエントリの最後の方にある「DeviceEmulator BSP を WEC 7 へ移植する」を読んでみて下さい。

■WinCE 6.0 と Windows Mobile のデバイスエミュレータ
Visal Studio 2008 までは、Pro 以上の版に含まれる Smart Device Development 機能の一部として、ARM ベースのデバイスエミュレータが付属しています。このデバイスエミュレータは、単体でも提供されており、最新版の 3.0(VS 2008 付属のものと同じ版)を、以下のページからダウンロードできます:

 Microsoft Device Emulator 3.0
 http://www.microsoft.com/ja-jp/download/details.aspx?id=5352

デバイスエミュレータがエミュレートしているのは、Samsung 製の、S3C2410 という ARM9 コアのプロセッサを搭載したリファレンスボードです。WinCE 6.0 の Platform Builder をお使いの方ならご存知の通り、WinCE 6.0 には、デバイスエミュレータの BSP が付属しています。そのため、WinCE 6.0 を動かす実機がなくても、デバイスエミュレータの BSP を使って OS イメージをビルドすれば、その OS イメージをデバイスエミュレータで動かし、実機に依存しない部分の開発が可能です。

WinCE 5.0 までは、デバイスエミュレータには Virtual PC が使われていました。つまり、ターゲットプロセッサは x86 でした。WinCE 6.0 では、それが ARM プロセッサになった、というわけです。しかし、後述するように、WEC 7 では、再び Virtual PC がデバイスエミュレータとして採用されました。

以下に、WinCE 6.0 のデバイスエミュレータ、つまり、S3C2410 リファレンスボードのエミュレータの特徴をまとめます:

  • Windows Mobile のエミュレータとしても利用されている。
  • 設定ファイルを作成することにより、スキン画像と入力ボタン/キーパッドを設定できる。
  • ARM プロセッサをエミュレートしている。

どれも、Virtual PC ベースのエミュレータには無いものです。専用の BSP を使って作成した OS イメージをロードして動かせるのは、どちらのエミュレータも同じです。一方、エミュレータから利用できるホスト PC の周辺機器機能は、Virtual PC ベースのエミュレータの方が豊富です。WinCE 6.0 のデバイスエミュレータの方は、実質、Ethernet とシリアルポートのみです。

WinCE 6.0 のデバイスエミュレータでは、周辺機器のエミュレート機能として、ホスト PC のマウス入力に対する、タッチパネル入力のエミュレートや、ホスト PC のディレクトリをメモリカードとしてマウント/エミュレートする、というものがあります。Virtual PC に比べると、利用できるホスト PC の周辺機器機能は貧弱ですが、入力ボタン/キーパッドのエミュレートが可能なことと、ARM プロセッサをエミュレートしているのは、便利な場合があります。

さて、WinCE 6.0 のデバイスエミュレータがエミュレート(シミュレート)しているのは、上述したように、Samsung の ARM9 コアのプロセッサ(S3C2410)のリファレンスボードです。S3C2410 の ARM9 コアは、ARM920T、つまり、命令セットが v4T である ARM9TDMI ファミリです。WinCE 6.0 までの ARM コンパイラは、ARMv4 の命令セットにしか対応していませんでしたので、それで十分でした。しかし、WEC 7 の ARM コンパイラでは、ARMv5/v6/v7 のサポートが追加された代わりに、ARMv4 には対応していません。このことは、このデバイスエミュレータで WEC 7 を動かそうとする場合に、問題となるように思われます。

しかし、心配はいりません。VS 2008 の Smart Device Development 機能に付属する、最新版(3.0)のデバイスエミュレータ(※上述したページから、単体でダウンロードできます)では、ARMv5 の命令セットにも対応しています。デバイスエミュレータのコマンドラインオプションのリファレンスを見ると、/cpucore オプションで ARMv5 を指定することにより、ARMv5 命令セットが有効になると説明されています(デフォルトは、ARMv4):

 デバイス エミュレータのコマンド ライン リファレンス
 http://msdn.microsoft.com/ja-jp/library/aa188169(v=VS.90).aspx

ちなみに、VS 2005 に付属していた版(1.0)、つまり、WinCE 6.0 用としても使われるデバイスエミュレータは、ソースコードを入手可能です:

 Shared Source Microsoft Device Emulator 1.0 Release
 http://www.microsoft.com/en-us/download/details.aspx?id=10865

デバイスエミュレータ 1.0 のソースコードは、上のページに書かれているように、Shared Source ライセンスで提供されていますので、興味がある方は、ご覧になってみて下さい。QEMU など、他のエミュレータと同様なところはあると思いますので、両者を比較しながら読んでみるのも、面白いでしょう。

■WEC 7 での Virtual PC 対応
WEC 7 では、WinCE 6.0 にあった ARM ベースのデバイスエミュレータの BSP は付属せず、代わりに、Virtual PC 用の BSP が付属しています。この BSP に対応した、Virtual PC の仮想マシンイメージ(cevm.vmc)も付属しており、BSP を使って OS イメージを作成すれば、すぐに動かせるようになっています。この仮想マシンイメージを有効にすると、Windows 7 で Windows XP Mode をお使いの場合、Windows Virtual PC の仮想マシンとして、Windows XP Mode に加えて cevm が表示されるようになる筈です。cevm には、WEC 7 のブートローダが組み込まれていますので、起動してブートローダのメニュー画面を表示させ、ネットワーク設定を行えば、Platform Builder を使って OS イメージをダウンロードできます。

WEC 7 での Virtual PC ベースのデバイスエミュレータを使う手順については、リファレンスの次のページをご覧下さい。

 Develop with Virtual CEPC (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/jj200433

お使いの PC に WEC 7 をインストール済みの方であれば、
 C:/Program Files/Windows Embedded Compact 7/Documentation/
ディレクトリの中に入っている
 Getting Started with Virtual CEPC.pdf
というドキュメントも、参考になるでしょう。

Virtual PC ベースのデバイスエミュレータを、WinCE 6.0 のデバイスエミュレータと比べると、上述したように、エミュレータから利用できるホスト PC の周辺機器機能は、Virtual PC ベース(前者)の方が豊富です。また、原理上、前者の方が高速でもあります。一見すると、Virtual PC ベースのエミュレータの方が良いことずくめで、WinCE 6.0 のデバイスエミュレータ(ARM ベースのエミュレータ)を使う理由は、ありません。

しかし、ARM ベースのエミュレータの方が便利な場合も、あります。たとえば、次の場合です。

  • 実機のプロセッサが ARM の場合。
  • 実機にハードウェアボタンを搭載予定であり、そのモック動作を、エミュレータで行いたい場合。

ハードウェアボタンのシミュレートについては、上述したように、設定ファイル(スキンファイル)へ記述することにより任意に設定できます。ARM ベースのデバイスエミュレータの設定ファイルの記述仕様は、リファレンスの次のページで説明されています:

 デバイス エミュレータのスキンの XML スキーマ リファレンス
 http://msdn.microsoft.com/ja-jp/library/aa188144(v=vs.90)

 デバイス エミュレータ構成の XML スキーマ リファレンス
 http://msdn.microsoft.com/ja-jp/library/bb531167(v=vs.90)


2012-09-05 追記:
デバイスエミュレータのスキン設定ファイルにおいて、ボタン/キーパッドに割り当てるキーコードについて、上記のリファレンスでは、特に説明されていません。割り当て可能なキーコードの説明は、WinCE 5.0 のリファレンスにある、次のページをご覧下さい:

 http://msdn.microsoft.com/en-us/library/ms905130.aspx

実機のプロセッサが ARM の場合ですが、Virtual PC ベースのデバイスエミュレータ、つまり、x86 のエミュレータで開発すると、エミュレータでの動作中には起きなかった例外送出が、実機の ARM プロセッサでは発生する、という可能性があります。これが起きる典型的なケースは、TCP/IP 通信や USB 通信でデータ転送を行う際に、転送プロトコルのパケットのペイロードを、構造体に cast して、構造体のメンバにアクセスする場合です。この時、構造体のメンバが 2Byte 以上のサイズの整数値型([unsigned] short, [unsigned] long など)であり、かつ、構造体に cast したメモリ領域のアドレスのアラインメントが、それらの整数値型のサイズに対して揃っていなければ、ARM プロセッサの場合は例外が発生します。しかし、x86 の場合には、アラインメントが揃っていなくても、例外が発生しません。

従って、Virtual PC ベースのデバイスエミュレータでは起きなかった、アプリケーションや汎用デバイスドライバ(USB のクラスドライバなど)の不具合が、ARM プロセッサの実機へ移植すると発生する、という可能性があります。同様のことは、x86 PC でしかテストされていないソフトウェアを、ARM や MIPS などのプロセッサへ移植する場合にも問題となります。これら、ワードアラインメントに関わる不具合を、デバイスエミュレータでの開発時に検出することができるのは、WinCE 6.0 において、ARM ベースのエミュレータが導入された利点の一つだったと思います。

ちなみに、WEC 7 の ARM コンパイラでは、WinCE 6.0 までの ARM コンパイラに比べて Compiler Intrinsics が強化されているため、WinCE 6.0 では発生しなかった例外送出が、WEC 7 では発生する、というケースもあります。つまり、WinCE 6.0 では、バイト単位のアクセス実行にしかコンパイルされなかったソースコードが、ワード単位でのアクセス実行にコンパイルされる場合がある、ということです。同じソースコードから生成されるバイナリの実行効率が上がるようにコンパイラが改善された一方で、その「副作用」として、不用意に書かれたソースコードによって新たに例外送出が発生する場合もある、というわけですね。

ところで、WEC 7 のデバイスエミュレータが、WinCE 6.0 での ARM ベースから Virtual PC ベースに戻ったのは、使用できるホスト PC の周辺機器や実行速度の違いが、主な要因だと思われます。それに加えて、WEC 7 からは PCMCIA がサポートされなくなったことも、要因の一つではないかと思います。WinCE 6.0 のデバイスエミュレータは、NE2000 互換の PCMCIA カードをエミュレートしているのですが、WEC 7 では PCMCIA がサポートされないため、その PCMCIA カード(NE2000 互換のネットワークカード)を利用できないのです。

このことは、Device Emulator の BSP を WinCE 6.0 から WEC 7 へ移植する際に、問題となります。さて、いよいよ、今回のエントリの本題です。

■Device Emulator BSP を WEC 7 へ移植する
これまでの説明で、WinCE 6.0 と WEC 7 では、デバイスエミュレータが異なっており、一方は、ARM プロセッサベース(Samsung S3C2410 のリファレンスボード)、他方は、Virtual PC ベースだということを述べました。また、WinCE 6.0 には付属していた、ARM プロセッサベースのデバイスエミュレータの BSP が、WEC 7 には付属していないことも述べました。そのため、Visual Studio 2008 の Smart Device Development 機能に含まれる、ARM ベースのデバイスエミュレータ(3.0; ARMv5 対応)で WEC 7 を動かそうとしても、OS イメージを作成する手段がないということについても、お分かり頂けたのではないかと思います。

では、Visual Studio 2008 付属のデバイスエミュレータで WEC 7 を動かすには、どうすればよいのでしょうか?答えは簡単です。WinCE 6.0 に付属するデバイスエミュレータの BSP を、WEC 7 に移植して、移植した BSP を使って OS イメージを作成すればよいのです。必要な作業は、以下の通りです:

  1. WinCE 6.0 の %_WINCEROOT%/platform/DEVICEEMULATOR/ ディレクトリを、WEC 7 の %_WINCEROOT%/platform/ ディレクトリへコピーする。
  2. WinCE 6.0 の %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/PCCARD/ ディレクトリを、WEC 7 にコピーした DEVICEEMULATOR/ ディレクトリの中の SRC/DRIVERS/ ディレクトリへコピーする(※その際、DRIVERS/ の下に PCMCIA/ というディレクトリを作り、その中に PCCARD/ ディレクトリの内容をコピーするのが良いでしょう)。
  3. コピーした DEVICEEMULATOR/ ディレクトリと PCCARD/ ディレクトリの内容、つまり、WinCE 6.0 のデバイスエミュレータの BSP と PCMCIA スタックに対して、WEC 7 への移植(必要な改訂)を施す。
  4. WinCE 6.0 の %_WINCEROOT%/PUBLIC/COMMON/DDK/INC/ ディレクトリから、PCMCIA 関連のヘッダファイル(tuple.h, cardsv2.h, socksv2.h, cardserv.h)を、WEC 7 にコピーした DEVICEEMULATOR/ ディレクトリの中の SRC/INC/ へコピーする。
  5. WinCE 6.0 の %_WINCEROOT%/PUBLIC/COMMON/OAK/files/common.reg ファイルから、PCMCIA の NE2000 互換カードに関するレジストリ項目を、WEC 7 にコピーしたデバイスエミュレータの platform.reg へコピーする。

上で述べたように、デバイスエミュレータがエミュレートするネットワークインタフェースは、NE2000 互換の PCMCIA カードなのですが、WEC 7 では PCMCIA がサポートされず、PCMCIA スタックが付属していません。そのため、WinCE から PCMCIA スタックを移植する必要があります。PCMCIA スタックを移植するにあたっては、%_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/ ディレクトリへコピーせず、デバイスエミュレータ専用のコンポーネントとして、デバイスエミュレータの BSP ディレクトリへコピーするのが良いでしょう。そうすれば、WEC 7 のソースツリー全体を「汚さずに」済みます。なお、デバイスエミュレータの BSP では、DRIVERS/ の下に PCCARD/ というディレクトリがあります。そのため、WinCE 6.0 の PCMCIA スタックのディレクトリ(PCCARD/)を、そのまま DRIVERS/ の下にコピーするのではなく、DRIVERS/ の下に PCMCIA/ というディレクトリを作って、その下に、WinCE 6.0 の PCARD/ ディレクトリの内容をコピーするのが良いでしょう。

デバイスエミュレータの BSP の移植作業は、他の BSP を WinCE 6.0 から WEC 7 へ移植する場合と同様です。中間ディレクトリのパスが WEC 7 で変わったことに伴う、各コンポーネントの sources ファイルの修正や、WinCE 6.0 時点で古くなっていた型定義が WEC 7 では削られてしまったことへの対応などが必要です。たとえば、デバイスエミュレータのディスプレイドライバでは、GPERotate や GPESurfRotate という型を参照していますが、これらは、WEC 7 のヘッダファイルでは削られてしまっているため、WinCE 6.0 のヘッダファイルからドライバのソースファイルへコピーする必要があります。

また、PCMCIA スタックの移植では、%_WINCEROOT%/PUBLIC/COMMON/DDK/INC/ にある devload.h や cebuscfg.h において、PCMCIA 関連の定数の定義が削られてしまっていることへの対応(WinCE 6.0 の同じ名前のファイルから、PCMCIA 関連の定数の定義を PCMCIA スタックのソースファイルへコピーする)も必要です。

上記のように、それなりの移植の手間は必要ですが、WEC 7 のコアのソース(PRIVATE/ および PUBLIC/ ディレクトリ配下)に手を加えずに、BSP および BSP 固有のコンポーネントとして追加するだけで済みます。これは、Windows Embedded Compact が、明確にモジュール化されていることの証だと思います。

移植後の BSP の品質を確保するためには、より多くの作業が必要ですが、「とりあえず動かす」レベルであれば、それほど大変な作業では、ありません。今回は、デバイスエミュレータで WEC 7 が起動して、ホスト PC でのマウス操作でタッチパネル入力をシミュレートでき、さらに、デバイスエミュレータのネットワークアダプタを使えるようになるまでの作業で分かったことを書いています。

最後に、デバイスエミュレータのネットワークアダプタを使えるようにするための移植作業に関して、注意点を二つ書きます。移植作業の細かい説明は省きましたが、次の二点は、見落としがちな要点なので、自分でも移植してみようと思った方のために書いておきます。

  • PCMCIA の NE2000 互換カードに関するレジストリ項目を、WinCE 6.0 の common.reg からコピーする際、BusType を 0 に変更する。
  • PCMCIA ホストコントローラのドライバ(pcc_smdk2410.dll)のロード順序を、後にする。

一番目の注意点ですが、[HKEY_LOCAL_MACHINE\Comm\NE20001\Parms] キーの下にある BusType キー値のことです。WinCE 6.0 の common.reg では、8、つまり PCMCIA の bus type 値が設定されています。しかし、WEC 7 では、PCMCIA がサポートされないため、この値を指定すると、「未知/未定義の bus type」というエラーになってしまい、NE2000 ミニポートドライバの初期化に失敗します。このキー値は、NE2000 ミニポートドライバがロードされた際に、初期化動作の一つとして NdisMRegisterIoPortRange() を呼び出す際に参照されるのですが(※NdisMSetAttributes() を使って、NDIS_HANDLE に対して bus type を設定したのち、NdisMRegisterIoPortRange() を呼び出します)、その呼び出しがエラーとなってしまいます。

このエラーは、bus type を 0 (Internal) に変更することで回避できます。今回、デバイスエミュレータで WEC 7 を動かすために上記の移植を行った際、当初は、NE2000 ミニポートドライバを自前実装し、NdisMRegisterIoPortRange() を呼び出さない仕組みで実装し直す必要があるのではないかと考えました。しかし、分析してみたところ、bus type の設定変更だけで済むことが分かり、移植の手間を減らすことができました。

最後に、二番目の注意点です。オリジナルの実装では、[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\PCC_SMDK2410] キーの下にある Order キー値が 1 になっています。しかし、その設定では、デバイスエミュレータの PCMCIA ホストコントローラがネットワークカードを認識して、NE2000 ミニポートドライバをロードする際に、エラーになってしまうのです。その原因は、解明できていないのですが、pcc_smdk2410.dll がロードされる順序を遅らせることにより、エラーを回避できるようです。設定する値は、dword:10 などにしてみて下さい。

二番目の点については、今後、もし調査の時間がとれたら、さらに追ってみたいと思います。もし、正確な原因や、より根本的な対処策をご存じの方がいらしたら、ぜひ教えて下さい。

1 comment2012/07/04 koga

Lilas-am440-6/7 の Armadillo-420 対応版

Armadillo-400 シリーズ用の弊社の WEC 7/WinCE 6.0 BSP (Lilas-am440-6/7) の、Armadillo-420 に対応した最新版が、アットマークテクノ社のサイトで公開されました:

 「Armadillo-420 ・ Armadillo-WLAN(AWL13) がWindows Embedded Compact 7に対応
  ~Armadillo-400シリーズすべてで BSP「Lilas」が利用可能に~」
 http://www.atmark-techno.com/news/notices/201205_lilas

両者のバージョンは、WEC 7 用 (Lilas-am440-7) が 1.0.4 で、WinCE 6.0 用 (Lilas-am440-6) が 1.1.2 です。三機種ある Armadillo-400 シリーズの中で、今回対応を追加した Armadillo-420 は、メモリの搭載容量が他の二つの半分です(RAM: 64MB, NOR Flash: 16MB)。このため、カーネルのメモリマップも異なります。Armadillo-420 で動く OS イメージを作成するためには、OS Design で、BSP のカタログ項目の “A420 CPU Board” を選択して頂く必要があります。この点、ご留意下さい。

また、Armadillo-420 には LCD インタフェースがないため、ヘッドレス(ディスプレイ無し)で使うことになりますが、ヘッドレスのデバイスで WEC 7/WinCE 6.0 を動かす場合のヒントとして、しばらく前に書いた、以下のエントリを参考にして頂ければ幸いです:

 WinCE デバイスのリモート操作~その1(1/2)
 http://www.stprec.co.jp/ceblog/2011/12/29/

 WinCE デバイスのリモート操作~その2(2/2)
 http://www.stprec.co.jp/ceblog/2012/01/04/

 WinCE/WEC でシリアルコンソール
 http://www.stprec.co.jp/ceblog/2012/03/15/

Armadillo-420 対応の追加によって、より多くの皆さんに、Armadillo-400 シリーズで WEC 7/WinCE 6.0 を評価してみて頂けることを期待しています。
Let’s Enjoy it! :-)

Add comment2012/06/08 koga

第2回 はこだて IT 見本市で展示します

明日と明後日(6/1, 2)に函館駅前の WAKO ビル 3F 会場で開催される、「第2回 はこだて IT 見本市」に、EMS-JP グループ北海道支部加盟企業として、弊社も出展します。
 http://hakoika.jp/modules/bulletin/index.php?page=article&storyid=34

Armadillo-400 シリーズの全ての機種、つまり、Armadillo-440, 460, そして 420 で Windows Embedded Compact 7 が動作する様子を展示します。お近くの方は、ぜひ会場にいらして下さいませ。

Add comment2012/05/31 koga

第三回 D2C (Device2Cloud) コンテスト

東京エレクトロンデバイス社主催の、D2C (Device2Cloud) コンテストを、一昨年(昨年3月決勝)と昨年(今年3月決勝)に続き、今年も開催することになりました:
 http://www.d2c-con.com/

第三回の今回は、3月ではなく、12月初めに決勝大会を実施します。参加される学生さんにとっては、3月に決勝大会を実施するよりも、12月初めに実施する方が、より参加しやすいのではないかと考えたからです。興味のある方は、ふるってご参加下さい。面白いアイディアが出てくることを、楽しみにしています。なお、上のコンテストのページには、「参加者向けトレーニング」を7月下旬もしくは8月上旬に実施予定となっていますが(5/25現在)、参加申し込み締め切りに近い9月上旬に実施する予定で、現在調整中です。

弊社は、過去二回と同様、今回も、CPU ボードの指定機材である Armadillo-440 用の、Windows Embedded Compact 7 (WEC 7) の BSP を提供します。これまで WEC 7 に触れたことのない学生の皆さんに、WEC 7 を使って組込みアプリケーション開発を体験するきっかけを提供できれば、素敵だなと考えています。参加を考えていらっしゃる皆さん、参加者向けのトレーニング、そして、決勝大会でお会いしましょう!

Add comment2012/05/25 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 comments2012/05/14 koga

WEC/WinCE から共有ディレクトリ(ファイルサーバ)をアクセス

前回のエントリで、.NET CF アプリケーションから WNetAddConnection3() を呼び出す例を述べました。その例の説明で書いたように、WNetAddConnection3() は、Windows フィルサーバとの接続などを行う API です。

■Windows ファイルサーバに対するクライアント機能
Windows ファイルサーバ(CIFS サーバ)をアクセスする機能、つまり、Windows ファイルサーバに対するクライアント機能は、”Windows Networking API/Redirector” というコンポーネントになっており、SYSGEN 変数の SYSGEN_REDIR を設定することにより、OS イメージに組み込まれます。詳細については、リファレンスの次のページをご覧ください。

 Windows Networking API/Redirector Reference (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee495264.aspx

 Windows Networking API/Redirector (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-US/library/ee494246(v=winembedded.60).aspx

Platform Builder のカタログ項目ビュー(Catalog Items View)の階層で言うと、次の項目を選択すると、SMB/CIFS クライアント機能が OS Design に組み込まれます(※WEC 7 の場合):

 <OS Design 名>
 Core OS
  Windows Embedded Compact
   Communication Services and Networking
    Networking – General
★    Windows Networking API/Redirector (SMB/CIFS)

ここで、”Redirector” という Windows の用語に馴染みのない方は、たとえば、次の解説記事が参考になるのではないかと思います。

 「基礎から学ぶWindowsネットワーク 第20回」
 http://www.atmarkit.co.jp/fwin2k/network/baswinlan020/baswinlan020_03.html

 「基礎から学ぶWindowsネットワーク」
 http://www.atmarkit.co.jp/fwin2k/network/baswinlan002/baswinlan002_03.html

なお、CIFS クライアントだけでなく、CIFS サーバ機能も WEC/WinCE に付属しています。こちらは、カタログ項目ビューの階層の、次の場所にあります:

 <OS Design 名>
 Core OS
  Windows Embedded Compact
   Communication Services and Networking
    Servers
     File Server (SMB/CIFS)

CIFS サーバに関する記述は、なぜか WEC 7 のリファレンスに見当たりません。興味のある方は、WinCE 6.0 のリファレンスをご覧ください。

 File Server (Windows Embedded CE 6.0)
 http://msdn.microsoft.com/en-us/library/ee500573(v=winembedded.60).aspx

■SMB/CIFS クライアント機能を組み込む場合の注意点
さて、SMB/CIFS クライアント機能(Windows Networking API/Redirector)を組み込む場合、一つ注意しなければいけません。WEC/WinCE のリファレンスには、このコンポーネントが依存するのは、TCP/IP (SYSGEN_TCPIP) と Winsock (SYSGEN_WINSOCK)、および NDIS (SYSGEN_NDIS) とだけ書かれているのですが、それらのコンポーネントだけでは、SMB/CIFS サーバにアクセスすることは、できません。

CIFS サーバに接続する場合は、認証処理が必要ですが、認証処理に必要なコンポーネントは、SMB/CIFS クライアント機能を組み込んでも自動的に組み込まれないため、明示的に組み込む必要があるのです。認証処理に必要なコンポーネントは、WEC 7 ですと、カタログ項目ビューの階層の、次の場所にあります:

 <OS Design 名>
 Core OS
  Windows Embedded Compact
   Security
    Authentication Services (SSPI)
★    Kerberos
★    NTLM

通常は、NTLM だけを選択しても CIFS サーバに接続できるでしょう。お手元の環境で、試してみて下さい。これらのコンポーネントについては、リファレンスの次のページで説明されています(※WEC 7 の場合)。

 NTLM Security Support Provider (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee498104.aspx

 Kerberos Security Support Provider (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee498711.aspx

 Authentication Services (Windows Embedded Compact 7)
 http://msdn.microsoft.com/en-us/library/ee498877.aspx

Add comment2012/05/07 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 comment2012/05/02 koga

Next Posts Previous Posts


Categories

Links

Posts by Authors

Recent Posts

Meta