Как вызвать CreateWindowEx из Ruby?

У меня есть следующий код, который подходит, если я указываю неверные параметры (хотя, очевидно, не работает), но всякий раз, когда я даю точные параметры, ruby ​​segfaults. Я склонен полагать, что это проблема с моим кодом и/или способностью ruby ​​фактически вызывать эту функцию API, но я хотел бы получить дополнительную информацию. Я пробовал как Win32API, так и DL::Importer с теми же результатами. Есть ли способ заставить это работать?

Для любопытных здесь доступна полная предыстория, включая попытки с Win32API и DL::Importer на разных ветках. Вы ищете examples/windows-test в обоих случаях.

EDIT: мне удалось заставить RegisterClassEx работать, но это все еще не помогает. Ruby молча падает в CreateWindowEx.

Следующее дает вывод следующим образом:

wndproc: 4293787656
hInstance: 4194304
Вход в RegisterClassEx
Класс окна: 49795
Вход в CreateWindowEx

РЕДАКТИРОВАТЬ 2. Мой незавершенный код стал немного больше, чтобы его можно было вставить в SE. Если вам нужен весь фон, вы можете увидеть его по ссылке выше. Тем не менее, я постарался сохранить здесь все, что относится к делу.

class Windows
  def initialize
    puts "wndproc: #{Win32::User32::WNDPROC}"

    hInstance = Win32::Kernel32::GetModuleHandle(DL::NULL)
    puts "hInstance: #{hInstance}"

    puts "Entering RegisterClassEx"

    @window_class_struct = Win32::User32::WNDCLASSEX.malloc
    @window_class_struct.cbSize        = Win32::User32::WNDCLASSEX.size
    @window_class_struct.style         = Win32::User32::CS_HREDRAW | Win32::User32::CS_VREDRAW
    @window_class_struct.lpfnWndProc   = Win32::User32::WNDPROC
    @window_class_struct.cbClsExtra    = 0
    @window_class_struct.cbWndExtra    = 0
    @window_class_struct.hInstance     = hInstance
    @window_class_struct.hIcon         = 0
    @window_class_struct.hCursor       = 0
    @window_class_struct.hbrBackground = Win32::User32::COLOR_WINDOWFRAME
    @window_class_struct.lpszMenuName  = DL::NULL
    @window_class_struct.lpszClassName = 'ruby-skype'
    @window_class_struct.hIconSm       = 0

    p @window_class_struct

    @window_class = Win32::User32::RegisterClassEx(@window_class_struct.to_i)
    puts "Window Class: #{@window_class}"

    puts "Entering CreateWindowEx"
    @window = Win32::User32::CreateWindowEx(0, 'ruby-skype', 'ruby-skype', Win32::User32::WS_OVERLAPPEDWINDOW,
                                    0, 0, 200, 200, DL::NULL, DL::NULL, DL::NULL)
    puts "Exited CreateWindowEx"

    p @window
  end

  module Win32

    module Types
      def included(m)
        m.module_eval {
          include ::DL::Win32Types

          # @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751.aspx
          typealias('HBRUSH', 'HANDLE')
          typealias('HCURSOR', 'HANDLE')
          typealias('HICON', 'HANDLE')
          typealias('HMENU', 'HANDLE')
          typealias('HMODULE', 'HANDLE')
          typealias('LPCTSTR', 'unsigned char *')
          typealias('LPVOID', 'void *')
          typealias('WNDPROC', 'void *') # Actually a function pointer
          typealias('WNDCLASSEX', 'void *') # struct
        }
      end
      module_function :included
    end

    module User32
      extend DL
      extend DL::Importer
      dlload 'user32'
      include Types

      extern 'HWND CreateWindowEx(DWORD, LPCTSTR, LPCTSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE)'

      WNDPROC = set_callback DL::TYPE_LONG, 4 do |window_handle, message_id, wParam, lParam|
        puts "WM: #{message_id}"
      end
    end
  end
end

Windows.new

person Matthew Scharley    schedule 18.06.2012    source источник
comment
Ну, вы говорите окнам НЕ создавать окно. Второй параметр CreateWindowEx — это имя класса создаваемого окна. Для предопределенных элементов управления (Windows) это может быть BUTTON, EDIT и т. д. Если вы хотите создать родительское окно, вам нужно сначала создать атом с помощью RegisterWindowEx, а затем передать его как имя класса.   -  person Gunner    schedule 18.06.2012
comment
@Gunner: Это должна была быть моя первая попытка сегодня вечером, но если так, то почему имя класса представлено как необязательное поле в документах API?   -  person Matthew Scharley    schedule 18.06.2012
comment
Потому что мы говорим о писателе документов в MS, и они, кажется, делают много ошибок/упущений. В любом случае вы должны проверять возврат вызовов API. CreateWindowEx возвращает NULL при ошибке, когда это происходит, вызовите GetLastError, чтобы узнать причину   -  person Gunner    schedule 18.06.2012
comment
@Gunner: я получаю NULL/Invalid Argument от GetLastError или segfault, отсюда и мой вопрос. Я предполагаю, что segfault означает, что я делаю успехи и передаю правильные параметры, за исключением того, что Ruby и/или Win32 API не нравятся друг другу, и все это падает.   -  person Matthew Scharley    schedule 18.06.2012
comment
@Gunner: смотрите обновления. У меня работает RegisterClassEx, но все еще не работает :( Я пытаюсь максимально точно следовать этому письму: msdn.microsoft.com/en-us/library/bb384843.aspx   -  person Matthew Scharley    schedule 18.06.2012
comment
Почему вы конвертируете @window_class_struct перед вызовом RegisterClassEx?   -  person Chibueze Opata    schedule 18.06.2012
comment
@ChibiuezeOpata: #to_i - это способ DL получить указатель на структуру. Почему они просто не сделали новую функцию с более подходящим названием, я не знаю. Но вот оно.   -  person Matthew Scharley    schedule 18.06.2012
comment
Определенно возможно, здесь есть работающий пример FFI: www19.atwiki. jp/tmtbnc/m/pages/56.html?guid=on сейчас попробую третью реализацию в FFI...   -  person Matthew Scharley    schedule 18.06.2012


Ответы (1)


Решение: используйте ffi. По какой-то причине это просто не работает в DL (Win32API использует DL под капотом)

Все кредиты указаны здесь, где бы это ни было (я не умею читать по-японски): http://www19.atwiki.jp/tmtbnc/m/pages/56.html

Я предполагаю, что это потому, что DL, похоже, не поддерживает stdcall, но я, честно говоря, недостаточно знаю об этом, чтобы знать.

Решение FFI, которое я использовал, приведено ниже:

class Windows
  def initialize
    hInstance = Win32::GetModuleHandle(nil)

    @window_class = Win32::WNDCLASSEX.new
    @window_class[:style]         = Win32::CS_HREDRAW | Win32::CS_VREDRAW
    @window_class[:lpfnWndProc]   = method(:message_pump)
    @window_class[:hInstance]     = hInstance
    @window_class[:hbrBackground] = Win32::COLOR_WINDOWFRAME
    @window_class[:lpszClassName] = FFI::MemoryPointer.from_string 'ruby-skype'

    @window = Win32::CreateWindowEx(Win32::WS_EX_LEFT, ::FFI::Pointer.new(@window_class.atom), 'ruby-skype', Win32::WS_OVERLAPPEDWINDOW,
                                    0, 0, 0, 0, Win32::NULL, Win32::NULL, hInstance, nil)
  end

  def message_pump(window_handle, message_id, wParam, lParam)
    puts "WM: #{message_id}"
    Win32::DefWindowProc(window_handle, message_id, wParam, lParam)
  end

  module Win32
    extend FFI::Library
    ffi_lib('user32', 'kernel32')
    ffi_convention(:stdcall)

    private

    def self._func(*args)
      attach_function *args
      case args.size
        when 3
          module_function args[0]
        when 4
          module_function args[0]
          alias_method(args[1], args[0])
          module_function args[1]
      end
    end

    public

    ULONG_PTR = FFI::TypeDefs[:ulong]
    LONG_PTR = FFI::TypeDefs[:long]

    ULONG = FFI::TypeDefs[:ulong]
    LONG = FFI::TypeDefs[:long]
    LPVOID = FFI::TypeDefs[:pointer]
    INT = FFI::TypeDefs[:int]
    BYTE = FFI::TypeDefs[:uint16]
    DWORD = FFI::TypeDefs[:ulong]
    BOOL = FFI::TypeDefs[:int]
    UINT = FFI::TypeDefs[:uint]
    POINTER = FFI::TypeDefs[:pointer]
    VOID = FFI::TypeDefs[:void]

    HWND = HICON = HCURSOR = HBRUSH = HINSTANCE = HGDIOBJ =
        HMENU = HMODULE = HANDLE = ULONG_PTR
    LPARAM = LONG_PTR
    WPARAM = ULONG_PTR
    LPCTSTR = LPMSG = LPVOID
    LRESULT = LONG_PTR
    ATOM = BYTE
    NULL = 0

    WNDPROC = callback(:WindowProc, [HWND, UINT, WPARAM, LPARAM], LRESULT)

    class WNDCLASSEX < FFI::Struct
      layout :cbSize, UINT,
             :style, UINT,
             :lpfnWndProc, WNDPROC,
             :cbClsExtra, INT,
             :cbWndExtra, INT,
             :hInstance, HANDLE,
             :hIcon, HICON,
             :hCursor, HCURSOR,
             :hbrBackground, HBRUSH,
             :lpszMenuName, LPCTSTR,
             :lpszClassName, LPCTSTR,
             :hIconSm, HICON

      def initialize(*args)
        super
        self[:cbSize] = self.size
        @atom = 0
      end

      def register_class_ex
        (@atom = Win32::RegisterClassEx(self)) != 0 ? @atom : raise("RegisterClassEx Error")
      end

      def atom
        @atom != 0 ? @atom : register_class_ex
      end
    end # WNDCLASSEX

    class POINT < FFI::Struct
      layout :x, LONG,
             :y, LONG
    end

    class MSG < FFI::Struct
      layout :hwnd, HWND,
             :message, UINT,
             :wParam, WPARAM,
             :lParam, LPARAM,
             :time, DWORD,
             :pt, POINT
    end

    _func(:RegisterWindowMessage, :RegisterWindowMessageA, [LPCTSTR], UINT)
    _func(:GetModuleHandle, :GetModuleHandleA, [LPCTSTR], HMODULE)
    _func(:RegisterClassEx, :RegisterClassExA, [LPVOID], ATOM)
    _func(:CreateWindowEx, :CreateWindowExA, [DWORD, LPCTSTR, LPCTSTR, DWORD, INT, INT, INT, INT, HWND, HMENU, HINSTANCE, LPVOID], HWND)
    _func(:GetMessage, :GetMessageA, [LPMSG, HWND, UINT, UINT], BOOL)
    _func(:TranslateMessage, [LPVOID], BOOL)
    _func(:DispatchMessage, :DispatchMessageA, [LPVOID], BOOL)
    _func(:DefWindowProc, :DefWindowProcA, [HWND, UINT, WPARAM, LPARAM], LRESULT)

    # @!group Predefined WindowHandle's
    #
    # These are WindowHandle's provided by the Win32 API for special purposes.

    # Target for SendMessage(). Broadcast to all windows.
    HWND_BROADCAST = 0xffff
    # Used as a parent in CreateWindow(). Signifies that this should be a message-only window.
    HWND_MESSAGE = -3

    # @!endgroup

    # CreateWindow Use Default Value
    CW_USEDEFAULT = 0x80000000

    COLOR_WINDOW = 5
    COLOR_WINDOWFRAME = 6

    # @!group Class Style contants.

    CS_VREDRAW = 0x0001
    CS_HREDRAW = 0x0002

    # @!group Window Style constants
    #
    # This is only a subset.
    # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms632600.aspx

    WS_BORDER =      0x00800000
    WS_CAPTION =     0x00C00000
    WS_DISABLED =    0x08000000
    WS_OVERLAPPED =  0x00000000
    WS_POPUP =       0x80000000
    WS_SIZEBOX =     0x00040000
    WS_SYSMENU =     0x00080000
    WS_THICKFRAME =  0x00040000
    WS_MAXIMIZEBOX = 0x00010000
    WS_MINIMIZEBOX = 0x00020000
    WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
    WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU

    # @!group Window Extended Style constants
    #
    # This is only a subset.
    # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543.aspx

    WS_EX_LEFT = 0

    # @!endgroup
  end
end
person Matthew Scharley    schedule 18.06.2012
comment
Я предполагаю, что это потому, что DL, похоже, не поддерживает stdcall, но я, честно говоря, недостаточно знаю об этом, чтобы знать. ›› Глядя на исходный DL Win32API, кажется, что он поддерживает stdcall (строка 11): def initialize(dllname, func, import, export = "0", calltype = :stdcall), или, скорее, кажется, что по умолчанию используется stdcall. Возможно, следует использовать другой тип вызова... Другие определения вызовов можно найти здесь: msdn.microsoft.com/en-us/library/984x0h58.aspx - person Sancarn; 08.09.2017