potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

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