Ruby 2.0.0とWin32APIで構造体内部の固定長文字列(char型)を扱う
本文
有名事項なのかもしれませんが、Win32APIを使って固定長文字列を含む構造体を扱おうとした時に文字化け(?)して手間取ったのでメモです。
結論としてはunpack("A*")
の返す文字列がASCII-8BITであることが原因だったので、
String::force_encoding('SJIS')
(文字コードがShift-JISのとき)
or
String::force_encoding('SJIS').encode('UTF-8')
(文字コードがUTF-8のとき)
で解決しました。
以下のサンプルコードはWin32 APIのmixerGetDevCapsA関数を呼び出して引数に使ったMIXERCAPSA構造体の固定長文字列を出力します。pack
およびunpack
のテンプレート文字列についてはリファレンスマニュアルを参考にして下さい。
require 'Win32API' MAXPNAMELEN = 32 sizeof_MIXERCAPS = 48 #構造体を格納するバッファーを用意する mixerCapsBuf = [0,0,0,'\0'*MAXPNAMELEN,0,0].pack("S!S!I!A#{MAXPNAMELEN}L!L!") #=> "\x00\x00\x00\x00\x00\x00\x00\x00\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\x00\x00\x00\x00\x00\x00\x00\x00" Win32API::new('winmm', 'mixerGetDevCapsA', 'IPI', 'I').call(0, mixerCapsBuf, sizeof_MIXERCAPS) #=> 0 #取得した構造体の中身を出力する buf = mixerCapsBuf.unpack("S!S!I!A#{MAXPNAMELEN}L!L!") #=> [1, 104, 1537, "\x83X\x83s\x81[\x83J\x81[ (Realtek High Defini", 0, 1] buf[3].force_encoding('SJIS') #=> "スピーカー (Realtek High Defini"
最後のbuf[3].force_encoding('SJIS')
は文字コードがSJISのコマンドプロンプトでは正常に表示されますが、UTF-8が設定されていると化けます。その場合は最終行は以下のコードに書き換えてください。
buf[3].force_encoding('SJIS').encode('UTF-8')
本当は文字コードに関わらず適用できるコードを探すべきなのですが――
おまけ:構造体の利用で少し扱いやすく
余談ですが、構造体を使うとunpackしたデータそのものよりは扱いやすくなるかもしれません。
mixerCapsStruct = Struct::new('MixerCaps', :mixerID, :productID, :driverVersion, :productName, :supportBits, :destinations) #=> Struct::MixerCaps buf = mixerCapsBuf.unpack("S!S!I!A#{MAXPNAMELEN}L!L!") #=> [1, 104, 1537, "\x83X\x83s\x81[\x83J\x81[ (Realtek High Defini", 0, 1] mixerCaps = mixerCapsStruct.new(buf[0], buf[1], buf[2], buf[3].force_encoding('SJIS'), buf[4], buf[5]) #=> #<struct Struct::MixerCaps # mixerID=1, # productID=104, # driverVersion=1537, # productName="スピーカー (Realtek High Defini", # supportBits=0, # destinations=1> mixerCaps.productName #or mixerCaps[:productName] #=> "スピーカー (Realtek High Defini"
C形式の定義
MIXERCAPSA構造体とmixerGetDevCapsA関数のC形式の定義を示しておきます。出典はVS2013に付属するヘッダーファイルです。
#define MAXPNAMELEN 32 /* max product name length (including NULL) */
typedef struct tagMIXERCAPSA { WORD wMid; /* manufacturer id */ WORD wPid; /* product id */ MMVERSION vDriverVersion; /* version of the driver */ CHAR szPname[MAXPNAMELEN]; /* product name */ DWORD fdwSupport; /* misc. support bits */ DWORD cDestinations; /* count of destinations */ } MIXERCAPSA, *PMIXERCAPSA, *LPMIXERCAPSA;
WINMMAPI MMRESULT WINAPI mixerGetDevCapsA( _In_ UINT_PTR uMxId, _Out_writes_bytes_(cbmxcaps) LPMIXERCAPSA pmxcaps, _In_ UINT cbmxcaps );