LoginSignup
1
1

More than 1 year has passed since last update.

Rubyで特殊フォルダのパスを取得する方法

Last updated at Posted at 2021-04-14

前書き

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万倍楽
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1