Ruby Win32 API интерфейс

Мне нужно получить доступ к нескольким функциям библиотеки win32 в ruby. Я нашел чрезвычайно скудную информацию о классе Win32API онлайн, поэтому я спрашиваю здесь.

Я знаю, что вы можете сделать что-то вроде этого:

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

Но я не могу вызвать эту функцию с текущими привязками win32:

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

Проблема в его прототипе:

UINT_PTR SHAppBarMessage(      
    DWORD dwMessage,
    PAPPBARDATA pData
);

Я смогу использовать привязки win32 ruby ​​для получения возвращаемого типа и первого параметра, однако второй ожидает структуру. Определение структуры выглядит следующим образом:

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

Я попытался определить этот метод API, используя оба:

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

а также

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

но первый происходит сбой во время метода call, а второй не запускается из-за неправильного количества аргументов, указанных в вызове метода call. Есть ли способ предоставить эту функцию API, не прибегая к созданию внешнего модуля в C++?

Благодарю.

3 ответа

Хитрость заключается в том, чтобы использовать "P" в качестве спецификатора формата для всех аргументов указателя. Вы должны будете указать строку как указанную область.

Конечно, вы должны убедиться, что эти строки имеют правильный ожидаемый размер, иначе произойдут плохие вещи.

Вы можете напрямую создавать эти строки

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

или использовать более цивилизованный Array#pack

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

Надеюсь это поможет.


Следующее работает на моей коробке XP со старым 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

SHAppBarMessage принимает два параметра: DWORD и указатель на APPBARDATA,
так что это должно быть объявлено так:

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

затем называется:

msg_id = 1
app_bar_data = "правильно инициализированная двоичная строка" # должен иметь размер байтов (APPBARDATA)
app_bar_msg.call(msg_id, app_bar_data)

Но я не знаю Руби, так что, возможно, я ошибаюсь...

Я думаю, вам придется расследовать String#pack способ получить ваш APPBARDATA struct заполнено правильно.

См. Раздел книги "Кирка" по Win32 и Ruby (прокрутите вниз до определения класса Win32API).

Как уже было замечено, вы будете использовать аргумент "P" и передадите правильно упакованный String (или же Strings) в функцию.

В качестве альтернативы, если у вас есть немного времени для исследования, вы можете взглянуть на библиотеку FFI, которая, кажется, делает все довольно дружелюбно. У меня нет прямого опыта, но попробуйте посмотреть на

Другие вопросы по тегам