← 返回首页目录
# RAPIを用いたPocket PCファイルアクセス実装ガイド — VB.NETによるCeCreateFile・CeReadFile・CeWriteFileの実践
**作者: 吉祥法师**
## 核心概念
本稿は、Windowsベースの開発環境(Visual Studio.NET 2003/VB.NET、Framework 1.1)から、Microsoft ActiveSyncを介してPocket PCデバイス上のファイルにアクセスするための手法について、実例とトラブルシューティングを交えて詳細に解説するものである。具体的には、RAPI(Remote Application Programming Interface)ライブラリ「rapi.dll」に含まれる主要なAPI関数—CeRapiInitEx、CeCreateFile、CeReadFile、CeWriteFile、CeCloseHandle、CeRapiUninit—を用いて、デスクトップPCからPDA上のファイルを読み書きする手順を扱う。
RAPIは、Windows Mobile搭載デバイスとデスクトップPCとの間で、ファイル操作、レジストリ操作、プロセス制御などを可能にする重要なインターフェースである。本稿では特に、ファイルハンドルの取得に失敗する問題、データ型の不一致によるエラー、そしてファイル読み書き関数の宣言ミスといった、実際の開発現場で頻出する障害に焦点を当て、その解決策を具体的なコードとともに提示する。
## 論理構造
1. **問題の背景と目的**
- RAPIを用いたPocket PCファイルアクセスの必要性
- 開発環境の明示(WindowsXP、VB.NET、Framework 1.1、VS.NET 2003)
- 遭遇した主な問題:CeCloseHandleがFalseを返す、ファイルハンドルが不正な値になる
2. **最初の実装と直面した問題**
- CeCreateFileで取得したハンドルが異常な値(例:2391675667176488959)
- CeCloseHandleがFalseを返す原因分析
- パス指定の正しさに関する疑問("\My Documents\test.txt")
3. **データ型の問題と解決**
- VB.NETにおけるLong型(64ビット)とInteger型(32ビット)の違い
- コミュニティからのアドバイスと検証結果
- CeRapiInitExのRAPIINIT構造体内部のLong型未設定問題
4. **CeReadFile・CeWriteFileの実装とエラー**
- CeReadFile呼び出しで「パラメータが間違っています」(エラーコード87)
- CeGetLastErrorによるエラー詳細の取得
- オーバーラップ構造体の扱いに関する問題
5. **最終的な解決策**
- CeReadFile/CeWriteFileの正しい宣言
- lpOverlappedパラメータを構造体ByRefからIntPtrへ変更
- 正常動作の確認
## 主要な論点と論拠
### 1. RAPIの初期化とファイルオープンにおける型の壁
最初の実装では、以下のコードを用いてPocket PC上のファイルを開こうとした。
**初期のAPI宣言(問題あり):**
```vb.net
Public Declare Function CeRapiInitEx Lib "rapi.dll" ( _
ByRef pRapiInit As RAPIINIT) As Long
Public Declare Function CeCreateFile Lib "rapi.dll" ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long) As Long
Public Declare Function CeCloseHandle Lib "rapi.dll" ( _
ByVal hObject As Long) As Boolean
Public Declare Function CeRapiUninit Lib "rapi.dll" () As Long
```
**使用コード:**
```vb.net
Dim stRapiInit As New RAPIINIT
stRapiInit.cbSize = Len(stRapiInit)
stRapiInit.heRapiInit = 0
stRapiInit.hrRapiInit = 0
Result = CeRapiInitEx(stRapiInit)
Dim FilePath As String = "\My Documents\test.txt"
hFile = CeCreateFile(FilePath, GENERIC_READ Or GENERIC_WRITE, 1, 0, OPEN_EXISTING, 0, 0)
Result = CeCloseHandle(hFile)
Result = CeRapiUninit()
```
このコードを実行すると、`hFile`には「2391675667176488959」という明らかに異常な値が返された。この数値は32ビット符号なし整数の範囲(0〜4294967295)をはるかに超えており、64ビット値として解釈されていることがわかる。
**論拠:** VB.NET 2003(.NET Framework 1.1)では、`Long`型は64ビット(8バイト)の整数型として定義されている。一方、Win32 API(およびRAPI)のハンドルや多くのパラメータは32ビット(4バイト)である。`Declare`ステートメントで`As Long`と指定すると、VB.NETコンパイラは64ビットの戻り値を期待してしまい、実際には32ビットで返される値の上位32ビットにゴミデータが入る結果となる。
**第一の解決策:** コミュニティからのアドバイスに従い、`Long`を`Integer`に変更したところ、`hFile`は「-1」(INVALID_HANDLE_VALUE)を返すようになった。これは、ファイルオープン自体が失敗していることを示している。
### 2. RAPIINIT構造体内の未初期化メンバ
`CeRapiInitEx`に渡す`RAPIINIT`構造体の定義と初期化に問題があった。
**RAPIINIT構造体(正しい定義例):**
```vb.net
_
Public Structure RAPIINIT
Public cbSize As Integer
Public heRapiInit As Integer ' 32ビットハンドル
Public hrRapiInit As Integer ' 32ビットHRESULT
End Structure
```
元のコードでは、`stRapiInit.heRapiInit`と`stRapiInit.hrRapiInit`を`Long`型として宣言していた可能性が高い(コード断片からは明示されていないが、初期値0の代入から推測される)。これらのメンバが64ビットで宣言されていると、構造体のレイアウトが不適切になり、`CeRapiInitEx`が正しく初期化を行えず、結果として後続の`CeCreateFile`が失敗する原因となる。
**論拠:** Win32の`RAPIINIT`構造体は、`cbSize`(DWORD=32ビット)、`heRapiInit`(HANDLE=32ビット)、`hrRapiInit`(HRESULT=32ビット)の3つの32ビットメンバから構成される。VB.NETで正しくマーシャリングするには、すべてを`Integer`(32ビット)として宣言する必要がある。
### 3. ファイルパスの指定方法
Pocket PC上のファイルパスは、デスクトップPCとは異なる形式で指定する必要がある。正しいパスは`\My Documents\test.txt`であり、先頭にドライブレター(`C:`など)は不要である。また、バックスラッシュはエスケープが必要なため、VB.NETの文字列リテラルでは`"\My Documents\test.txt"`と記述する。
**論拠:** Windows CE(Pocket PCのOS)は、MS-DOSやWindows NT系とは異なるファイルシステム階層を持つ。ルートは`\`であり、一般的なドキュメントフォルダは`\My Documents`である。絶対パスは常にルートから始まる。
### 4. CeReadFile/CeWriteFileの宣言ミス
`CeReadFile`の呼び出しで「パラメータが間違っています」(エラーコード87)が発生した。
**問題のあった宣言:**
```vb.net
Public Declare Function CeReadFile Lib "rapi.dll" ( _
ByVal hFile As IntPtr, _
ByVal lpBuffer As Byte(), _
ByVal nNumberOfBytesToRead As Int32, _
ByRef lpNumberOfBytesRead As Int32, _
ByRef lpOverlapped As OVERLAPPED) As Boolean
```
**使用コード:**
```vb.net
Dim szbuf(17) As Byte
Dim ReadDataLen As Integer = 0
Dim Result As Boolean
Dim hFile As IntPtr
' ... hFileの取得 ...
Result = CeReadFile(hFile, szbuf, szbuf.Length, ReadDataLen, Nothing)
```
ここでの問題は、`lpOverlapped`パラメータの型にある。元の宣言では`ByRef lpOverlapped As OVERLAPPED`としていたが、実際の呼び出しでは`Nothing`(Null)を渡している。VB.NETでは、`ByRef`パラメータに`Nothing`を渡すことは可能だが、API関数側が`NULL`ポインタを期待している場合、正しくマーシャリングされないことがある。
**論拠:** Win32の`ReadFile`関数(およびRAPIの`CeReadFile`)の`lpOverlapped`パラメータは、オーバーラップI/Oを行わない場合は`NULL`を渡すように設計されている。`ByRef`で構造体を期待する宣言では、`Nothing`を渡しても内部で空の構造体へのポインタが生成される可能性があり、APIが意図しない動作をする。正しくは、`ByVal lpOverlapped As IntPtr`と宣言し、呼び出し時に`IntPtr.Zero`を渡すべきである。
### 5. 最終的な正しい実装
以上の検討を踏まえ、以下の修正を行った。
**修正後のCeReadFile/CeWriteFile宣言:**
```vb.net
Public Declare Function CeReadFile Lib "rapi.dll" ( _
ByVal hFile As IntPtr, _
ByVal lpBuffer As Byte(), _
ByVal nNumberOfBytesToRead As Int32, _
ByRef lpNumberOfBytesRead As Int32, _
ByVal lpOverlapped As IntPtr) As Boolean
Public Declare Function CeWriteFile Lib "rapi.dll" ( _
ByVal hFile As IntPtr, _
ByVal lpBuffer As Byte(), _
ByVal nNumberOfBytesToWrite As Int32, _
ByRef lpNumberOfBytesWritten As Int32, _
ByVal lpOverlapped As IntPtr) As Boolean
```
**使用コード:**
```vb.net
Dim szbuf(17) As Byte
Dim ReadDataLen As Integer = 0
Dim Result As Boolean
Dim hFile As IntPtr
' CeCreateFileでhFileを取得(成功した場合)
hFile = CeCreate(...)
' CeReadFileの呼び出し
Result = CeReadFile(hFile, szbuf, szbuf.Length, ReadDataLen, IntPtr.Zero)
```
この修正により、`CeReadFile`は正常に動作し、ファイルの読み取りが可能になった。同様に、`CeWriteFile`も`IntPtr.Zero`を渡すことで正常動作が確認された。
## 結論と教訓
本稿で扱った一連の問題と解決策から、以下の重要な教訓が得られる。
1. **データ型の一致は絶対条件:** VB.NETとWin32 APIの間でのデータ型の違い(特に32ビットと64ビットの整数型)を常に意識しなければならない。`Long`型の使用には細心の注意が必要であり、API宣言では原則として`Integer`(32ビット)を使用するのが安全である。
2. **構造体のレイアウトは正確に:** APIに渡す構造体は、各メンバのサイズと順序がC言語の定義と完全に一致していなければならない。`StructLayoutAttribute`を使用して明示的にレイアウトを指定することで、マーシャリングの問題を回避できる。
3. **NULLポインタの扱いに注意:** API関数に`NULL`を渡す必要がある場合、`ByRef`で構造体を宣言するのではなく、`ByVal As IntPtr`と宣言し、`IntPtr.Zero`を渡すべきである。これにより、ポインタのマーシャリングが正しく行われる。
4. **エラーコードの活用:** `CeGetLastError`(または`Marshal.GetLastWin32Error`)を使用してエラーの詳細を取得することで、問題の特定が容易になる。エラーコード87(パラメータが間違っています)や183(ファイルが既に存在します)などの情報は、デバッグの強力な手がかりとなる。
5. **コミュニティの活用:** 本稿の問題解決において、フォーラムでの他者からのアドバイス(`Long`から`Integer`への変更提案)が重要な突破口となった。開発においては、自身の知識だけに頼らず、コミュニティの知恵を積極的に活用することが推奨される。
RAPIを用いたPocket PCファイルアクセスは、適切な知識と注意深い実装によって確実に動作する。本稿で示した解決策と注意点を踏まえることで、同様の問題に直面した開発者は迅速に原因を特定し、修正を行うことができるだろう。