hack のためのネタ帳, etc,,,

Ruby から Win32 API 叩くためのクラス
添付ライブラリに含まれる

サンプル

PSAPI と kernel32 によるプロセス一覧とかプロセスの終了とか (2011-02-06)

#!/usr/bin/ruby
require 'Win32API'

class String
  def to_DWORD x = 0
    self.to_DWORDS[x]
  end
  def to_DWORDS
    self.unpack("L*")
  end
end

class Win32API
  SIZEOF_DWORD = 4
  SIZEOF_TCHAR = 1
  
  # @reference winnt.h
  SYNCHRONIZE               = 0x100000
  STANDARD_RIGHTS_REQUIRED  = 0xF0000
  PROCESS_TERMINATE         =    1
  PROCESS_CREATE_THREAD     =    2
  PROCESS_SET_SESSIONID     =    4
  PROCESS_VM_OPERATION      =    8
  PROCESS_VM_READ           =   16
  PROCESS_VM_WRITE          =   32
  PROCESS_DUP_HANDLE        =   64
  PROCESS_CREATE_PROCESS    =  128
  PROCESS_SET_QUOTA         =  256
  PROCESS_SUSPEND_RESUME    = 0x0800
  PROCESS_SET_INFORMATION   =  512
  PROCESS_QUERY_INFORMATION = 1024
  PROCESS_ALL_ACCESS        = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)
  
  ############################################################################
  # kernel32.dll
  
  # @reference http://msdn.microsoft.com/ja-jp/library/cc429278.aspx
  def Win32API.OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId)
    openprocess = Win32API.new('kernel32.dll', 'OpenProcess', 'lll', 'l')
    bInheritHandle = bInheritHandle ? 1 : 0
    hProcess = openprocess.call(dwDesiredAccess, bInheritHandle, dwProcessId)
  end
  
  # @reference http://msdn.microsoft.com/ja-jp/library/cc429605.aspx
  def Win32API.CloseHandle(hObject)
    closehandle = Win32API.new('kernel32.dll', 'CloseHandle', 'l', 'l')
    closehandle.call(hObject) ? true : false
  end
  
  # @reference http://msdn.microsoft.com/ja-jp/library/cc429376.aspx
  def Win32API.TerminateProcess(hProcess, uExitCode)
    terminateprocess = Win32API.new('kernel32.dll', 'TerminateProcess', 'll', 'l')
    terminateprocess.call(hProcess, uExitCode) ? true : false
  end
  
  ############################################################################
  # psapi.dll
  
  # @reference http://msdn.microsoft.com/ja-jp/library/cc429383.aspx
  def Win32API.EnumProcesses
    enumprocesses = Win32API.new('psapi.dll', 'EnumProcesses', 'plp', 'i')
    lpidProcess = [0].pack("L") * 1024
    cbNeeded    = [0].pack("L")
    while true
      enumprocesses.call(lpidProcess, lpidProcess.length, cbNeeded)
      break if cbNeeded.to_DWORD < lpidProcess.length
      lpidProcess *= 2
    end
    lpidProcess.slice(0, cbNeeded.to_DWORD).to_DWORDS
  end
  
  # @reference http://msdn.microsoft.com/ja-jp/library/cc429387.aspx
  def Win32API.EnumProcessModules(hProcess)
    enumprocessmodules = Win32API.new('psapi.dll', 'EnumProcessModules', 'lplp', 'l')
    lphModule  = ""
    lpcbNeeded = [0].pack("L")
    while true do
      result = enumprocessmodules.call(hProcess, lphModule, lphModule.length, lpcbNeeded)
      return [] if result == 0
      break if lpcbNeeded.to_DWORD <= lphModule.length
      lphModule = "\0" * lpcbNeeded.to_DWORD
    end
    lphModule.slice(0, lpcbNeeded.to_DWORD).to_DWORDS
  end
  
  # @reference http://msdn.microsoft.com/ja-jp/library/cc429400.aspx
  def Win32API.GetModuleBaseName(hProcess, hModule)
    getmodulebasename = Win32API.new("psapi.dll", "GetModuleBaseName", "llpl", "l")
    lpBaseName = "\0" * 1024
    while true do
      len = getmodulebasename.call(hProcess, hModule, lpBaseName, lpBaseName.length / SIZEOF_TCHAR)
      break if len < lpBaseName.length / SIZEOF_TCHAR
      lpBaseName *= 2
    end
    lpBaseName.slice(0, len * SIZEOF_TCHAR)
  end
  
  # @reference http://msdn.microsoft.com/ja-jp/library/cc429403.aspx
  def Win32API.GetModuleFileNameEx(hProcess, hModule)
    getmodulefilenameex = Win32API.new("psapi.dll", "GetModuleFileNameEx", "llpl", "l")
    lpFilename = "\0" * 1024
    while true do
      len = getmodulefilenameex.call(hProcess, hModule, lpFilename, lpFilename.length / SIZEOF_TCHAR)
      break if len < lpFilename.length / SIZEOF_TCHAR
      lpFilename *= 2
    end
    lpFilename.slice(0, len * SIZEOF_TCHAR)
  end
end



def putProcessList bShowModules = false
  Win32API.EnumProcesses.sort.each do |pidProcess|
    hProcess = Win32API.OpenProcess(Win32API::PROCESS_QUERY_INFORMATION | Win32API::PROCESS_VM_READ, FALSE, pidProcess)
    hModules = Win32API.EnumProcessModules(hProcess)
    indent = sprintf("%d ", pidProcess)
    hModules.each do |hModule|
      printf("%s%s\n", indent, Win32API.GetModuleFileNameEx(hProcess, hModule))
      indent = "\t"
      break if !bShowModules
    end
    Win32API.CloseHandle hProcess
  end
end

def KillProcess pid
  hProcess = Win32API.OpenProcess(Win32API::PROCESS_TERMINATE, FALSE, pid)
  Win32API.TerminateProcess hProcess, 0
  Win32API.CloseHandle hProcess
end

ちょっと汚いけどこんな感じで行けそう
例えば上記のスクリプトを PSAPItest.rb として保存しておくと以下のようにして使える
$ ruby -r PSAPItest.rb -e "putProcessList"
kill したいプロセスの pid が 12345 なら以下のようにすれば kill できる
$ ruby -r PSAPItest.rb -e "KillProcess 12345"

ポインタ渡しの引数に戻り値が入る場合とかをどう扱うかが悩みどころな気がする
wrapper に徹するべきか、Ruby らしい関数やクラスに仕立てるべきか悩ましい

Ruby から sizeof() の値知る方法がないのでちょっと困る。
例えば
GetModuleFileNameEx()
nSize って sizeof(lpFilename)/sizeof(TCHAR) のはずなんだけど
sizeof(TCHAR) とかどうやってとれば良いんだ?

32bit 環境と 64bit 環境でポインタサイズが違うのでハマりそうな予感
とりあえず環境の判別は以下の方法で可能な模様
Answers: Determining XP 32 or 64Bit by calling IsWow64Process():

関連

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Wiki内検索

フリーエリア

編集にはIDが必要です