では実際のUSBドライバの開発です。
CEに特化していない部分については本拠地や各種の本を参照してください。
Win2000用ドライバがある場合はUSB Snoopy等で通信内容を覗くと
開発の助けになると思います。
USBドライバの開発環境
usbドライバはPB4.1中のDllプロジェクトとして開発するのが普通ですが、
他のマシンでのusbドライバの使用も考えてeVC4で開発します。
基本的なUSBドライバ開発に必要なのは、usbdi.h、usbtypes.h、usb100.h の
3つのヘッダファイルと、usbd.dllとリンクするためのusbd.libだけです。
ヘッダファイルはSTANDARDSDK(\ProgramFiles\WindowsCETools\wce410\STANDARDSDK_410)にあるので普通にincludeできますが、
usbd.libはCEPCプロジェクトフォルダのWINCE410\CEPC\cesysgen\oak\lib\x86\debug
とかに作成されるのでライブラリパスを通してください。
PlatformBuilderがない場合でも、以下の手順でusbd.libを捏造できます。
- USBドライバ中で呼び出しているusbd.dllの関数をリストアップする。
- (その関数が実際に使用するマシンのusbd.dllからexportされているかどうか
dumpbin.exe等で確認する。)
- ダミーのusbd.dllプロジェクトを作成。例えばこんな感じで。
必要な関数は通常はこの程度でしょう。
#include <windows.h>
#include <usbdi.h>
extern "C" BOOL WINAPI
DllMain(HANDLE h, DWORD dw, LPVOID lp) { return TRUE; }
extern "C" BOOL __declspec(dllexport)
RegisterClientDriverID(LPCWSTR sz) { return 0; }
extern "C" BOOL __declspec(dllexport)
RegisterClientSettings(LPCWSTR sz, LPCWSTR u, LPCWSTR r, LPCUSB_DRIVER_SETTINGS s)
{ return 0; }
extern "C" HKEY __declspec(dllexport)
OpenClientRegistryKey(LPCWSTR sz) { return 0;};
- コンパイル。(CEPCへのダウンロードはしないように。)
できたusbd.libを、実際のUSBドライバのリンク時に使う。
ドライバのデバッグは基本的にPlatformBuilderで行いますが、
eVC4ではJITが有効になっていれば実行中のdevice.exeにアタッチできるので
PBなしでもデバッグが可能です。
なお、eVC3.0しかない場合でも、eVC4.0のSTANDARDSDK_410の上記ヘッダファイルと
捏造usbd.libを利用すると、CE3.0でも動作するUSBドライバの開発が可能です。
この場合デバッグは大変になりますが、
デバイスドライバ中からでもログをファイル等に書き出しできるので、
困難というわけでもないです。
市販CEマシン向けのUSBドライバの開発では、
PBとeVCを利用してCEPC上で開発したものを移植するか、
このようにeVCだけで直接開発することになります。
USBドライバのロード順序
- USB機器が刺されると、そのベンダ番号、クラス番号、インターフェース番号を元に
レジストリHKLM\Drivers\USB\LoadClientsが検索され、
そこに指定されたdllをdevice.exeにロードし、USBDeviceAttachが呼ばれます。
- USBDeviceAttachがFALSEを返した場合、上記レジストリがさらに検索され、
より汎用的なドライバのUSBDeviceAttachが呼び出されます。
- 試したドライバすべてのUSBDeviceAttachがFALSEだった場合は、
ユーザに新たなdllの名前を問い合わせ、そのdllのUSBInstallDriverを呼び出し、
1.から再実行します。
- USBDeviceAttachがTRUEを返した場合、OSはそれ以上何もしません。
まずUSBInstallDriverですが、これは最初にユーザがdll名を入力することにより
呼び出されます。この関数では上記のHKLM\Drivers\USB\LoadClients、
およびドライバ固有の情報を保持するためのレジストリである
HKLM\Drivers\USB\ClientDriversへ自分自身を登録します。
これはusbd.dllのヘルパー関数RegisterClientSettingsやRegisterClientDriverID、
OpenClientRegistryKey等により行うこともできます。
システムにあらかじめ組み込むドライバの場合は、
そういうレジストリをproject.reg等で登録しておいても構いませんし、
別の登録用アプリケーションから登録することもできます。
その場合はUSBInstallDriverは何もしなくてもいいでしょう。
なお、USBInstallDriverが呼ばれた時点では、
どんな機器が刺された結果として呼ばれたのかを知ることはできません。
次にUSBDeviceAttachでは、自分の対象機器かどうかを確認して、
違っていたらFALSEで単に戻ります。
対象機器の場合はTRUEで戻りますが、OSはそれ以上何もしないので、
TRUEで戻る前に必要な作業をすべて行う必要があります。
ActivateDeviceによるデバイスの登録、及び、
機器が抜かれた場合にDeactivateDeviceを呼び出すための
コールバックルーチンの登録、の2つの作業です。
デバイスドライバのアクティブ化
USBに限らず、アプリケーションから
CreateFile(L"MYU1:",...)という形で呼び出されるデバイスドライバは、
すべてActivateDeviceを呼び出してそのドライバをアクティブ状態として
登録する必要があります。
- レジストリに
HKLM\Drivers\USB\ClientDrivers\MyUSBDriver\Dll="myusb.dll"、Prefix="MYU" として登録します。
これはUSBデバイスドライバでは通常USBInstallDriver関数中で行ないます。
この状態ではまだアクティブにはなっていません。
- ActivateDevice(L"Drivers\\USB\\ClientDrivers\\MyUSBDriver", 数値)を
実行します。
すると、HKLM\Drivers\Active\23\...といったように登録され、
ActivateDeviceで引数として渡した数値は
HKLM\Drivers\Active\23\ClientInfoというキーとして登録されます。
- 次に、OSは当該dllをdevice.exeにロードし、
その関数MYU_Initを引数"Drivers\Active\23"と共に呼び出します。
ここで関数名は上記レジストリ中のPrefixに、_Initを付けたものです。
これに限らず、すべての関数名はMYU_OpenやMYU_Readのような
名前付けがされます。ヘルプで検索するときはXXX_Readのように指定してください。
- MYU_Init中で先ほどのClientInfoレジストリを読み取ることにより、
ActivateDeviceで渡された数値を読み取ります。
USBDeviceAttachと、呼び出されるデバイスドライバは
このClientInfoを経由してしか情報をやりとりできないことに注意してください。
両方同じmyusb.dllですのでグローバル変数でも情報を渡せなくもないですが、
ClientInfo経由で情報を渡すようにすれば
同種機器が複数接続された場合にも問題ありません。
つまり、USBDeviceAttachに渡されたUSBに関する情報は、
LocalAllocした専用の構造体に代入して、
その構造体の先頭アドレスをActivateDeviceに渡します。
MYU_Initではそれをレジストリから読み出して、構造体にCastします。
なお、両方ともdevice.exeに読み込まれたdllなので、
LocalAllocアドレスはそのまま渡せますが、
気になるならMapPtrToProcess(...,GetCurrentProcess())して渡してください。
- MYU_Initで初期化作業が終わったらデバイスコンテキストを持って戻ります。値0は初期化失敗です。
この戻り値は以後のMYU_Open等で引数になりますので、
USBデバイスドライバの場合、
MYU_Initで読み出したClientInfoをそのまま戻せばいいでしょう。
- この時点でデバイスがアクティブになり、一般のアプリケーションから
CreateFile(L"MYU1:", ...)といった呼び出しが可能になります。
USBDeviceAttach関数ではこのActivateDeviceの呼び出しの他、
デバイスがはずされた時のコールバックの登録(PCUSB_FUNCS)->lpRegisterNotificationRoutine(...)を行います。
コールバックでは後処理を行いDeactivateDeviceを呼び出した後、
専用の構造体をLocalFreeするようにします。
ストリームインタフェースドライバ
あとは実際のCreateFileやReadFileに対応する関数である
MYU_Init、MYU_Deinit、MYU_PowerUp、MYU_PowerDown、
MYU_Open、MYU_Close、MYU_Read、MYU_Write、MYU_Seek、MYU_IOControlを
書いて、それをエクスポートすればいいです。
MYU_DeinitはDeactivateDeviceの呼び出し、
つまりUSB機器がはずされた時に呼び出されます。
MYU_PowerXXは電源イベント関連で呼び出されます。
その他の関数はReadFileとかに一意に対応しているのでわかるでしょう。
USBデバイスドライバで実際に必要なのは
DeviceIoControlに対応するMYU_IOControlぐらいなものでしょうし、
CEは非同期呼び出しをサポートしていないので、
普通の関数呼び出しのつもりで書けば十分です。
但し呼び出し元空間のアドレスは適宜MapPtrToProcess(...,GetCallerProcess())で
変換してください。
USBデバイスドライバの例として
カメラ(stv0680)のデバイスドライバを置いてあります。
これはCE3.0のものですがCE4.1でも同じだと思います。
DeviceIOControlしか実装していませんが、通常のデバイスのGET/SET転送、
およびバルクGET転送に対応しているので、
ちょっとした変更である程度汎用に用いることができると思います。
この例ではアプリケーション(spyz)の側でスレッドを切って、
USBからの画像転送中にサムネイルの表示を行っています。
USBデバイスドライバからは
USBInstallDriver、USBUnInstallDriver、USBDeviceAttach
をエクスポートすることになります。
USBUnInstallDriverは事実上呼び出されませんので中身は空でいいです。
また、CreateFile(L"MYU1:",...)の形で呼び出されるデバイスドライバは、
さらにMYU_Init、MYU_Open等もエクスポートする必要があります。
なお、このUSBDeviceAttachを含むdllと、MYU_Init等を含むdllは
別のdllファイルでも構いませんし、
逆にActivateDeviceを複数回呼び出すことにより1つのdllファイルで
MYU_InitのほかにMAM_Initをエクスポートして、
MAM1:等の名前で利用することも可能です。
上の0680の例では__declspec(dllexport)を用いてこれらをエクスポートしていますが、
この宣言がusbdi.h中のものとバッティングしてコンパイル時にエラーとなる場合には、
defファイルを用いるように書き換えるかusbdi.hを変更してください。
このようなCreateFileで利用できるドライバは
ストリームインタフェースドライバと呼ばれ、
一番簡単なものです。
しかしUSB機器によってはファイルシステムとして見せたり、
あるいはNDISとしてプロトコルスタックに継ぐ必要があるでしょう。
そのような場合にはそれぞれの作法に法って開発する必要があります。
→VB.NET