リストHOME  リストOpen Source

第8回VBAプログラム豆知識

概要 ~ StrPtrとは? ~

 皆さん、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を生み出しているように思います。


 このプログラムの処理内容を簡単に説明すると、以下のようになります。


  1. str="abc" により、str変数に、データabcを格納します。
  2. WinAPIのRtlMoveMemory関数により、StrPtrが指し示すアドレスのデータを8バイト、配列bufにコピーします。
  3. For文で、コピーされた4個(2×4=8バイト)の値をそれぞれ、表示します。


 以下が、【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に対する理解が、深まってくれれば幸いです。



第7回  <  >  第9回



ダウンロード

 以下から、今回の豆知識で紹介した内容のエクセルファイルがダウンロードできます。

サンプルのダウンロード



連絡先

 ご意見・ご要望等ありましたら、画面最下部のメールアドレスまでご連絡ください。




エクセルバックアップ・ページのフッター
管理者のメールアドレス