Win32API を使って、Ruby が開いている .dll を確認してみる。
方法はいくつかある。
まず、CreateToolhelp32Snapshot でプロセスのスナップショットを撮って、Module32First と Module32Next を使う方法。
↑のコードの主要部分を抜き出すと、こんな感じ。
require 'Win32API' # http://msdn.microsoft.com/en-us/library/ms686849(v=vs.85).aspx # load functions # DWORD WINAPI GetCurrentProcessId(void); win32_GetCurrentProcessId = Win32API.new('kernel32.dll', 'GetCurrentProcessId', 'V', 'L') # HANDLE WINAPI CreateToolhelp32Snapshot( __in DWORD dwFlags, __in DWORD th32ProcessID ); win32_CreateToolhelp32Snapshot = Win32API.new('kernel32.dll', 'CreateToolhelp32Snapshot', 'LL', 'L') # BOOL WINAPI Module32First( __in HANDLE hSnapshot, __inout LPMODULEENTRY32 lpme ); win32_Module32First = Win32API.new('kernel32.dll', 'Module32First', 'LP', 'I') # BOOL WINAPI Module32Next( __in HANDLE hSnapshot, __out LPMODULEENTRY32 lpme ); win32_Module32Next = Win32API.new('kernel32.dll', 'Module32Next', 'LP', 'I') # BOOL WINAPI CloseHandle( __in HANDLE hObject ); win32_CloseHandle = Win32API.new('kernel32.dll', 'CloseHandle', 'L', 'I') # constants _TH32CS_SNAPMODULE = 8 _INVALID_HANDLE_VALUE = -1 sizeof_MODULEENTRY32 = 548 # is this for i686 only? # start here dwPID = win32_GetCurrentProcessId.call() hModuleSnap = win32_CreateToolhelp32Snapshot.call(_TH32CS_SNAPMODULE, dwPID) if hModuleSnap == _INVALID_HANDLE_VALUE raise "Invalid handle value returned by CreateToolhelp32Snapshot" end # set dwSize first me32 = [sizeof_MODULEENTRY32].pack("N") + "\0" * (sizeof_MODULEENTRY32 - 4) # sizeof DWORD = 4 if !win32_Module32First.call(hModuleSnap, me32) win32_CloseHandle.call(hModuleSnap) raise "Error in Module32First" end while 1 #module_name = me32.slice(32, 256).sub(/\0.*/, '') executable = me32.slice(288, 260).sub(/\0.*/, '') puts executable break if 0 == win32_Module32Next.call(hModuleSnap, me32) end win32_CloseHandle.call(hModuleSnap)
LPMODULEENTRY32 というのはこういう構造体へのポインターになっている。
typedef struct tagMODULEENTRY32 { // size DWORD dwSize; // 4 DWORD th32ModuleID; // 4 DWORD th32ProcessID; // 4 DWORD GlblcntUsage; // 4 DWORD ProccntUsage; // 4 BYTE *modBaseAddr; // 1 (occupy 4 bytes due to alignment) DWORD modBaseSize; // 4 HMODULE hModule; // 4 TCHAR szModule[MAX_MODULE_NAME32 + 1];// 256 TCHAR szExePath[MAX_PATH]; // 260 } MODULEENTRY32, *PMODULEENTRY32; // 545 (total of above)
ここで気になるのが、この構造体のサイズは alignment によって変わるという点。うちではワード長が4なので MODULEENTRY32 のサイズは548に切り上げられるため、上のコードではそれが前提になっているが、もしかすると546に切り上げられたり、545のままの環境があるかもしれない。
また、うちでは sizeof(HANDLE) が4なんだけど、HANDLE の実体はポインターなので、long に変換するのは良くない気がしないでもない。(Windows 7 は 64bit OS でもポインターのサイズが 32bit なんだっけ?これは Ruby が 32bit でビルドされてるからか)
もうひとつは EnumProcessModules を使う方法。こっちもありがたくサンプルコードがあるので、写経するだけ。
#!/usr/bin/ruby require 'Win32API' # http://msdn.microsoft.com/en-us/library/ms682621(v=vs.85).aspx # functions # DWORD WINAPI GetCurrentProcessId(void); win32_GetCurrentProcessId = Win32API.new('kernel32.dll', 'GetCurrentProcessId', 'V', 'L') # HANDLE WINAPI OpenProcess( __in DWORD dwDesiredAccess, __in BOOL bInheritHandle, __in DWORD dwProcessId ); win32_OpenProcess = Win32API.new('kernel32.dll', 'OpenProcess', 'LIL', 'L') # BOOL WINAPI EnumProcessModules( __in HANDLE hProcess, __out HMODULE *lphModule, __in DWORD cb, __out LPDWORD lpcbNeeded ); win32_EnumProcessModules = Win32API.new('kernel32.dll', 'EnumProcessModules', 'LPLP', 'I') rescue Win32API.new('psapi.dll', 'EnumProcessModules', 'LPLP', 'I') # psapi.dll on XP # DWORD WINAPI GetModuleFileName( __in_opt HMODULE hModule, __out LPTSTR lpFilename, __in DWORD nSize ); win32_GetModuleFileName = Win32API.new('kernel32.dll', 'GetModuleFileName', 'LPL', 'L') # BOOL WINAPI CloseHandle( __in HANDLE hObject ); win32_CloseHandle = Win32API.new('kernel32.dll', 'CloseHandle', 'L', 'I') # constants _PROCESS_QUERY_INFORMATION = 0x0400 _PROCESS_VM_READ = 0x0010 _MAX_PATH = 260 sizeof_HANDLE = 4 # start here buflen = 1024 hMods = "\0" * sizeof_HANDLE * buflen processID = win32_GetCurrentProcessId.call() hProcess = win32_OpenProcess.call(_PROCESS_QUERY_INFORMATION | _PROCESS_VM_READ, 0, processID) raise "Error in OpenProcess" if 0 == hProcess cbNeeded = "\0" * 4 if 0 != win32_EnumProcessModules.call(hProcess, hMods, hMods.length, cbNeeded) len = cbNeeded.unpack("l!")[0] / sizeof_HANDLE hMods.unpack("l!#{len}").each {|hModule| szModName = "\0" * _MAX_PATH if 0 != win32_GetModuleFileName.call(hModule, szModName, _MAX_PATH) puts szModName.sub(/\0.*/, '') end } end win32_CloseHandle.call(hProcess)
とりあえず alignment の問題はなくなった。HANDLE と DWORD のサイズが4であるとしているところや、GetModuleFileName が LPTSTR を受け取るべきなのに勝手に LPSTR と想定しているのが問題と言えば問題。しかしうちでは動いているのが謎。Ruby のコンパイルのときに UNICODE が付いてないってことだろうか?
EnumerateLoadedModulesEx とか EnumerateLoadedModules64 というのがあるらしい。しかし、これらはコールバック関数ベースなので、たぶん Ruby で使うのは無理だと思う。
方法1と方法2の実行結果は同じ。
% ruby dlls.rb C:\cygwin\bin\ruby.exe C:\Windows\SysWOW64\ntdll.dll C:\Windows\syswow64\kernel32.dll C:\Windows\syswow64\KERNELBASE.dll C:\cygwin\bin\cygruby18.dll C:\cygwin\bin\cygcrypt-0.dll C:\cygwin\bin\cygwin1.dll C:\Windows\syswow64\ADVAPI32.DLL C:\Windows\syswow64\msvcrt.dll C:\Windows\SysWOW64\sechost.dll C:\Windows\syswow64\RPCRT4.dll C:\Windows\syswow64\SspiCli.dll C:\Windows\syswow64\CRYPTBASE.dll C:\Windows\syswow64\USER32.dll C:\Windows\syswow64\GDI32.dll C:\Windows\syswow64\LPK.dll C:\Windows\syswow64\USP10.dll C:\Windows\system32\IMM32.DLL C:\Windows\syswow64\MSCTF.dll \\?\C:\cygwin\lib\ruby\1.8\i386-cygwin\Win32API.so C:\Windows\syswow64\psapi.dll
もしスクリプトの一番上で require 'iconv' と書いたら、↓のような行が増える。
\\?\C:\cygwin\lib\ruby\1.8\i386-cygwin\iconv.so C:\cygwin\bin\cygiconv-2.dll C:\cygwin\bin\cyggcc_s-1.dll
この \\?\ というのは、Windows のパス指定のときの習慣で、MSDN に説明がある(と教えてもらった)。
とにかく、cygwin 由来の .dll や .so についてはこの接頭辞が付くことが多いので、cygwin の中間層で付け足して開いているのだと思う。
実際、mingw32 や mswin32 版の Ruby ではそういうのはつかない。
% /cygdrive/c/Ruby186/bin/ruby dlls.rb C:\Ruby186\bin\ruby.exe C:\Windows\SysWOW64\ntdll.dll C:\Windows\syswow64\kernel32.dll C:\Windows\syswow64\KERNELBASE.dll C:\Ruby186\bin\msvcrt-ruby18.dll C:\Windows\syswow64\ADVAPI32.DLL C:\Windows\syswow64\msvcrt.dll C:\Windows\SysWOW64\sechost.dll C:\Windows\syswow64\RPCRT4.dll C:\Windows\syswow64\SspiCli.dll C:\Windows\syswow64\CRYPTBASE.dll C:\Windows\syswow64\SHELL32.DLL C:\Windows\syswow64\SHLWAPI.dll C:\Windows\syswow64\GDI32.dll C:\Windows\syswow64\USER32.dll C:\Windows\syswow64\LPK.dll C:\Windows\syswow64\USP10.dll C:\Windows\syswow64\WS2_32.DLL C:\Windows\syswow64\NSI.dll C:\Windows\system32\apphelp.dll C:\Windows\AppPatch\AcLayers.DLL C:\Windows\syswow64\ole32.dll C:\Windows\syswow64\OLEAUT32.dll C:\Windows\system32\USERENV.dll C:\Windows\system32\profapi.dll C:\Windows\system32\WINSPOOL.DRV C:\Windows\system32\MPR.dll C:\Windows\system32\IMM32.DLL C:\Windows\syswow64\MSCTF.dll C:\Windows\system32\mswsock.dll C:\Ruby186\lib\ruby\1.8\i386-mingw32\iconv.so C:\Ruby186\bin\libiconv2.dll C:\Ruby186\lib\ruby\1.8\i386-mingw32\Win32API.so C:\Windows\syswow64\psapi.dll
Win32API は遊び以上の目的で使うものではないかな。