Ruby win32 api interface

后端 未结 3 1465
囚心锁ツ
囚心锁ツ 2020-12-06 03:12

I need to access a few functions of the win32 library in ruby. I have found extremely sparse information on the Win32API class online, so I\'m asking here.

I know th

相关标签:
3条回答
  • 2020-12-06 03:34

    The trick is to use 'P' as a format specifier for all pointer arguments. You'll have to provide a string as the pointed-to area.

    Of course you'll have to make sure these strings have the correct expected size otherwise bad things will happen.

    You can directly create these strings

    # Mostly useful when the area will be totally overwritten
    pointed_to_area = "\0" * n
    

    or use the more civilized Array#pack

    # Allows you to control how ruby values get encoded in the buffer
    pointed_to_area = [1, 2, 3, 4].pack('SsLI')
    

    Hope this helps.


    The following works on my XP box with an old ruby 1.8.2:

    require 'Win32API'
    
    
    module Win32
      # This method is only here for test purposes
      # Be careful to use the ascii version
      FindWindow = Win32API.new('user32', 'FindWindowA', ['P', 'P'], 'L')
      def self.findWindow(lpClassName, lpWindowName)
        h = FindWindow.call(lpClassName, lpWindowName)
        raise "FindWindow failed" if h == 0
        h
      end
    
      # From winddef.h
      RECT = Struct.new(:left, :top, :right, :bottom)
      RECT.class_eval do
        def pack
          [left, top, right, bottom].pack('l4')
        end
        def self.unpack(s)
          new(*s.unpack('l4'))
        end
      end
    
      # From shellapi.h
      APPBARDATA = Struct.new(:cbSize, :hWnd, :uCallbackMessage, :uEdge, :rc, :lParam)
      APPBARDATA.class_eval do
        def pack
          unless rc.is_a? RECT
            raise ArgumentError, ":rc must be an instance of Win32::RECT, got #{rc.inspect}"
          end
          # DWORD + HWND + UINT + UINT + RECT + LPARAM
          cbSize = 4 + 4 + 4 + 4 + 16 + 4
          [cbSize, hWnd, uCallbackMessage, uEdge, rc.pack, lParam].pack('L2I2a16L')
        end
        def self.unpack(s)
          tmp = self.new(*s.unpack('L2I2a16L'))
          tmp.rc = RECT.unpack(tmp.rc)
          tmp
        end
      end
      SHAppBarMessage = Win32API.new('shell32', 'SHAppBarMessage', ['L', 'P'], 'L')
    
      # Calls SHAppBarMessage and returns the altered APPBARDATA
      def self.shAppBarMessage(dwMessage, appBarData)
        s = appBarData.pack
        ok = (SHAppBarMessage.call(dwMessage, s) != 0)
        raise "SHAppBarMessage failed" unless ok
        APPBARDATA.unpack(s)
      end
    
      ABM_NEW              = 0x00000000
      ABM_REMOVE           = 0x00000001
      ABM_QUERYPOS         = 0x00000002
      ABM_SETPOS           = 0x00000003
      ABM_GETSTATE         = 0x00000004
      ABM_GETTASKBARPOS    = 0x00000005
      ABM_ACTIVATE         = 0x00000006
      ABM_GETAUTOHIDEBAR   = 0x00000007
      ABM_SETAUTOHIDEBAR   = 0x00000008
      ABM_WINDOWPOSCHANGED = 0x00000009
      ABM_SETSTATE         = 0x0000000a
    
      ABE_LEFT   = 0
      ABE_TOP    = 1
      ABE_RIGHT  = 2
      ABE_BOTTOM = 3
    end
    
    
    
    
    if __FILE__ == $0
      require 'test/unit'
      class SHAppBarMessageTest < Test::Unit::TestCase
        include Win32
    
        def test_pack_unpack
          a = APPBARDATA.new(-1, 0, 0, ABE_TOP, RECT.new(1, 2, 3, 4), 0)
          b = APPBARDATA.unpack(a.pack)
          a.cbSize = b.cbSize
          assert_equal(a.values, b.values)
        end
        def test_simple_pos_query
          h = Win32.findWindow("Shell_TrayWnd", nil)
          a = APPBARDATA.new(-1, 0, 0, ABE_TOP, RECT.new(0, 0, 0, 0), 0)
          result = Win32.shAppBarMessage(ABM_GETTASKBARPOS, a)
          assert(result.rc.left < result.rc.right)
          assert(result.rc.top < result.rc.bottom)
          puts result.rc.inspect
        end
      end
    end
    
    0 讨论(0)
  • 2020-12-06 03:43

    SHAppBarMessage takes two parameters: a DWORD and a pointer to APPBARDATA,
    so it shuold be declared thus:

    app_bar_msg = Win32API.new('shell32', 'SHAppBarMessage', ['L', 'P'], 'L')

    then called:

    msg_id = 1
    app_bar_data = "properly initalized binary string" #should have sizeof(APPBARDATA) bytes
    app_bar_msg.call(msg_id, app_bar_data)

    But I don't know Ruby, so maybe I am mistaken...

    0 讨论(0)
  • 2020-12-06 03:46

    I think you'll have to investigate the String#pack method to get your APPBARDATA struct filled correctly.

    See the "Pickaxe" book section on Win32 and Ruby (scroll down to the Win32API class definition).

    So as already spotted, you'll use a 'P' argument and you'll pass a properly-packed String (or Strings) into the function.

    Alternatively, if you have a little time to investigate, you may want to look at the FFI library, which seems to do everything in a rather more friendly way. I have no direct experience, but try looking at

    • The original announcement by Charles Nutter of the JRuby team, where there's an example of a much nicer way to declare C structs.
    • The Ruby FFI home page at Sun
    0 讨论(0)
提交回复
热议问题