require 'win32/api' require 'rbconfig' require 'forwardable' include RbConfig # The Windows module serves as a namespace only module Windows # With Microsoft Visual C++ 8 and later users should use the associated # DLL instead of msvcrt directly, if possible. if CONFIG['host_os'].split('_')[1] if CONFIG['host_os'].split('_')[1].to_i >= 80 MSVCRT_DLL = 'msvcr' + CONFIG['host_os'].split('_')[1] else MSVCRT_DLL = 'msvcrt' end else MSVCRT_DLL = 'msvcrt' end # Wrapper around the Win32::API class class API extend Forwardable # The version of the windows-api library VERSION = '0.4.4' # The methods from Win32::API are delegated to the appropriate object def_delegators(:@api, :function_name, :dll_name, :prototype) def_delegators(:@api, :return_type, :effective_function_name) private # Verbose data types that can be used instead of single letters DATA_TYPES = { 'ATOM' => 'I', 'BOOL' => 'B', 'BOOLEAN' => 'B', 'BYTE' => 'I', 'CALLBACK' => 'K', 'CHAR' => 'I', 'COLORREF' => 'L', 'DWORD' => 'L', 'DWORDLONG' => 'L', 'DWORD_PTR' => 'P', 'DWORD32' => 'I', 'DWORD64' => 'L', 'HACCEL' => 'L', 'HANDLE' => 'L', 'HBITMAP' => 'L', 'HBRUSH' => 'L', 'HCOLORSPACE' => 'L', 'HCONV' => 'L', 'HDC' => 'L', 'HFILE' => 'I', 'HFONT' => 'L', 'HINSTANCE' => 'L', 'HKEY' => 'L', 'HLOCAL' => 'L', 'HMENU' => 'L', 'HMODULE' => 'L', 'HRESULT' => 'L', 'HWND' => 'L', 'INT' => 'I', 'INT_PTR' => 'P', 'INT32' => 'I', 'INT64' => 'L', 'LANGID' => 'I', 'LCID' => 'L', 'LCTYPE' => 'L', 'LONG' => 'L', 'LONGLONG' => 'L', 'LONG_PTR' => 'P', 'LONG32' => 'L', 'LONG64' => 'L', 'LPARAM' => 'P', 'LPBOOL' => 'P', 'LPBYTE' => 'P', 'LPCOLORREF' => 'P', 'LPCSTR' => 'P', 'LPCTSTR' => 'P', 'LPCVOID' => 'L', 'LPCWSTR' => 'P', 'LPDWORD' => 'P', 'LPHANDLE' => 'P', 'LPINT' => 'P', 'LPLONG' => 'P', 'LPSTR' => 'P', 'LPTSTR' => 'P', 'LPVOID' => 'L', 'LPWORD' => 'P', 'LPWSTR' => 'P', 'LRESULT' => 'P', 'PBOOL' => 'P', 'PBOOLEAN' => 'P', 'PBYTE' => 'P', 'PHKEY' => 'P', 'SC_HANDLE' => 'L', 'SC_LOCK' => 'L', 'SERVICE_STATUS_HANDLE' => 'L', 'SHORT' => 'I', 'SIZE_T' => 'P', 'TCHAR' => 'L', 'UINT' => 'I', 'UINT_PTR' => 'P', 'UINT32' => 'I', 'UINT64' => 'L', 'ULONG' => 'L', 'ULONGLONG' => 'L', 'ULONG_PTR' => 'P', 'ULONG32' => 'L', 'ULONG64' => 'L', 'USHORT' => 'I', 'USN' => 'L', 'WINAPI' => 'L', 'WORD' => 'I' } public @auto_constant = false @auto_method = false @auto_unicode = false @auto_namespace = nil # Returns the value of the @auto_constant class instance variable. The # default is nil, i.e. none. See the Windows::API.auto_constant= # documentation for more information. # def self.auto_constant @auto_constant end # Automatically sets a constant to match the function name. # # The standard practice for defining Windows::API objects is to use # a constant that matches the function name. For example, this is a # typical idiom: # # module Windows # module File # GetFileAttributes = API.new('GetFileAttributes', 'P','L') # end # end # # With the API.auto_constant value set to true you can avoid the # assignment step and the matching constant name will be automatically # set for you in the namespace defined in API.auto_namespace. In other # words, this example is identical to the one above: # # module Windows # module File # API.auto_constant = true # API.auto_namespace = 'Windows::File' # API.new('GetFileAttributes', 'P', 'L') # end # end # # If the auto_constant class variable is set to true, but no # auto_namespace is set, an error will be raised. Note that the # namespace must refer to an existing module (not a class). #-- # TODO: If there's a way to automatically grab the namespace internally, # nesting and all, I'd love to know the solution. # def self.auto_constant=(bool) @auto_constant = bool end # Returns the value of the auto_namespace class instance variable. Used # in conjunction with API.auto_constant and/or API.auto_method. # def self.auto_namespace @auto_namespace end # Sets the value of the auto_namespace class nstance variable. The # default is nil, i.e. none. Use in conjunction with the auto_constant # and/or auto_method class variables, this method will automatically set # a constant and/or method in +namespace+ equal to the function name set # in the constructor. # # The +namespace+ must refer to an existing module, not a class. # def self.auto_namespace=(namespace) @auto_namespace = namespace end # Returns the value of the auto_method class instance variable. Used in # conjunction with auto_unicode. See API.auto_method= for more # information. # def self.auto_method @auto_method end # If this option is set to true then a corresponding method is # automatically generated when you create a new Windows::API object. # # For example, instead of doing this: # # module Windows # module File # GetFileAttributes = API.new('GetFileAttributes', 'P', 'L') # # def GetFileAttributes(x) # GetFileAttributes.call(x) # end # end # end # # You can do this, and have the method autogenerated for you. # # module Windows # module File # API.auto_namespace = 'Windows::File' # API.auto_constant = true # API.auto_method = true # API.new('GetFileAttributes', 'P', 'L') # end # end # # include Windows::File # GetFileAttributes('C:/test.txt') # vs. GetFileAttributes.call # # If the Windows::API object is declared to be a boolean in the # constructor, then the method definition automatically includes a # '!= 0' clause at the end of the call. That way, you can do # 'if SomeMethod(x)' instead of having to do 'if SomeMethod(x) != 0', # and it will do the right thing. # # If the API.auto_unicode option is also set to true, then you will # get three method definitions. The standard function name, the explicit # ANSI ('A') version and the explicit Unicode/wide version ('W'). The # exception to this rule is that the explicit ANSI and Unicode methods # will NOT be generated if the function passed to the constructor # already ends with 'A' or 'W'. # def self.auto_method=(bool) @auto_method = bool end # Returns the value of the auto_unicode class instance variable. This # is used in conjunction with the auto_method and/or auto_constant class # variables. Not significant if neither of those variables are set. # def self.auto_unicode @auto_unicode end # If set to true, and the auto_constant variable is set, then the # automatic constant generation will generate the explicit ANSI ('A') # and Unicode/wide ('W') versions of the function passed to the # constructor, if such versions exist. Likewise, if the the # auto_method variable is set, then explicit ANSI and Unicode methods # are generated. # # Here's a typical idiom: # # module Windows # module Path # API.auto_namespace = Windows::Path # API.auto_constant = true # API.new('shlwapi', 'PathAddBackslash', 'P', 'P') # API.new('shlwapi', 'PathAddBackslashA', 'P', 'P') # API.new('shlwapi', 'PathAddBackslashW', 'P', 'P') # end # end # # That can be reduced to this: # # module Windows # module Path # API.auto_namespace = Windows::Path # API.auto_constant = true # API.auto_unicode = true # API.new('shlwapi', 'PathAddBackslash', 'P', 'P') # end # end # # This value is ignored if the function passed to the constructor # already ends with an 'A' or 'W'. # def self.auto_unicode=(bool) @auto_unicode = bool end # call-seq: # API.new(func, proto='V', rtype='L', dll='kernel32') # # Creates and returns a new Windows::API object. The +func+ is the # name of the Windows function. # # The +proto+ is the function prototype for +func+. This can be a # string or an array of characters. The possible valid characters # are 'I' (integer), 'B' (BOOL), 'L' (long), 'V' (void), or 'P' (pointer). # The default is void ('V'). # # The +rtype+ argument is the return type for the function. The valid # characters are the same as for the +proto+. The default is long ('L'). # # The 'B' (BOOL) return type is the same as 'I' (Integer). This is # significant only if the API.auto_method option is set to true, in which # case it alters the generated method definition slightly. See the # API.auto_method= class method for more information. # # The +dll+ is the name of the DLL file that the function is exported # from. The default is 'kernel32'. # # If the function cannot be found then an API::Error is raised (a subclass # of RuntimeError). # def initialize(func, proto='V', rtype='L', dll='kernel32') # Convert literal data types to values that win32-api understands if proto.is_a?(Array) proto.each_with_index{ |pt, index| if pt.length > 1 proto[index].replace(DATA_TYPES[pt]) end } end if rtype.length > 1 rtype.replace(DATA_TYPES[rtype]) end @function_name = func @prototype = proto @return_type = rtype == 'B' ? 'I' : rtype @dll_name = dll @boolean = rtype == 'B' ? true : false @api = Win32::API.new(func, proto, rtype, dll) api_a = nil api_w = nil # If the auto_unicode option is set, and the func is not already # an explicit ANSI or Wide function name, generate Win32::API # objects for those functions as well. Ignore errors because not # all functions have explicit ANSI or Wide character implementations. # # This entire bit of logic is skipped if the DLL is msvcrt, since # msvcrt functions never have explicit ANSI or Wide character # versions. # if Windows::API.auto_unicode && dll !~ /msvcr/i begin unless ['A', 'W'].include?(func[-1].chr) api_a = Win32::API.new("#{func}A", proto, rtype, dll) end rescue RuntimeError end begin unless ['W', 'A'].include?(func[-1].chr) api_w = Win32::API.new("#{func}W", proto, rtype, dll) end rescue RuntimeError end end func_upper = nil # Automatically define a constant matching the function name if the # auto_constant option is set. Lower case method names will have a # capitalized equivalent created, e.g. Memcpy for memcpy, etc. # if Windows::API.auto_constant && Windows::API.auto_namespace if Windows::API.auto_namespace != 'Windows' namespace = class_for(Windows::API.auto_namespace) else namespace = Windows::API.auto_namespace end # Convert e.g. 'strstr' to 'Strstr' as an equivalent constant if ('A'..'Z').include?(func[0].chr) namespace.const_set(func, @api) else func_upper = func.dup if func_upper[0].chr == '_' func_upper = func_upper[1, func_upper.length] end func_upper[0, 1] = func_upper[0].chr.capitalize namespace.const_set(func_upper, @api) end # Automatically define the explicit ANSI and Unicode functions # as constants as well, if appropriate. # if Windows::API.auto_unicode namespace.const_set("#{func}A", api_a) if api_a namespace.const_set("#{func}W", api_w) if api_w end end # Automatically define a method in the auto_namespace if the # auto_method option is set. The explicit ANSI and Unicode methods # are added as well if the auto_unicode option is set to true. # if Windows::API.auto_method && Windows::API.auto_namespace if proto == 'V' proto = '' else n = 0 if proto.is_a?(String) proto = proto.split('').map{ |e| n += 1 e.downcase + n.to_s }.join(', ') else proto = proto.map{ |e| n += 1 e.downcase + n.to_s }.join(', ') end end # Use the upper case function equivalent if defined call_func = func_upper || func if @boolean string = <<-EOC module #{Windows::API.auto_namespace} def #{func}(#{proto}) #{call_func}.call(#{proto}) != 0 end EOC if api_a string << %Q{ def #{func}A(#{proto}) #{call_func}A.call(#{proto}) != 0 end } end if api_w string << %Q{ def #{func}W(#{proto}) #{call_func}W.call(#{proto}) != 0 end } end string << 'end' else string = <<-EOC module #{Windows::API.auto_namespace} def #{func}(#{proto}) #{call_func}.call(#{proto}) end EOC if api_a string << %Q{ def #{func}A(#{proto}) #{call_func}A.call(#{proto}) end } end if api_w string << %Q{ def #{func}W(#{proto}) #{call_func}W.call(#{proto}) end } end # Create aliases for methods with an underscore that do not # require an underscore, e.g. umask and _umask. if func[0].chr == '_' func_alias = func[1, func.length] string << "alias #{func_alias} #{func}\n" end string << 'end' end eval(string) end end # Calls the function name set in the constructor. # def call(*args) @api.call(*args) end private # Get a module's namespace. This is basically the equivalent of # the rb_path2class() function from intern.h # def class_for(class_name) names = class_name.split("::") result = Object names.each{ |n| result = result.const_get(n) } result end end end