【问题标题】:Ruby win32 api interfaceRuby win32 api接口
【发布时间】:2009-07-29 18:45:55
【问题描述】:

我需要在 ruby​​ 中访问 win32 库的一些功能。我在网上找到了关于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”方法调用中指定的参数数量错误而无法运行。有什么方法可以在不借助 C++ 创建外部模块的情况下公开这个 api 函数?

谢谢。

【问题讨论】:

    标签: c++ ruby winapi


    【解决方案1】:

    诀窍是使用“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')
    

    希望这会有所帮助。


    以下适用于带有旧 ruby​​ 1.8.2 的 XP 盒子:
    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
    

    【讨论】:

      【解决方案2】:

      SHAppBarMessage 有两个参数:一个 DWORD 和一个指向 APPBARDATA 的指针,
      所以应该这样声明:

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

      然后调用:

      msg_id = 1
      app_bar_data = "正确初始化的二进制字符串" #应该有 sizeof(APPBARDATA) 字节
      app_bar_msg.call(msg_id, app_bar_data)

      但我不了解 Ruby,所以也许我弄错了......

      【讨论】:

        【解决方案3】:

        我认为您必须调查String#pack 方法才能正确填写您的APPBARDATA struct

        请参阅“Pickaxe”一书 section on Win32 and Ruby(向下滚动到 Win32API 类定义)。

        正如已经发现的那样,您将使用“P”参数,并将正确打包的String(或Strings)传递给函数。

        或者,如果您有一点时间进行调查,您可能想查看 FFI 库,它似乎以一种更友好的方式完成了所有工作。我没有直接经验,但请尝试查看

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-03-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多