potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

Ruby 2 Fiddle/Win32APIでWin32 APIのUnicode版を呼び出す時はUTF-16LEエンコーディングが必要

Win32APIパッケージからFiddleパッケージへの乗り換えを初めてUnicode文字列(WCHAR, LPWSTR, LPCWSTR, OLECHAR, LPOLESTR, LPCOLESTR)を扱う関数、特にAnsi版が存在しないOLE関数周りで文字化けが起きて困っていました。

文字コードについて色々と調べた結果、UTF-16LEへのエンコードが必要だったことが原因だったようです。標準の文字コードは同じUnicodeでもUTF-8形式であり、またUTF-16とだけ指定するとダミーエンコーディングとなるので注意が必要です。

以下、FiddleからMessageBoxA/Wを呼び出して文字化けを確かめるサンプルコードです。

require 'fiddle/import'
require 'fiddle/types'

module WinAPI
    extend Fiddle::Importer
    dlload 'user32.dll'
    include Fiddle::BasicTypes
    include Fiddle::Win32Types
    
    typealias 'LPCWSTR', 'wchar_t*'

    extern "int MessageBoxA(HWND, LPCSTR, LPCSTR, DWORD)"
    extern "int MessageBoxW(HWND, LPCWSTR, LPCWSTR, DWORD)"
    
    MB_OK = 0
end

require 'pp'

s_sjis  = 'あいうえお'.encode!('Shift_JIS')
s_uni   = 'あいうえお'.encode!('UTF-16')
s_unibe = 'あいうえお'.encode!('UTF-16BE')
s_unile = 'あいうえお'.encode!('UTF-16LE')

#エンコーディングの確認
pp [s_sjis, s_uni, s_unibe, s_unile].map {|x| x.encoding}
#=>[#<Encoding:Shift_JIS>,
# #<Encoding:UTF-16 (dummy)>,
# #<Encoding:UTF-16BE>,
# #<Encoding:UTF-16LE>]

#ANSI版
WinAPI::MessageBoxA(0, s_sjis, 0, WinAPI::MB_OK)  #=> OK
WinAPI::MessageBoxA(0, s_uni, 0, WinAPI::MB_OK)   #=> 文字化け
WinAPI::MessageBoxA(0, s_unibe, 0, WinAPI::MB_OK) #=> 文字化け
WinAPI::MessageBoxA(0, s_unile, 0, WinAPI::MB_OK) #=> 文字化け

#Unicode版
WinAPI::MessageBoxW(0, s_sjis, 0, WinAPI::MB_OK)  #=> 文字化け
WinAPI::MessageBoxW(0, s_uni, 0, WinAPI::MB_OK)   #=> 文字化け
WinAPI::MessageBoxW(0, s_unibe, 0, WinAPI::MB_OK) #=> 文字化け
WinAPI::MessageBoxW(0, s_unile, 0, WinAPI::MB_OK) #=> OK

確認部分を省くと以下の様なコードとなります。

require 'fiddle/import'
require 'fiddle/types'

module WinAPI
    extend Fiddle::Importer
    dlload 'user32.dll'
    include Fiddle::BasicTypes
    include Fiddle::Win32Types
    
    typealias 'LPCWSTR', 'wchar_t*'

    extern "int MessageBoxA(HWND, LPCSTR, LPCSTR, DWORD)"
    extern "int MessageBoxW(HWND, LPCWSTR, LPCWSTR, DWORD)"
    
    MB_OK = 0
end

#ANSI版
WinAPI::MessageBoxA(0, 'あいうえお'.encode!('Shift_JIS'), 0, WinAPI::MB_OK)
#Unicode版
WinAPI::MessageBoxW(0, 'あいうえお'.encode!('UTF-16LE'), 0, WinAPI::MB_OK)

Win32APIを使用する場合も引数の型を'P'、encode/encode!('UTF-16LE')を使えばUnicode版関数を呼び出すことができます。

require 'Win32API'

MessageBoxW = Win32API::new('user32', 'MessageBoxW', 'PPPI', 'I')
MessageBoxW.call 0, 'あいうえお'.encode!('UTF-16LE'), 0, 0