Skip to content

Instantly share code, notes, and snippets.

@sancarn
Last active July 15, 2018 20:14
Show Gist options
  • Save sancarn/365d561e8c428eed3b3c74e70f592384 to your computer and use it in GitHub Desktop.
Save sancarn/365d561e8c428eed3b3c74e70f592384 to your computer and use it in GitHub Desktop.
How to call enum windows (and other win32 functions) using dl / fiddle, in a clean manor.

Source: https://syon.github.io/wiki/Win32API/

Pre ruby 2.0:

require 'dl/import'

module User32
  extend DL::Importer
  dlload 'user32'
  extern 'int MessageBoxA(int, char*, char*, int)'
  extern 'int EnumWindows(void*, int)'
end

=begin
cf = DL::CFunc.new(0, DL::TYPE_INT)
cb = DL::Function.new(cf, [DL::TYPE_INT])
cb.bind{|hwnd|
  puts hwnd
  -1
}
=end

=begin
cb = DL::Function.new(DL::CFunc.new(0, DL::TYPE_INT), [DL::TYPE_INT]){|hwnd|
  puts hwnd
  -1
}
=end

def f(hwnd)
  puts hwnd
  -1
end

cb = DL::Function.new(DL::CFunc.new(0, DL::TYPE_INT), [DL::TYPE_INT], &method(:f))

User32.EnumWindows(cb, 0)
User32.MessageBoxA(0, "exit", "caption", 0)

Ruby 2.0:

module User32
  extend Fiddle::Importer
  dlload 'user32'
  extern 'int MessageBoxA(int,char*, char*, int)'
  extern 'int EnumWindows(void*,int)'
end

cb = Fiddle::Closure::BlockCaller.new(Fiddle::TYPE_INT, [Fiddle::TYPE_INT]) do |hwnd|
  puts hwnd
  -1
end
func = Fiddle::Function.new(cb,[Fiddle::TYPE_INT], Fiddle::TYPE_INT)
User32.EnumWindows(func,0)

WIN32API Fiddle implementation source:

require 'fiddle'

class Win32API
  DLL = {}
  TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
  POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'

  def initialize(dllname, func, import, export = "0", calltype = :stdcall)
    @proto = [import].join.tr("VPpNnLlIi", "0SSI").sub(/^(.)0*$/, '\1')
    handle = DLL[dllname] ||= Fiddle.dlopen(dllname)
    temp = import.each_char.map{|s|s.tr("VPpNnLlIi", "0SSI")}.map{|v|TYPEMAP[v]}
    @func = Fiddle::Function.new(handle[func], temp, TYPEMAP[export.tr("VPpNnLlIi", "0SSI")], calltype == :stdcall ? Fiddle::Function::STDCALL : Fiddle::Function::DEFAULT)
  rescue Fiddle::DLError => e
    raise LoadError, e.message, e.backtrace
  end

  def call(*args)
    import = @proto.split("")
    args.each_with_index do |x, i|
      args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
      args[i], = [x].pack("I").unpack("i") if import[i] == "I"
    end
    ret, = @func.call(*args)
    return ret || 0
  end

  alias Call call
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment