APIから文字列を得たい場合で、文字列先頭へのポインタしか受け取れないことがある。とくに、構造体でAPIとやり取りする場合、構造体が文字列先頭へのポインタ保持していると、文字列そのものを取り出すためには、工夫が必要となる。以下に、文字列先頭のアドレスが判明している場合に、文字列を取り出すコードを示す。
Private Declare Function SysAllocStringByteLen _
Lib "oleaut32" _
(ByVal psz As Long, _
ByVal length As Long) _
As String
Private Declare Function lstrlenA _
Lib "kernel32" _
(ByVal lpString As Long) _
As Long
Public Function GetANSIStringAt(ByVal sz As Long) As String
Dim lngLengthByte As Long
If sz = 0 Then
GetANSIStringAt = vbNullString
Else
lngLengthByte = lstrlenA(sz)
If lngLengthByte = 0 Then
GetANSIStringAt = ""
Else
GetANSIStringAt = SysAllocStringByteLen(sz, lngLengthByte)
End If
End If
End Function
SysAllocStringByteLenは、OLE AutomationのAPIであり、pszでポイントされている位置に存在するNull文字で終了するANSI文字列を、BSTRに、lengthバイトだけANSI文字列のままコピーし、さらに最後にNull文字を追加し、戻す。Visual Basicでは、APIからString型が戻ると自動的にANSIからUNICODEに変換してくれるので、Visual Basic側でANSIからUNICODEに変換する必要はない。
ただし、SysAllocStringByteLenでは、ANSI文字列のバイト数が分かっていなければならないので、lstrlenAを用いる。lstrlenAは、lpStringでポイントされている位置に存在するNull文字で終了するANSI文字列のバイト数を返す。このバイト数には、最後のNull文字は含まれない。
SDKによると、lstrlenの関数個別の説明では、ANSI文字列の場合バイト数を返すと説明されており、一方、文字列取扱関数の概説(String Manipulation in Windows)では、lstrlenは、ANSIでもUNICODEでも文字数を返すとなっており、矛盾が見られるが、動作を確認してみると、前者のlstrlenの関数個別の説明が正しい。
GetANSIStringAtは、szでポイントされている位置に存在するNull文字で終了するANSI文字列をString型(UNICODE)として取り出すVisual Basicの関数である。最初に、lstrlenAを呼び出しANSI文字列のバイト数を調べ、文字列の長さがゼロでなければ、SysAllocStringByteLenを呼び出す。SysAllocStringByteLenのAPI自身は、ANSIからUNICODEの変換を行わないが、結果がVisual Basicの渡される際に変換が行われる。
Private Declare Function lstrlenW _
Lib "kernel32" _
(ByVal lpString As Long) _
As Long
Private Declare Sub RtlMoveMemory _
Lib "kernel32" _
(ByVal dest As Long, _
ByVal src As Long, _
ByVal count As Long)
Public Function GetUNICODEStringAt(ByVal sz As Long) As String
Dim lngLengthChar As Long
If sz = 0 Then
GetUNICODEStringAt = vbNullString
Else
lngLengthChar = lstrlenW(sz)
If lngLengthChar = 0 Then
GetUNICODEStringAt = ""
Else
GetUNICODEStringAt = String$(lngLengthChar, vbNullChar)
RtlMoveMemory StrPtr(GetUNICODEStringAt), sz, lngLengthChar * 2
End If
End If
End Function
lstrlenWは、lpStringでポイントされている位置に存在するNull文字で終了するUNICODE文字列の文字数を返す。この文字数には、最後のNull文字は含まれない。UNICODEの場合、1文字が必ず2バイトである。
RtlMoveMemoryは、srcでポイントされるアドレスから、destでポイントされるアドレスへ、メモリの内容をコピーする。コピーするデータの長さがcountバイトである。コピー元の領域(srcからsrc+count-1)とコピー先の領域(destからdest+count-1)に重複があっても、正しくコピー元の内容がコピー先に移る。
GetUNICODEStringAtは、szでポイントされている位置に存在するNull文字で終了するUNICODE文字列をString型(UNICODE)として取り出すVisual Basicの関数である。最初に、lstrlenWを呼び出しUNICODE文字列のバイト数を調る。文字列の長さがゼロでなければ、GetUNICODEStringAtの戻り値として文字数分の領域を確保した上で、RtlMoveMemoryにより、szから確保した領域へ内容のコピーを行う。
Null文字で終了するUNICODE文字列をBSTRに格納するだけであれば、OLE AutomationのSysAllocString系のAPIが使えるが、このBSTRがVisual BasicにString型として戻る際に、余計なANSI-UNICODEの変換が行われるので、内容が破壊されてしまう。このため、SysAllocString系のAPIが使えないので、ここで紹介したような方法を採用せざるを得ない。
ここでは、StrPtrという、Visual Basicの隠し関数を使用しているが、これを嫌う場合は、次のようなコードとなる。
Private Declare Function lstrlenW
Lib "kernel32" _
(ByVal lpString As Long) _
As Long
Private Declare Sub RtlMoveMemory _
Lib "kernel32" _
(dest As Any, _
ByVal src As Long, _
ByVal count As Long)
Public Function GetUNICODEStringAt(ByVal sz As Long) As String
Dim lngLengthChar As Long
Dim bytBuffer() As Byte
If sz = 0 Then
GetUNICODEStringAt = vbNullString
Else
lngLengthChar = lstrlenW(sz)
If lngLengthChar = 0 Then
GetUNICODEStringAt = ""
Else
Redim bytBuffer(1 to lngLengthChar * 2)
RtlMoveMemory bytBuffer(1), sz, lngLengthChar * 2
GetUNICODEStringAt = bytBuffer
End If
End If
End Function