親機側がDCOMで片付いたら、 今度は周辺装置との入出力を手当てしないといけません。 RS232Cで片付く奴は標準のシリアルドライバでいいですが、 それ以外のものだと、周辺信号⇔RS232Cの変換を介して接続したり、 あるいはPCIボードを購入・開発し、 CEドライバを書くことになるでしょう。 んが、PCIのボードはでかいです。またRS232Cへの変換装置を開発するぐらいなら、 EZ-USBあたりを使ってUSBに変換する方がおしゃれです。 しかもUSBで周辺をつなぐようにしておけば、 CEPC本体は普通のPocketPCで置き換えることも可能になるのです。 (もちろん市販品がDCOMをサポートしてビルドされているか等の問題はありますが…)

CEのデバイスドライバ

0000〜01ffカレントプロセス空間
0200〜03ff共用DLL領域
0400〜05ffプロセス2
0600〜07ffプロセス3
0800〜09ffプロセス4
4000〜41ffプロセス32
4200〜7fffファイルシステム操作
メモリマップファイル
8000〜9fff実アドレス0000〜1fff
(含カーネルコード)
a000〜bfff実アドレス0000〜1fff
(キャッシュ無効)
c000〜c1ff空き
c200〜c3ffプロセス97
カーネル(nk.exe)
c400〜dfffOEM使用領域
e000〜ffffカーネル使用領域
(ページテーブル等)
CEのデバイスドライバを書くのは簡単です。 Win2000のWDMドライバの1/10程度の難しさと思えばいいです。 デバイスドライバはdevice.exeプロセスに読み込まれる単なるdllで、 それ以上でも以下でもありません。通常のWin32 APIは何でも発行できますし、 ファイルをオープンしてログを書くことだってできます。 WDMみたいに、 現在の割り込みレベルによって呼び出すAPIに気を使う必要もありません。 ( ISR…Interrupt Service Routineは除きます。 これは割り込み要因を判断するためのルーチンで、 カーネルの一部であるためWin32 APIは呼び出せません。 CE4からはプラットフォームのリビルドなしに動的にISRの追加が可能となりました。)

メモリマップだって簡単です。 プロセスのメモリ空間は0x0000.0000から0x01ff.ffffの32MBの範囲を占めます。 プロセススイッチで実アドレス⇔仮想アドレスの対応が変わるのはこの部分だけで、 すべてのプロセスから0x0200.0000以上のメモリ空間は同一です。 0x0200.0000〜0x03ff.ffffは共用DLL領域(CE4以降)です。 0x0400.0000〜0x41ff.ffffの空間はプロセス毎に割り当てられたメモリ空間です。 プロセス数は最大31なので、31x32MBの空間を占めます。 0x4200.0000〜0x7fff.ffffはファイルシステム操作やメモリマップファイルのための ワーク領域です。 0x8000.0000〜0x9fff.ffffの512MBはx86の場合、システムの実アドレス0x0000.0000〜0x1fff.ffffが そのままマップされています。 0xa000.0000〜0xbfff.ffffも実アドレスのそのままのマップですが、 こちらの領域はMMUでキャッシュが無効になっています。 つまりI/Oレジスタアクセス用です。 0xc000.0000〜0xc1ff.ffffは空き、 0xc200.0000〜0xc3ff.ffffはカーネル(nk.exe)のプロセス空間ですが、 あくまでnk.exeをプロセスとして扱うための仮想プロセス空間であり、 カーネルコードは0x8000.0000〜の実アドレス空間で直接実行されます。

StrongARMやXScaleは仮想アドレスでキャッシュします。 この方が仮想アドレス→実アドレス変換の前にミスヒットかどうか判断できるので 性能は高いのですが、 プロセススイッチの度にすべてのキャッシュをフラッシュする必要があるので 性能が低下します。 そこでStrongARMはプロセス番号レジスタ(PID)を装備しました。 これは0x0000.0000〜0x01ff.ffffをPIDに応じ 例えば0x1200.0000〜0x13ff.ffffとしてキャッシュするもので、 これによりプロセススイッチが発生する度にキャッシュをクリアする必要がなくなり、 性能が高くなります。 ただし、1プロセス空間は最大32MB、 総プロセス数は最大でも127に制限されることになります。 上記CEのメモリマップはこのあたりの事情を大きく反映しています。
LinuxとかをXScaleに載せると、 プロセススイッチの度にキャッシュのフラッシュが行われるので遅いです。 が、CEでは割り込みが発生するとカーネル中のISRを経由して device.exeのIST(Interrupt Service Thread)にプロセススイッチが発生しますが、 Linuxではデバイスドライバはカーネル空間に存在し、 デバイスからの割り込みの度にプロセススイッチが発生するわけではないので、 CEよりもましになっています。 その代わりデバイスドライバはカーネル空間にあるのでそれなりに開発が難しいです。
このようになっているため、device.exe中のデバイスドライバから、 呼び出し元プロセス中にあるメモリ領域にアクセスするのが簡単になっています。 MapPtrToProcess(p, GetCallerProcess())で 呼び出し元のメモリ空間のポインタを device.exe中からアクセスできるポインタに変換できますが、 この関数の中身はアドレスの上位7ビットを当該プロセスのものに変更するという、 極めて簡単なものです。

非同期IOの非サポート

またWindowsCEでは非同期IOをサポートしていません。 ReadFileで呼び出されたデバイスドライバは単純にCOM_Read等の関数が呼び出されて、 その関数が終われば呼び出し元に復帰するだけです。 そのため、WDMで必要となる、リクエストキューの管理などは一切不要です。 単なる関数呼び出しと考えてまったく問題ありません。

性能向上のための手段をどのようにアプリケーションに提供するのかは デバイスドライバ開発者の手に委ねられています。 例えば非同期用のDeviceIoControlを提供し、 呼び出しはすぐにリターンして別スレッドで転送を行い、 終わったらMutexに通知するような仕組みを持ってもかまいません。 また、連続して入力を行うようなデバイスドライバでは、 入力バッファを管理し、 機器からの入力は別スレッドでこのバッファに対して行うことにより スループットを向上させることも考えられます。 あるいはデバイスドライバ側では一切このような処理を行わず、 アプリケーション側でスレッドを切って、 転送用スレッドが転送している間に別の処理を行わせることもできます。

WinCEの設計思想はあっさりしていて、 他のもので代替が利くものは実装しないようになってます。 そういう意味では昔からのしがらみがあるWin32APIよりも 洗練されたものになっており、 この非同期非サポートなんかはその最たるものだと思います。 その代わり、スレッドや同期オブジェクトといったものの扱いに精通している 必要があります。

→USBドライバ