Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
88 views
in Technique[技术] by (71.8m points)

c++ - Ruby win32 api interface

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 that you can do something like this:

function = Win32API.new('user32','MessageBox',['L', 'P', 'P', 'L'],'I')

But I can't seem to be able to call this function with the current win32 bindings:

http://msdn.microsoft.com/en-us/library/bb762108%28VS.85%29.aspx

The problem is in its prototype:

UINT_PTR SHAppBarMessage(      
    DWORD dwMessage,
    PAPPBARDATA pData
);

I will be able to use the win32 ruby bindings to grab the return type and the first parameter, however, the second one expects a structure. The definition of the structure is as follows:

typedef struct _AppBarData {
    DWORD cbSize;
    HWND hWnd;
    UINT uCallbackMessage;
    UINT uEdge;
    RECT rc;
    LPARAM lParam;
} APPBARDATA, *PAPPBARDATA;

I tried to define this api method using both:

api = Win32API.new('shell32','SHAppBarMessage',['L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L'],'I') 

and

api = Win32API.new('shell32','SHAppBarMessage',['L', 'LLLLLLLL'],'I')

but the first one segfaults during the "call" method and the second fails to run due to the wrong amount of arguments specified in the "call" method invocation. Is there any way to expose this api function without resorting to creating an external module in C++?

Thanks.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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 = "" * 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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...