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
(или же String
s) в функцию.
В качестве альтернативы, если у вас есть немного времени для исследования, вы можете взглянуть на библиотеку FFI, которая, кажется, делает все довольно дружелюбно. У меня нет прямого опыта, но попробуйте посмотреть на
- Первоначальное объявление Чарльза Наттера из команды JRuby, где есть пример гораздо более приятного способа объявления структур Си.
- Домашняя страница Ruby FFI в Sun