VB6.0 + MySQL 4.1.22 での文字化け


バケ  2008-04-25 16:49:43  No: 100628  IP: 192.*.*.*

はじめまして。VB6.0 + MySQL 4.1.22の環境で、接続文字列を使いデータベース接続を行っています。キャラクタセットはUTF-8なので、

  DRIVER={MySQL ODBC 3.51 Driver}
  ;SERVER=nnn.nnn.nnn.nnn
  ;DATABASE=db_name
  ;UID=uid
  ;PWD=pwd
  ;STMT=SET CHARACTER SET UTF8

という接続文字列になっていますが、「東京」という文字列を取得すると「テァツヲツ湘・ツウツカ」と文字化けしてしまいます。

http://www.tenjinmail.net/library/index.jsp?id=1198294141826
を参照してみましたが結果は×でした。

接続文字列が正しくないのでしょうか。

編集 削除
やじゅ  2008-04-26 20:51:28  No: 100629  IP: 192.*.*.*

×だったリンク先に加えて
ODBCの設定画面の「Connect Option」タブに、
Initial StatementにSET NAMES SJISと入力
してみたらいかがでしょうか?
http://blog.goo.ne.jp/smiu2/e/2003d3885c7d54206c9551652e1a8327

編集 削除
バケ  2008-04-29 01:37:32  No: 100630  IP: 192.*.*.*

やじゅさん、ご回答ありがとうございます。

おっしゃっていたステートメント設定を試しましたがダメでしたので、発想を変え、取得した文字列を変換する方法を取ろうと考えました。

そこで、

  'UTF8  →  Unicode  変換関数
  '**************************************************
  Private Function UTF8_TO_UNICODE(strData As String) As String
  
    Dim bytData() As Byte
    Dim objStream As Object
  
    bytData = StrConv(strData, vbFromUnicode)
  
    Set objStream = CreateObject("ADODB.Stream")
  
    With objStream
      .Open
      .Type = adTypeBinary
      .Write bytData
      .Position = 0
      .Type = adTypeText
      .Charset = "UTF-8"
      UTF8_TO_UNICODE = .ReadText()
      .Close
    End With
  
    Set objStream = Nothing
  
  End Function
  '**************************************************

  'Unicode  →  UTF8  変換関数
  '**************************************************
  Private Function UNICODE_TO_UTF8(strData As String) As String
  
  Dim bytData() As Byte
  Dim objStream As ADODB.Stream
  
    bytData() = StrConv(strData, vbUnicode)
  
    Set objStream = CreateObject("ADODB.Stream")

    With objStream
      .Open
      .Type = adTypeBinary
      .Write bytData()
      .Position = 0
      .Type = adTypeText
      .Charset = "UTF-8"
      UNICODE_TO_UTF8 = .ReadText()
      .Close
    End With
  
    Set objStream = Nothing
  
  End Function
  '**************************************************

という関数を作ってみました。データベース接続して取得した文字列を

  strText = UTF8_TO_UNICODE("譚ア莠ャ")

のように変換・取得し、

  strText = UNICODE_TO_UTF8("東京")

のように変換・利用しようと思います。「譚ア莠ャ」=「東京」。

よくよく調べてみると、http://madia.world.coocan.jp/vb/vb_bbs2/200506/200506_05060016.htmlの質問と同様でした。

しかし、UNICODE_TO_UTF8関数を実行しても思った結果が得られません。コードのどの部分が誤りなのでしょうか?

編集 削除
魔界の仮面弁士  2008-04-29 02:51:48  No: 100631  IP: 192.*.*.*

> 発想を変え、
変えるべきでは無いと思いますけれども…。


> 取得した文字列を変換する方法を取ろうと考えました。
う〜ん、いろいろと混乱があるようですね。


> 'UTF8  →  Unicode  変換関数
> Private Function UTF8_TO_UNICODE(strData As String) As String

そもそも、UTF-8 も Unicode である、という点はさておき。
http://homepage1.nifty.com/nomenclator/unicode/ucs_utf.htm

String 型は、UTF-16 形式で格納される事を前提に設計されています。
UTF-8 データの受け渡しなら、Byte配列/ファイルパス/Stream などを使うべきかと。


そもそも、文字列そのものには、文字コード変換(エンコード/デコード)の概念はありません。
それが必要になるのは、文字列をテキストファイル化するなどの目的で、バイトデータの並びに
変換するとき(あるいは逆に、そうしたバイナリから文字列に復元する際)なのですから、
文字コード変換時の入出力に String を使うのは不自然です。
まぁ、String は任意のバイナリを格納できますから、使えないわけではありませんけれども。


で、本題。

>'UTF8  →  Unicode  変換関数
>     :
> bytData = StrConv(strData, vbFromUnicode)
この処理は、strData 内のバイナリを「UTF-16 → Shift_JIS 変換」するためのものです。
すなわち strData の内容は、UTF-16 エンコードされた文字列で無ければなりません。
(要するに、VB6で通常利用されている標準的な文字列データであるべき、という事です)

もし、strData の内容が UTF-8 バイナリなのであれば、この時点で NG となります。

そして提示されたコードを見ると、その Shift_JIS に変換されたバイナリを、
>      .Charset = "UTF-8"
>      UTF8_TO_UNICODE = .ReadText()
のようにして、「UTF-8 とみなして無理に読み込もうとしている」のですから、
データが破損するのも当然かと。



>  'Unicode  →  UTF8  変換関数
>     :
> bytData() = StrConv(strData, vbUnicode)
こちらは、strData 内のバイナリを、「Shift_JIS → UTF-16 変換」するものです。

という事で、
> UNICODE_TO_UTF8 = .ReadText()
これも無意味です。

そもそも、Stream の ReadText メソッドは、データを String 型の文字列として返します。
(すなわち、このメソッドを使う限り、出力結果のエンコードは、常に UTF-16 という事です)

編集 削除
魔界の仮面弁士  2008-04-29 03:28:46  No: 100632  IP: 192.*.*.*

# 読み返すと、我ながら、何だかよく分からない事を書いているなぁ…。

ざっくりとまとめ。

・VB6 の String 型は、符号化文字集合としては UCS-2 相当。
・VB6 の String 型は、文字符号化方式としては UTF-16 相当。
・VB6 の StrConv の vbFromUnicode 指定は、UTF-16 なデータを
  システム規定のコード(日本語環境では Shift_JIS) に変換する処理。
・ADODB.Stream のテキストモードは、データが UCS-2 の文字集合として管理される。
・ADODB.Stream の Charset 指定は、WriteText されたデータを内部保持用のバイナリに
  エンコードする際、および、逆にそれを ReadText で読み出す際などに利用される。
・ADODB.Stream の WriteText メソッドは、(UTF-16 バイナリな)String を受け取る。
・ADODB.Stream の ReadText メソッドは、(UTF-16 バイナリな)String を返す。

http://msdn.microsoft.com/library/ja/jpado260/htm/mdprocharset.asp

編集 削除
バケ  2008-04-29 12:28:04  No: 100633  IP: 192.*.*.*

質問の仕方が正しくありませんでした。申し訳ありません。

「東京」「譚ア莠ャ」という2つの文字列があり、「譚ア莠ャ」は「東京」のUTF-8でエンコードされた文字列です。

UTF8_TO_UNICODE関数を利用し、「譚ア莠ャ」を「東京」にすることは成功したのですが、UNICODE_TO_UTF8関数を利用し、「東京」を「譚ア莠ャ」することができません。

この場合どうしたらいいのでしょうか?

編集 削除
やじゅ  2008-04-29 13:02:54  No: 100634  IP: 192.*.*.*

ここらへんと同じことのような
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200804/08040045.txt

編集 削除
魔界の仮面弁士  2008-04-29 15:03:39  No: 100635  IP: 192.*.*.*

「東京」ぐらいなら、どうにか逆変換できますが、文字によっては不可逆となります。
無理に VB 側でコード変換するのではなく、環境設定側を見直すべきかと思いますよ。
(ここは MySQL の掲示板では無いので、設定方法に関する回答がつくかどうかは微妙ですが…)



>「東京」「譚ア莠ャ」という2つの文字列があり、「譚ア莠ャ」は「東京」のUTF-8でエンコードされた文字列です。

この部分で、微妙に認識が間違っているようです。


まず、「東京」という文字列が、《UTF-8 でエンコード》された場合、
『E6 9D B1 E4 BA AC』という 6 バイトのバイナリになります。

その 6 バイトのバイナリを、《Shift_JIS と仮定》してデコードすると、
  E69D = "譚"
  B1 = (半角カナの)"ア"
  E4BA = "莠"
  AC = (半角カナの)"ヤ"
という事で、(見た目上は)「譚ア莠ャ」という 4 文字の文字列になります。
これが、今見えているものの正体でしょう。

そしてそれらのバイナリが、(Byte 配列ではなく)VB の String 型に渡されているため、
実際に変数の内容は《UTF-16 にてエンコード》されて、
  "譚" => U+8B5A
  (半角カナの)"ア" => U+FF71
  "莠" => U+83A0
  (半角カナの)"ヤ" => U+FF6C
すなわち、『5A 8B 71 FF 6C FF』の 6 バイトで格納される事になります。
これが、実際のメモリ上にあるものの正体かと。


> UTF8_TO_UNICODE関数を利用し、「譚ア莠ャ」を「東京」にすることは成功したのですが
残念ながら、それは、たまたまうまくいっただけのように思えます。

現在の関数仕様は、
    入力:文字列(UTF-16バイナリ)「譚ア莠ャ」
    出力:文字列(UTF-16バイナリ)「東京」
になっていますが、本来は、
    入力:UTF-8バイナリ『E6 9D B1 E4 BA AC』
    出力:文字列(UTF-16バイナリ)「東京」
であるべきです。関数内のコーディング以前に、関数仕様そのものが不適切です。


一応、今の引数仕様のままでも、呼びだし側で
  Dim UTF8(5) As Byte
  UTF8(0) = &HE6
  UTF8(1) = &H9D
  UTF8(2) = &HB1
  UTF8(3) = &HE4
  UTF8(4) = &HBA
  UTF8(5) = &HAC
  Dim s As String
  s = UTF8
などとして、String 変数に『E6 9D B1 E4 BA AC』という UTF-8 バイナリを代入する事は
できますが、その場合の(MsgBox s 等での)見た目上は、「鷦 」であるかのように見えるでしょう。

2 文字目は Chr(&HF68A) 、3 文字目はハングルゆえ日本語環境では文字化けします。
#  E6 9D => (U+9DE6)"鷦"
#  B1 E4 => (U+E4B1)"  (私用領域の文字)
#  BA AC => (U+ACBA)ハングル音節文字(Hangul syllable Kiyeok-Yeo-Pieupsios)



ということで、変換関数に渡しているという【UTF-8 な東京】が
『E6 9D B1 E4 BA AC』という 6 バイトのバイナリであるのなら、
それを元の「東京」に戻すために、UTF8_TO_UNICODE の実装として
  (1) Stream をバイナリモードで開く。
  (2) そこに、『E6 9D B1 E4 BA AC』を Write する。
  (3) Position を先頭に戻し、UTF-8 テキストモードに切り替え。
  (4) ReadText で読み込んで「東京」を得る。
という手順を取ることができます。

また、元データが「譚ア莠ャ」という 4 文字の String であるならば、
  (1) それらを Byte 配列に無変換代入し、『E6 9D B1 E4 BA AC』を得る。
  (2) あとは、先ほどと同様の手順で「東京」を得る。
という流れになります。
# ただし、本来とは異なる Byte 配列が得られた場合は、その時点で
# すでにデータが破損しているため、変換処理は続行できません。


なお、逆変換である UNICODE_TO_UTF8 の場合は、渡すべきデータは
「東京」という 2 文字の String からの変換になるはずなので、
  (1) Stream を UTF-8 のテキストモードで開く。
  (2) "東京" の 2 文字を WriteText する。
  (3) Position を先頭に戻し、バイナリモードに切り替え。
  (4) Read メソッドで読み込んで、『E6 9D B1 E4 BA AC』のバイナリを得る。
という実装になりますね。

編集 削除
魔界の仮面弁士  2008-04-29 16:51:30  No: 100636  IP: 192.*.*.*

> (ここは MySQL の掲示板では無いので、設定方法に関する回答がつくかどうかは微妙ですが…)

MySQL を知らないので、解決案と言えるのかどうかは分かりませんが、
環境設定として、このあたりなどは如何でしょう。

http://puchiko.lowtech.ne.jp/?itemid=349&catid=27
http://www.millionwaves.com/200601220359.html
http://winofsql.jp/VA003334/install060720182639.htm

編集 削除
YK  2008-04-30 16:56:20  No: 100637  IP: 192.*.*.*

MySQLは良く分からないので  SQL Serverでテストしました

書込みは出来ているでしょうから
MySQLからのReadのみ
F1のフィールドタイプはバイナリ

    Dim strS    As String
    Dim bytS()  As Byte

    With RecordSet
        Do While Not .EOF
            bytS() = !F1      ’バイナリフィールドからバイト配列へ
            strS = Utf8Decode(bytS())  ' 変換
            .MoveNext
        Loop
        .Close
    End With


'(Bas)
Public Declare Function WideCharToMultiByte Lib "kernel32" _
                           (ByVal CodePage As Long, _
                            ByVal dwFlags As Long, _
                            ByVal lpWideCharStr As Long, _
                            ByVal cchWideChar As Long, _
                            ByRef lpMultiByteStr As Any, _
                            ByVal cchMultiByte As Long, _
                            ByVal lpDefaultChar As String, _
                            ByVal lpUsedDefaultChar As Long) As Long
     
Public Declare Function MultiByteToWideChar Lib "kernel32" _
                           (ByVal CodePage As Long, _
                            ByVal dwFlags As Long, _
                            ByRef lpMultiByteStr As Any, _
                            ByVal cchMultiByte As Long, _
                            ByVal lpWideCharStr As Long, _
                            ByVal cchWideChar As Long) As Long

Public Const CP_UTF8 = 65001

Public Function Utf8Decode(ByRef bytUtf8() As Byte) As String
    Dim lngUtf8Size As Long
    Dim strBuffer As String
    Dim lngBufferSize As Long
    Dim lngResult As Long
 
    On Error GoTo EndFunction
    lngUtf8Size = UBound(bytUtf8) + 1

    lngBufferSize = lngUtf8Size * 2
    strBuffer = String$(lngBufferSize, vbNullChar)  'デコード文字列を格納するバッファを確保
    lngResult = MultiByteToWideChar _
                   (CP_UTF8, _
                    0, _
                    bytUtf8(0), _
                    lngUtf8Size, _
                    StrPtr(strBuffer), _
                    lngBufferSize)                  'MultiByteToWideChar で UTF-8 → Unicode 変換

    If lngResult Then                               'MultiByteToWideChar が成功した場合
        Utf8Decode = Left$(strBuffer, lngResult)
    End If
    Exit Function
EndFunction:
    ' Error 処理
End Function

編集 削除