最終更新日 :
VB Tips

文字列とメモリ

今回は「文字列の長さをバイト単位で」得る方法を知り、またメモリの扱いかたにちょっと足を突っ込みたいと思います。
ある文字列を、バイト配列に格納し、StrConv()関数で元に戻して表示する、という結果がわかりきっていることをやります。

構文
' lstrlen()関数
Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As String) As Long
' メモリ操作関数
Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal ByteLen As Long)
' ANSI文字列をUnicodeに変換(参考)
Declare Function MultiByteToWideChar Lib "kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As String, ByVal cchMultiByte As Long, ByVal lpWideCharStr As String, ByVal cchWideChar As Long) As Long

VBの文字列管理
1.VBでの文字列
VBとANSI文字列の扱いかたの違い VB内部では右の図上段のように、文字列は全てUnicodeで処理されています。マス目は1マス2バイト(Nullも"00 00")です。そして、ダークグレーの場所には文字列の長さが書込まれます。これによって、文字列の途中にNull文字が含まれていても正常に認識されるのです。
対して、Unicodeが登場する以前使用され、現在でも使用され続けている1バイトAscii文字、2バイトマルチバイト文字(漢字など)(総称してANSI文字)は、マス目は1マス1バイトです。VBから文字列に関係したAPIを呼び出す場合、ANSI文字列を使用する必要があります。ですから、関数名は"〜A"、引数の型には"ByVal ... As String"とするのです。
また、文字列を使用するAPIの文字列に対応する変数の型として、Byte配列もあります。これは、右図下段のように文字列をバイト配列として処理するものです。この時、値渡し(ByVal)ではなく参照渡し(ByRef、もしくは"As Any")で呼び出す必要があります。なぜなら、配列の内容を直接読み込む/書込む必要があるからです。引数は、

"ByRef byteStr() As Byte"ではなく(プロシージャの引数ではないので)、
"ByRef byteStr As Byte"もしくは"byteStr As Any"とし、

Dim b(6) As Byte

b = StrConv("ばいと", vbFromUnicode) ' ANSI文字列に変換
SomeFunc b(0) ' Declare Sub SomeFunc Lib "a_dll.dll" (ByRef byteStr As Byte) で定義

というようにして使います。もっとも、
Dim r As Long, b(255) As Byte ' 256バイト分の配列を確保
Erase b() ' 配列の初期化(全て0になる)

r = GetWindowsDirectoryA(b(0))

MsgBox StrConv(b, vbUnicode)

という風に使う方が一般的です。(文字列への変換時にNullを見つける必要がないので。)

※ 図中の「LPSR」は「LPSTR」の間違いです。
2.文字列の文字数を数える
文字列の中に漢字があるか、文字列は何バイトか、VB2.0JではLen()関数を使用すればいいことでした。それはVBの文字列の処理がUnicodeではなくANSI文字列であったからでした。
しかし、VBが4.0になり、マイクロソフトはCDK(OCXの前身VBX、つまりActiveXの2世代前のコントロール開発キット)に使用されていた文字列型をUnicodeに拡張したOLESTR(BSTR)型を作り、OCXに組み込みました。 それに伴い、文字列処理系の関数も増えました(AscW、StrConv、LenBなど)。
そこで問題が生じました。文字列型の長さは、バイト数ではなく、文字数を示すようになったのです。すなわち、"A"も"あ"も同じ長さなのです。それは文字列のバイト数を返すLenBでも同じです。 LenB()は文字列のバイト数を返すのですが、内部がUnicodeである以上返ってくる長さは2の倍数("A"も"あ"も2バイト)です。ですから、結局は文字列に漢字が含まれているかなどはわからないのです。
3.本当の文字列の長さは?
では、一体本当の文字列のバイト数はいくつなのでしょう? 方法は2つ(かな?)あります。

1、APIのlstrlenA()を使う方法。
これは、ANSIAPIに文字列を渡す時文字列がANSI文字列になるのを利用する方法です。
Dim l As Long

l = lstrlen("abcあいう")


この関数でバイト数が返ってくるのです。

2、VB内部関数を使う方法。
こちらは、バイト配列の特性を生かす方法です。APIは嫌だ、という方に。
Dim l As Long, b() As Byte

b = StrConv("abcあいう", vbFromUnicode)
l = Len(b())


Len()関数は、C/C++でのsizeof()の代わりにもなります。余談ですが、宣言されたユーザー定義型変数(構造体)の長さを測ると、全体のサイズがわかり、一部のAPIで必要になる構造体の長さを得ることができます。 またこの場合、Ubound()を使用しても同じです。


メモリを操作する
1.文字列をバイト配列に移す
メモリを扱う第一ステップとして、文字列をバイト配列に移すということをやってみます。

Dim s As String, b() As Byte

s = "たとえばこんなの"

ReDim b(lstrlen(s))

MoveMemory b(0), ByVal s, Len(b())

これは、
b = StrConv(s, vbFromUnicode)


と同じ事です。
ここで、"ByVal"の重要性について少しばかり書きます。このMoveMemoryは、"As Any"で宣言された関数です。この型で宣言された場合、どんな型であろうとも関係無しに渡すことができる代わり、参照渡しとして処理されます。
ですから、文字列を渡す場合や、「ポインタ」を値として渡す場合、"ByVal"が必要になります。
2.バイト配列の中身は?
さて、メモリを移したバイト配列の中身ですが、もちろんUnicodeではなく、Ascii/SJISで構成された文字コードの配列です。
3.元に戻して表示する
バイト配列を元(VB文字列)に戻すのですが、ここでは単純にStrConv()関数を使用します。
s = StrConv(b, vbUnicode)

これで普通の文字列に戻ります。
もっと複雑な方法としては、OLE文字列操作関数を使用して文字列を確保し(その場合解放という作業が必要です。)、MultiByteToWideChar()という関数でUnicodeに変換するという方法もあります。
4.なぜこんなことを?
こんなことをするにも、単に

Dim Bytes() As Byte, StrVal As String, StrVal2 As String
Bytes = Strconv(StrVal, vbFromUnicode)
StrVal2 = Strconv(Cstr(Bytes), vbUnicode)

とやってしまえばいいだけなのですが...ね。

Copyright (C) 1999 Satoshi Tadaフィードバックはこちら
Home