Top / プログラミングテクニック / 10.ポインタ自由自在

ポインタというか、API を使うコツみたいなものです。

ByRef = ByVal ... As Long

API にパラメータを渡すとき、値渡しのものと参照渡しのものがあります。
ウインドウの座標を取得する GetWindowRect は、API ビューワで、以下のように定義されています。

   Declare Function GetWindowRect _
        Lib "user32" Alias "GetWindowRect" _
        (ByVal hwnd As Long, lpRect As RECT) As Long

hwnd は値渡し、lpRect は参照渡しです。 変数の引渡しには、スタックが次のように使用されます。

hwndスタックに4バイトの領域が作成され、変数の内容がコピーされる
lpRectスタックにポインタを格納する領域が作成され、変数のポインタがセットされる

APIは、スタックから、これらを取り出して処理を行うわけです。
32ビットアプリのVBは、32ビットでアドレスを管理していますから、参照渡しは、32ビット、つまり Long 値を値渡しするのと等しい動きをしていることになります。

よって、上の宣言は

   Declare Function GetWindowRect _
        Lib "user32" Alias "GetWindowRect" _
        (ByVal hwnd As Long, ByVal lpRect As Long) As Long

と書いても、なんら問題なく、ポインタを求めて lpRect にセットすればよいのです。

では、どうやってポインタを知るのでしょうか?

09.オブジェクトブラウザ で、隠し機能がわかると書きましたが、その中にポインタを扱う関数があります。
VarPtr, ObjPtr, StrPtr の3つです。

VarPtr

変数を渡すと、そのポインタを返す関数です。 上で取り上げた GetWindowRect APIは、

Dim udtRect As RECT
Call GetWindowRect(Me.hWnd, VarPtr(udtRect))

のように使うことができるのです。

StrPtr

String 型は長さを格納する部分と、実際の文字列を格納する部分に分かれています。
StrPtr は、実際に文字列が格納された部分の先頭へのポインタを返します。

文字列は Unicode で書かれているので、この関数を使えば、05.バイト型配列で説明したバイト型のテクニックは必要ないのです。(^_^;)

ちょっと面白い実験です。
vbNullString と空文字("")は、If 文で比較すると同じものですが、StrPtr で見ると、明らかに違うものです。 イミディエイトウインドウで、以下の文を実行してみましょう。

Debug.Print StrPtr(vbNullString), StrPtr("")
(結果)
 0             72366692

ObjPtr

オブジェクト型変数には、オブジェクトがどこにあるかを示すポインタが格納されています。 ObjPtr は、そのポインタを返す関数です。

こんなプログラムを実行してみましょう。

   Dim obj As Object
   Set obj = Nothing
   Debug.Print ObjPtr(obj)
   Set obj = New Class1
   Debug.Print ObjPtr(obj)

(結果)

0 
68823864 

Nothing のときはゼロ、クラスを作成して格納すると、オブジェクトへのポインタが格納されます。

プログラムを書き換えて、以下のようにしてみてください。

   Dim obj1 As Object
   Dim obj2 As Object
   
   Set obj1 = New Class1
   Set obj2 = obj1
   Debug.Print ObjPtr(obj1), ObjPtr(obj2)

(結果)

68847008      68847008 

obj1 と obj2 という変数は同じオブジェクトを指していることがわかりますね。

裏技的ですが、ポインタを渡して、オブジェクトへの参照を返すような関数を作ってみます。

Private Declare Sub RtlMoveMemory _
               Lib "kernel32" _
               (hpvDest As Any, _
                hpvSource As Any, _
                ByVal cbCopy As Long)

Public Function ObjectFromPointer(ByVal lngObjPtr As Long) As Object
    Dim objDest As Object
    Dim ErasePointer As Long
    ' ポインタを Object 変数に書き込む
    RtlMoveMemory objDest, lngObjPtr, Len(lngObjPtr) 
    ' オブジェクトの参照を戻り値として返す
    Set ObjectFromPointer = objDest
    ' 参照カウンタに矛盾が出ないようにポインタをクリア
    ErasePointer = 0
    RtlMoveMemory objDest, ErasePointer, Len(ErasePointer)
End Function

実行してみましょう。

   Dim obj1 As Object
   Dim obj2 As Object
   
   Set obj1 = New Class1
   Set obj2 = ObjectFromPointer(ObjPtr(obj1))
   Debug.Print ObjPtr(obj1), ObjPtr(obj2)

(結果)

2326472       2326472 

Set obj2 = obj1 を実行したのと同じ結果が得られます。

ByVal ... As String

いろいろな変数をAPIに渡すことが出来ますが、String 型だけは参照渡しをしなければならないのに、値渡しをしています。

値渡しをしているAPIは、Ansi 版の API を呼んでいます。
たとえば GetWindowText は GetWindowTextA を呼びます。

  Declare Function GetWindowText _
         Lib "user32" Alias "GetWindowTextA" _
         (ByVal hwnd As Long, ByVal lpString As String, _
          ByVal cch As Long) As Long

ここから先は、想像でしかないのですが、おそらくVB は、ByVal ... As String で宣言された引数を、いったん ANSI に変換して API に渡し、API 呼び出しが終わった後で、Unicode に変換して、呼び元に戻しているのでしょう。

ということは!
API が値を返さない引数に対しても、これは行われているのではないでしょうか。
GetPrivateProfileString をAPIビューワの宣言のまま使っていると、すごく無駄な処理が行われていることになりますね。

実は Unicode 版の APIよりも ANSI 版のほうが、05.バイト型配列で説明したバイト型のテクニックを必要としているのではないかという気がしてきました。




トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   最終更新のRSS
Last-modified: 2009-10-25 (日) 23:55:56 (2800d)