前書き
Windowsには特殊フォルダとして知られる「既知のフォルダ」がいくつかあり
そのパスは環境によってはデフォルトとは別であることもあります。
例: Program Files
やユーザーディレクトリやデスクトップなど
これらを取得する方法としてSHGetKnownFolderPath
があります。
ソース
specialdir.rb
require 'fiddle/import'
require 'fiddle/types'
module Shell32
extend Fiddle::Importer
dlload 'shell32.dll', 'Ole32.dll'
include Fiddle::Win32Types
# HRESULT SHGetKnownFolderPath(
# REFKNOWNFOLDERID rfid,
# DWORD dwFlags,
# HANDLE hToken,
# PWSTR *ppszPath
# );
extern "DWORD SHGetKnownFolderPath(PDWORD id, DWORD flgs, HANDLE token, char** path)"
extern "void CoTaskMemFree(void* pv)"
module_function def wcscpy!(s)
buf = []
0.step(by: 2) do |i|
a,b = s[i], s[i+1]
c = a | b << 8
break if c == 0
buf.concat([a, b])
end
buf.pack("C*").force_encoding("UTF-16LE")
end
module_function def GetKnownFolderPath(id, flg = 0)
ppstr = Fiddle::Pointer.new(0).ref
ret = SHGetKnownFolderPath(id, flg, 0, ppstr)
fail ArgumentError, "SHGetKnownFolderPath return E_INVALIDARG" if ret == 0x80070057 # E_INVALIDARG
fail "SHGetKnownFolderPath return 0x#{ret.to_s(16)}" if ret != 0
str = ppstr.ptr
# 返ってきたppstrから文字列を取り出しCoTaskMemFreeで解放
wcscpy!(str).encode("UTF-8").tap{ CoTaskMemFree(str) }
end
end
例えばLocalLowを取得する場合は
locallow.rb
# {A520A1A4-1780-4FF6-BD18-167343C5AF16}
FOLDERID_LocalAppDataLow = [0xA520A1A4, 0x1780, 0x4FF6, 0xBD, 0x18, 0x16, 0x73, 0x43, 0xC5, 0xAF, 0x16].pack("L<S<S<C8")
puts locallow = Shell32.GetKnownFolderPath(FOLDERID_LocalAppDataLow)
# => C:\Users\{ユーザー}\AppData\LocalLow
反省点
-
wcscpy!
にエラー落ち・無限ループの危険がある。(そもそもこの実装は原始的すぎてなんとも・・・) - モジュール名が
Shell32
なのにOle32
のapiも呼び出している。 - windows以外の場合にエラー・警告を出した方が親切
- というか、外部モジュールにした方が100万倍楽