日本語VB6で128~255のキャラを取得するには?

タグの編集
投稿者 やんやん  (その他) 投稿日時 2009/1/21 09:42:45
やんやんと申します。
唐突で申し訳ないのですが…
#説明するのが下手なのですが、がんばって見ます。

環境:
 OS:Windows XP pro SP3
 VB:VB6 SP5(今更VB6かとお思いかもしれませんが…)

VB6では、日本語環境で使うと、128~255の文字が取得できないようですが、
ちょっと事情があり、取得したいのです、それも英語圏としての文字で

何がしたいかというと、例えば、Chr(130)は日本語だと文字列にすると
Unicodeで(0,0)(2バイト)になりますが、
コンパネ→地域と言語のオプション→詳細設定→Unicode対応でないプログラムの言語
で、「英語(米国)」を選らび、英語圏として動作させると、Chr(130)の文字列は
Unicodeで(26,32)となります。
Chr(131)なら(146,1)です。

上記のように、例えば130の値をStringの文字列にしたときに、英語圏と同じように
(26,32)、131なら(146,1)としたいのです。

http://www.kanaya440.com/contents/script/vbs/others/lcid.html
でStrConvでロケールIDを変更すれば出来るのかと思いましたが、出来ませんでした。

http://homepage1.nifty.com/MADIA/vb/vb_bbs2/200706/200706_07060057.html
を見ると、英語圏ではEBCDICのようなので、コードページ変換テーブルをみて
WideCharToMultiByte等を使用してみても出来ませんでした。
(該当しそうなEBCDICのコードページ変換テーブルの値は試してみました)

http://homepage2.nifty.com/nonnon/SoftSample/SampleModADOS.html
も見ましたが、ちょっと事情があり試せませんでした。

ヒントやポインタでもいいので、「ここを見ればいい」とか「こうすればいい」
とか教えていただけないでしょうか?

以下に、試したソースを貼り付けます。

どなたかご教授頂きたく思います。

以上宜しくお願い致します。

・試したソース
---
Private 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
Private 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

Private Sub Command1_Click()

Dim aaa() As Byte
Dim bbb() As Byte
Dim ccc(1) As Byte
Dim i As Integer
Dim tmpstr As String
Dim tmpstr2 As String
'Dim fileno As Integer
'
'fileno = FreeFile

'Open "C:\tmp\chren.txt" For Append As #fileno
'Print #fileno, Chr(130)
'Print #fileno, Chr(131)
'Close #fileno

'Open "C:\tmp\chrjp.txt" For Append As #fileno
'Print #fileno, Hex(130)
'Print #fileno, Hex(131)
'Close #fileno

'Debug.Print StrConv(CStr(Hex(130)), vbUnicode)

ccc(0) = &H1A
ccc(1) = &H20

Debug.Print Hex(130)

tmpstr = CStr(ccc)
Debug.Print tmpstr

bbb = StrConv(Hex(130), vbFromUnicode, 1033)
tmpstr = StrConv(Hex(130), vbFromUnicode, 1033)

Debug.Print ":" & tmpstr

For i = 0 To UBound(bbb)
    Debug.Print bbb(i) & " ";
Next
Debug.Print vbCrLf
'aaa = tmpstr

tmpstr = CStr(130)

Debug.Print "tmpstr=" & tmpstr

'aaa = WCMB_Encode(20127, tmpstr)
'aaa = WCMB_Encode(500, tmpstr)
aaa = tmpstr

For i = 0 To UBound(aaa)
    Debug.Print aaa(i); " " & Hex(aaa(i)) & " ";
Next

Debug.Print vbCrLf

'tmpstr = Chr(131)
'
'aaa = tmpstr

'aaa = WCMB_Encode(20127, "131")

tmpstr = "131"

aaa = tmpstr

For i = 0 To UBound(aaa)
    Debug.Print aaa(i); " "; ' & Hex(aaa(i)) & " ";
Next

Debug.Print vbCrLf


' 26  1A  32  20
'
' 146  92  1  1

' 63  3F

' 49   0   51   0   49   0


End Sub

'---共通関数
' 関数名    : WCMB_Encode
' 返り値    : 出力文字データ
' 引き数    : cp    : 出力文字データのコードページ番号
'           : strUni: UNICODE文字データ
' 機能説明  : UNICODE文字データを指定のコードに変換する
' 備考      : WideCharToMultiByteによる文字コード変換
Private Function WCMB_Encode(ByVal cp As Long, ByRef strUni As String) As Byte()
    On Error GoTo ErrHandler

    Dim lngUniLen As Long
    Dim lngBufLen As Long
    Dim lngRtn As Long
    Dim bytOut() As Byte

    lngUniLen = Len(strUni)
    If lngUniLen = 0 Then Exit Function
    lngBufLen = lngUniLen * 5
    ReDim bytOut(lngBufLen - 1)
    lngRtn = WideCharToMultiByte _
        (cp, 0, StrPtr(strUni), lngUniLen, bytOut(0), lngBufLen, vbNullString, 0)
    If lngRtn Then
        ReDim Preserve bytOut(lngRtn - 1)
        WCMB_Encode = bytOut
    End If
    
    Exit Function
ErrHandler:
    Debug.Print "文字コード変換エラー:" & Err.Description
End Function
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/1/21 19:12:07
> ちょっと事情があり、取得したいのです、それも英語圏としての文字で
その事情をお聞かせ願えませんか?
画面表示が目的なのか、通信電文やファイル出力などに使うのが
目的なのかによって、行うべき対処方法も変わってきてしまいますので。

まず、それは「文字」で無ければならないのでしょうか。
電文系/ファイル出力系であるならば、そもそも String を使うべきでは無く、
Byte() など、生のバイナリで扱うべき領域であるかと思います。

どうしても String しか使えない事情があるのだとしたら、
ChrW 連結で構築するか、StrConv を使うなどせねばならないでしょう。


> StrConvでロケールIDを変更すれば出来るのかと思いましたが、出来ませんでした。
Byte() → String 変換の意味だとすれば、StrConv で良いと思いますよ。
この場合、vbUnicode を指定すれば、CStr の代用となります。

この手の処理を行う際、LocaleID を使用できない文字列関数/ステートメント
(例えば、CStr/Str/Chr/Mid/Left/Right/MsgBox 等)および ANSI 系 API や
標準コントロール(TextBox, Label 等) は一切使用不可能と思ってください。

バイト系関数(LeftB/MidB/RightB 等)や Wide 系 API、それと
Unicode 対応な ActiveX コントロールなどが限定的に使用可能です。

また OS の違い、特に 9x系列 と NT系列では異なる結果になりえる事も覚悟して置いてください。


> 英語圏ではEBCDICのようなので
VB6 が使うのは、「既定の ANSI コードページ」です。

日本語(LCID=1041)
 既定のANSIコードページ = 932
 既定のEBCDICコードページ = 20290

米語(LCID=1033)
 既定のANSIコードページ = 1252
 既定のEBCDICコードページ = 37


> 上記のように、例えば130の値をStringの文字列にしたときに、英語圏と同じように
> (26,32)、131なら(146,1)としたいのです。
(26,32) というのは、Unicode の U+201A [Single Low-9 Quatation Mark]、
(146,1) というのは、Unicode の U+0192 [Latin Small Letter F With Hook] ですね。

> 以下に、試したソースを貼り付けます。
当方環境では、StrConv で変換できています。
Dim b1() As Byte, b2() As Byte
ReDim b1(0), b2(0)
b1(0) = 130: b2(0) = 130

Dim s1 As String, s2 As String
s1 = StrConv(b1, vbUnicode, 1041)
s2 = StrConv(b1, vbUnicode, 1033)

Debug.Print Hex(AscW(s1))    '「0」     U+0000 'NULL'  
Debug.Print Hex(AscW(s2))    '「201A」  U+201A 'Single Low-9 Quatation Mark' 
ファイル出力が目的なら、Byte() を Put# する事で対応してください。
投稿者 やんやん  (その他) 投稿日時 2009/1/22 11:12:22
魔界の仮面弁士様、フォロー頂きありがとうございます!!!

>その事情をお聞かせ願えませんか?
>画面表示が目的なのか、通信電文やファイル出力などに使うのが
>目的なのかによって、行うべき対処方法も変わってきてしまいますので。

そうなんですか、用途によって対処法が変わるのですね…
実は、USBで接続したある機器を操作するために必要な処理で使うのです。
「データ頂戴」と機器に送信し、送信した内容が正しければ機器から正しいデータが返ってくるというものです。
機器はアメリカ製でDLLのドライバの製作者もアメリカ人のようです。

で、130とか131とかいうのは、その機器のIDでして、「データ頂戴」というときに、Chr(130)や他の制御文字等を一緒に送信して、送信内容が正しければ正しいデータが機器から返ってきます。
「文字」と書いてしまったのでまずかったと思いますが、要は英語版のChr(130)と同じ値のUnicodeの文字列の2バイトの値2バイト分ともが欲しかったのです。
#日本語版だと0になってしまって、送っても正しいデータが返ってこないので

結論から言うと、変換は出来ましたが、正しいデータは取得できませんでした。

ある方から、「そういう場合はnバイト丸ごと送るのではなく、1バイトずつ送ることもあるので、"1" "3" "0"と1バイトずつ送るように文字列を作ってみれば?といわれたので、バイト配列で「1」等数字そのものやAsc("1")等Asc()で変換して送ったりしてみましたが、駄目でした。

ご提示頂いたコードで、
>ReDim b1(0), b2(0)
というのがキモだったのですね、気づきませんでした…
#先のコードには書いてませんでしたが(消してしまってましたが)、ReDim a(1)と2バイト分取っていたので、StrConvで正しく変換できなかったようです…

---
Dim a() as Byte
Dim s as String
ReDim a(0)
a(0) = 130
s = StrConv(a,vbUnicode,1033)
---
としたら、文字列sに(26,32)と入りました!
ありがとうございます!

しかし、英語版を見ても日本語版を見ても、送信しているデータをUnicodeの2バイトの文字を1バイトずつByteにして見てみました。
同じ値が入っているにもかかわらず、日本語版ではデータが取得できませんでした…

>この手の処理を行う際、LocaleID を使用できない文字列関数/ステートメント
>(例えば、CStr/Str/Chr/Mid/Left/Right/MsgBox 等)および ANSI 系 API や
>標準コントロール(TextBox, Label 等) は一切使用不可能と思ってください。

>バイト系関数(LeftB/MidB/RightB 等)や Wide 系 API、それと
>Unicode 対応な ActiveX コントロールなどが限定的に使用可能です。

ご教授ありがとうございます。
ということは、もし機器のIDをドライバが取得するのに、Chr(130)を含む文字列で送られてきたものを、Asc(<Chr(130)のある1文字>)とかして取得してたら、日本語版ではどう転んでも無理ということですね…
英語版ではAsc(Chr(130))は130で返ってくるのですが、日本語版では63が返ってきます。
130でも131でも63です。
#128以上が駄目なので、当たり前といえば当たり前なのですが…
機器のIDのドライバ内での取得の仕方をちょっと確認を取ってみます。
#Asc()でIDを取ってる可能性は否定できませんので。

>また OS の違い、特に 9x系列 と NT系列では異なる結果になりえる事も覚悟して置いてください。

承知致しました。
W2K or Laterのようなので、多分大丈夫だと思います。

>VB6 が使うのは、「既定の ANSI コードページ」です。

>日本語(LCID=1041)
> 既定のANSIコードページ = 932
> 既定のEBCDICコードページ = 20290

>米語(LCID=1033)
> 既定のANSIコードページ = 1252
> 既定のEBCDICコードページ = 37

こちらもご教授ありがとうございます。
「VB6 英語 コードページ」とかでぐぐってみたのですが、キーワードが悪かったのか、上記の内容が検索できませんでした。

ともあれ、ソース的にやりたいことは出来ましたので、謹んで深く感謝致します。
ありがとうございました!!!
#まだまだ精進が足りません、もっと精進せねばと思いました…
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/1/22 12:41:13
> 機器はアメリカ製でDLLのドライバの製作者もアメリカ人のようです。
そのドライバの仕様が書かれていないので、推測でしか答えようがないのですが、たとえば
それが Declare して使う API であり、引数がいわゆる LPTSTR であるならば、VB 側では
ByVal String を使うのではなく、ByRef Byte を使うようにしてください。
(あるいは、ByVal Long でポインタを指定するのも有効です)

> としたら、文字列sに(26,32)と入りました!
入れるだけなら、Byte() から変換するのではなく、
 s = ChrW(&H201A)
あるいは、
 s = ChrW(32 * &H100 + 26)
で十分かと。


> 送信しているデータをUnicodeの2バイトの文字を1バイトずつByteにして見てみました。
それは具体的には、どのようにして Byte 変換されたのでしょうか?
投稿者 やんやん  (その他) 投稿日時 2009/1/23 10:22:55
魔界の仮面弁士様、またまたフォローありがとうございます!

結論を言いますと、早速色々試したところ、データが受信できるSJISの文字と出来ない文字に分かれました。

>そのドライバの仕様が書かれていないので、推測でしか答えようがないのですが、たとえば
>それが Declare して使う API であり、引数がいわゆる LPTSTR であるならば、VB 側では
>ByVal String を使うのではなく、ByRef Byte を使うようにしてください。

はい、Declareで宣言して使用するAPIです。
で、仰る通りByVal Stringになっていたので、ByRef a() as Byteとしてみましたが、型か違うと怒られてしまいました…

>入れるだけなら、Byte() から変換するのではなく、
> s = ChrW(&H201A)
>あるいは、
> s = ChrW(32 * &H100 + 26)
>で十分かと。

ご教授ありがとうございます。
こういう設定もあったのですね、奥深いですね…

>それは具体的には、どのようにして Byte 変換されたのでしょうか?

以下で試しました。
---
Dim a() as Byte
Dim i as Integer
Dim str as String

str = <何か文字列(今回の送信用データの内容を実際には入れました)>
a = str

For i = 0 to Ubound(a)
    Debug.Print a(i)
Next
---

上記で、イミディエイトウィンドウで値を表示させて見ました。


で、問題の機器なのですが…
IDをファームで設定出来るようで、変更して試してみました。
#IDを変更するとその機器の電源入れなおしが必要ですが…
IDの値は1~255(実際には制御文字等もあるので、32からでしょうね)が設定可能です。

そこで、機器のIDを色々変更してみてテストしてみました。

結果、日本語版でも、Asc(Chr(<数値>))の結果の値が<数値>と同値の場合は正しいデータが取得できました。

しかし、SJISコードにも文字等が設定されてない箇所(全部ではありませんが)では、Asc(Chr(<数値>))の結果は0となり、正しいデータは取得できません。

しかも、0じゃなく正しいデータが取得できる数値は、引数として渡す文字列にChr(<数値>)としてByte列でみてみると、<数値>の値が入っているのです。

しかし、0となる<数値>に関しては、ここで質問したとおり、(26,32)を無理やり突っ込みましたが、正しい値は得られませんでしたし、Chr(<数値>)としてUnicodeで見ると、(26,32)のように、IDの値が推測できそうにない値になってしまいます。
例えば、Chr(130)の結果は63となります。
ここで、どういう法則で結果が<数値>になったり、0の場合はよくわからない値が文字列に設定されるのかがわかりません。
ぐぐりましたが、文字コード等との対応表となるようなものは見つかりませんでした…
#検索のキーワードが悪かったのかもしれませんが…
どういう法則でAsc(Chr(<数値>))で<数値>がでるのか、よくわからない数値が出るのかが不明です…
#ロケールIDの違いかとも思い、SetLocaleInfoとかでかわそうかと思ったのですが、それもうまくいきませんでした…
なぜ<数値>によってAsc(Chr(<数値>))の結果が違うのか原因不明なので、調査しないといけません…
#特殊文字等が入っているのであれば、仕方ないですが、SJISの文字コード表を見ると、制御文字等ではなくても0が返ってきます。
どういう法則でそのような変換がかかるのか、調査をしないといけません。

ともあれ、ありがとうございました、大変助かりました!!!
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/1/23 18:38:46
>> ByVal String を使うのではなく、ByRef Byte を使うようにしてください。
> (あるいは、ByVal Long でポインタを指定するのも有効です)
> 仰る通りByVal Stringになっていたので、ByRef a() as Byteとしてみましたが、型か違うと怒られてしまいました…

もう一度読みなおしてみてください。私は「ByRef a() As Byte」と書いた覚えはありませんよ。

例えば、
 Declare Sub Foo Lib "dllname" (ByVal a As String)
という API 宣言があったとしたら、
 Declare Function Foo Lib "dllname" (ByRef a As Byte) As Long
   (中略)
 ret = Foo(buf(0))   'バイト配列の先頭要素をByRefで渡す
のようにする、という事です。

String 型で渡した場合、既定のコードページで ANSI/Unicode 変換が行われてしまうので、
変換せずに渡したい場合には、このように記述する必要があります。


>>  s = ChrW(&H201A)
>>  s = ChrW(32 * &H100 + 26)
> こういう設定もあったのですね、奥深いですね…
あるいは、
 Dim s As String
 s = ChrB(26) & ChrB(32)
という手もあります。Chr/ChrW/ChrB の使い分けに注意。


> (26,32)を無理やり突っ込みましたが
突っ込むのは良いですが、それを ByVal String で渡したのでは意味がありません。

API に String で渡すと、そのデータを「Unicode テキストを表すバイナリ」とみなして
規定のコードページ(日本語では CP932/Shift_JIS)に変換した上で DLL に渡されます。
そして、DLL から受け取る場合には、それを逆変換して Unicode として String に
戻そうとする設計になっているためです。

そうした ANSI/Unicode 変換を行わせず、生データを直接やりとりさせるためには、
String 型は使用せず、先に書いたように、ByRef Byte(あるいは、ByVal Long)として、
バイナリデータの先頭アドレスを渡すようにする必要があります。
投稿者 やんやん  (その他) 投稿日時 2009/1/26 08:38:16
魔界の仮面弁士様
報告が遅れてしまい、すみません。
自分の勉強不足にもかかわらず、フォローいただいたことに深く感謝致します。
自分も魔界の仮面弁士様のように、困っている人がいたら、ちゃんとフォローしていこうと思いました。

結果から言うと、Byte配列で送信したら、データが無事受信できました。
「データ頂戴」という以外にも、同じAPIを使っている箇所があり、それらは問題なかったので、Aliasで別名つけて、ByRef Byteを引数として、バイト配列の先頭のアドレスを渡すようにしました。
これで無事データが受信できました。

ただ、
>API に String で渡すと、そのデータを「Unicode テキストを表すバイナリ」とみなして
>規定のコードページ(日本語では CP932/Shift_JIS)に変換した上で DLL に渡されます。
>そして、DLL から受け取る場合には、それを逆変換して Unicode として String に
>戻そうとする設計になっているためです。

上記のせいか、受信データから特定の値を何も変更せずに取り出そうとすると、きちんと値がとれなかったりしたので、以下のコードを追加しました。
---
    Dim ts1 As String

    ts1 = StrConv(<受信データの文字列>, vbFromUnicode, 1033)
    ts2 = StrConv(ts1, vbUnicode)
    
    <受信データの文字列> = ts2
---

アメリカ人が作ったAPIなので、自分の推測通り、ASCIIコードを扱っていたようです。
なので、SJISではAsc(Chr(<番号>))の結果が0になるところがありました。
ご教授頂いた通りバイト配列で数字を突っ込んだら値が取れました。
多分、1バイトで機器のIDを取得したくてASCIIコードを利用したのだと推測しています。

元々Unix+Cあがりなので、Windows+VBなどは経験値が低いので、わからないことが多いので、もっと勉強しなければいけないと思いました。

魔界の仮面弁士様、本当にありがとうございました。
本当に助かりました。

結果お知らせまで。