皆さん、VBAで、変数のアドレスを返却する関数に、VarPtr関数がありますが、StrPtr関数については、ご存知でしょうか?
このStrPtr関数は、String型変数に格納された文字列データのアドレス(ポインタ)を取得するときに使用します。
サンプルコードを使って、StrPtrとVarPtr関数の違いについて説明していきたいと思います。
サンプルファイルのダウンロード
以下が、StrPtrとVarPtr関数を使った簡単なサンプルコードです。これは、str変数にabcという文字列データを設定し、そのstr変数に対する、2つの関数の結果を、出力する内容となっています。
'StrPtrとVarPtrの使用例
Private Sub CBtnStrAndVarPtr_Click()
Dim str As String
str = "abc"
Debug.Print "StrPtr(str)=" & StrPtr(str)
Debug.Print "VarPtr(str)=" & VarPtr(str)
End Sub
サンプルファイルをダウンロードし、【StrPtrとVarPtr】ボタンを押下すると、サンプルコードが実行されます。私の環境での、実行結果は以下のようになりました。これを見ると、StrPtrとVarPtr関数で、返却するポインタのアドレスが異なることが分かります。
StrPtr(str)=423548068
VarPtr(str)=2615800
実行結果が同じではありませんが、それでは、この2つの関数が、それぞれ何の値を返却しているか、ということを簡単に説明すると以下となります。
それでは、次に、String型の変数に設定されている、文字列データを、StrPtr関数を使って、確認してみましょう。以下は、String型の変数に、データabcを設定したときのサンプルです。この処理は、サンプルファイルの【StrPtrの使い方1】ボタンを押すことにより、実行できます。
Public Sub 文字列データの取り出し1()
Dim str As String
Dim buf(3) As Integer 'Null分含む 4個 8バイト
Dim i As Long
'テストデータをabcとします。
str = "abc"
// 途中省略
'▼▼▼ StrPtrが示すアドレスに格納されている値 ▼▼▼
'Integer(WIDE CHAR)が2バイト、文字列長3+1(Null)
'文字列バッファのサイズは、2×4=8
'文字列データの終端にNull(zero)が存在します。
'C言語では、文字列の終端をNullコードで判断します。
'8バイトコピーします。
DllRtlMoveMemory VarPtr(buf(0)), StrPtr(str), 8
Debug.Print "--- StrPtrの示すメモリアドレスに格納されている値 ---"
For i = LBound(buf) To UBound(buf)
Debug.Print "buf(" & i & ")=" & buf(i)
Next i
'▲▲▲ StrPtrが示すアドレスに格納されている値 ▲▲▲
End Sub
※VBAのString型では、文字列データはすべて、1文字2バイトの文字コード(Unicode)で管理されています。インターネットで主流となって使用されている文字コード(UTF-8)もUnicodeと呼びますが、ここでのUnicodeとは、異なるコード体系です。文字コードを統一化する目的で、設計されたコード体系のことをUnicodeと呼んでいますが、統一化を目的に、様々な分野で設計され、結果的にこれがまた複数のUnicodeを生み出しているように思います。
このプログラムの処理内容を簡単に説明すると、以下のようになります。
以下が、【StrPtrの使い方1】ボタンを押した時の実行結果ですが、これから、StrPtrが指し示すアドレスに、 97, 98, 99, 0 という値が、2バイトごとに保存されているということが分かります。また、終端には、Null(0)コードが存在するため、6バイトではなく、合計8バイトとなっています。
--- StrPtrの示すメモリアドレスに格納されている値 ---
buf(0)=97
buf(1)=98
buf(2)=99
buf(3)=0
この配列bufに格納されている値が、文字列データのabcと言うわけなのですが、本当に、正しいデータなのかどうかを確認するために、以下のように、もう少しコードを追加してみます。これは、VBAの標準関数のAsc関数を使って、文字列abcのそれぞれの文字コードを出力するようにしています。
Public Sub 文字列データの取り出し2()
// 途中省略
'▼▼▼ StrPtrが示すアドレスに格納されている値 ▼▼▼
// 途中省略
'▲▲▲ StrPtrが示すアドレスに格納されている値 ▲▲▲
'▼▼▼ "abc"のそれぞれの文字コード ▼▼▼
Debug.Print "--- abcのそれぞれの文字コード ---"
Debug.Print "a code=" & Asc("a")
Debug.Print "b code=" & Asc("b")
Debug.Print "c code=" & Asc("c")
'▲▲▲ "abc"のそれぞれの文字コード ▲▲▲
End Sub
この処理は、サンプルファイルの【StrPtrの使い方2】ボタンを押すことにより、実行できますが、実際に実行した結果が以下です。
--- StrPtrの示すメモリアドレスに格納されている値 ---
buf(0)=97
buf(1)=98
buf(2)=99
buf(3)=0
--- abcのそれぞれの文字コード ---
a code=97
b code=98
c code=99
この結果から、WinAPIでコピーした配列bufのそれぞれの値が、 a, b, c の文字コードと一致することが分かります。
最後に、StrPtr関数を使ったWinAPIの呼び出し例を紹介したいと思います。このサンプルは、WinAPIの文字列ポインタの引数に、StrPtr関数の戻り値を、直接指定しています。サンプルファイルの【StrPtrの使い方2】を押下すると、以下のプログラムを実行することが出来ます。プログラムの内容は、サンプルのエクセルブックのバックアップ・ファイルを、backフォルダに作成する処理となっています。
'ファイルのコピー
Public Declare Function DllCopyFile Lib "kernel32.dll" Alias "CopyFileW" ( _
ByVal SrcFile As LongPtr, _
ByVal DestFile As LongPtr, _
ByVal Exists As Long) As Long
Public Sub バックアップ()
Dim src_file As String
Dim dst_file As String
src_file = ThisWorkbook.FullName
dst_file = ThisWorkbook.Path & "\back\" & ThisWorkbook.Name
'バックアップフォルダの作成
If Len(Dir(ThisWorkbook.Path & "\back", vbDirectory)) = 0 Then
MkDir ThisWorkbook.Path & "\back"
End If
'ファイルコピー
DllCopyFile StrPtr(src_file), StrPtr(dst_file), False
End Sub
このようにWinAPIの呼び出し(コードの一番下の行)に、必要な引数コピー元ファイル名と、コピー先のファイル名のポインタに、StrPtr関数の戻り値を直接指定することができます。
VBAのString型がUnicodeで管理されているので、今回は宣言文で、エイリアスにCopyFileWを指定しましたが、文字コード体系がSJISのバッファを利用したいのであれば、エイリアスにCopyFileAを指定します。
※宣言文を説明すると、『kernel32.dllに実装されているエクスポート関数CopyFileWを、VBA側で、DllCopyFileとして使用します。』という内容となっています。このように、文字列バッファを引数に渡すようなAPIは、エクポート関数名に接尾辞として、AやWを付与している場合があります。また、エイリアスとはエクスポート関数を異なる名前で使用することを意味しています。
もし、文字コード体系としてSJISを採用するのであれば、宣言文は以下のように変更します。ちなみに、SJISは、半角が1バイト、全角が2バイトとなるマルチバイトのコード体系です。
'ファイルのコピー
Public Declare Function DllCopyFile Lib "kernel32.dll" Alias "CopyFileA" ( _
ByVal SrcFile As LongPtr, _
ByVal DestFile As LongPtr, _
ByVal Exists As Long) As Long
この場合、StrPtr関数は使えず、一旦、String型を以下のようにSJISのバッファに変換してから、VarPtr関数を使ってWinAPIを使用してください。
Dim sjis_bufs() As Byte
Dim sjis_bufs_size As Long
Dim str As String
str = "abc"
'Unicode→SJISのバッファに変換
sjis_bufs = StrConv(str, vbFromUnicode)
sjis_bufs_size = UBound(sjis_bufs) - LBound(sjis_bufs) + 1
'Null分確保し、終端に0設定
ReDim Preserve sjis_bufs(sjis_bufs_size)
'CopyFileAのWinAPIを利用する場合は、
'引数のポインタに、VarPtr(sjis_bufs(Lbound(sjis_bufs)))を渡します。
'使い終わったら
Erase sjis_bufs
※StrConv関数で変換したバッファの終端にはNullコードは含まれません。C言語側では、Null終端で、文字列の終わりを判定していますので、APIに文字列バッファを指定する時は、必ず、Null終端したデータを渡してください。
VBAで、WinAPIを使用し始めると、やはりC言語の知識が、少なからず不可欠となってくるように思います。今回の豆知識で、皆さんのVBAに対する理解が、深まってくれれば幸いです。
以下から、今回の豆知識で紹介した内容のエクセルファイルがダウンロードできます。
サンプルのダウンロード
ご意見・ご要望等ありましたら、画面最下部のメールアドレスまでご連絡ください。