Ruby调用Win32API——EnumDisplaySettings的调用

程序

今天写了一个用Ruby调用Win32接口的程序,基础功能是枚举出当前显示设备支持的分辨率。

先上代码:

require 'Win32API'

class EnumDisplaySetting
  DM_188_FLAG_S_LEN = 188
  DM_188_FLAG_W_LEN = 220

  DM_188_Pointer_FLAG     = "A32S4Ls13A32SL*"
  DM_188_Display_FLAG     = "A32S4L5s5A32SL*"


  # 188 type flag
  DM_DeviceName        = 0
  DM_SpecVersion       = 1
  DM_DriverVersion     = 2
  DM_Size              = 3
  DM_DriverExtra       = 4
  DM_Fields            = 5

  # Printer
  DM_Orientation       = 6
  DM_PaperSize         = 7
  DM_PaperLength       = 8
  DM_PaperWidth        = 9
  DM_Scale             = 10
  DM_Copies            = 11
  DM_DefaultSource     = 12
  DM_PrintQuality      = 13
  DM_Color_p             = 14
  DM_Duplex_p            = 15
  DM_YResolution_p       = 16
  DM_TTOption_p          = 17
  DM_Collate_p           = 18
  DM_FormName_p          = 19
  DM_LogPixels_p         = 20
  DM_BitsPerPel_p        = 21
  DM_PelsWidth_p         = 22
  DM_PelsHeight_p        = 23
  DM_DisplayFlags_p      = 24
  DM_DisplayFrequency_p  = 25

  # Display
  DM_Position_x         = 6  # 显示器的坐标原点X
  DM_Position_y         = 7  # 显示器的坐标原点Y
  DM_DisplayOrientation = 8  # 显示方向:0 - DMDO_DEFAULT; 1 - DMDO_90; 2 - DMDO_180; 3 - DMDO_270; 
  DM_DisplayFixedOutput = 9  # 固定分辨率屏幕显示小分辨率图像时的方式: 0 - DMDFO_DEFAULT; 1 - DMDFO_STRETCH; 2 - DMDFO_CENTER;
  DM_Color_d             = 10  # 彩色打印机的色彩模式:1 - DMCOLOR_MONOCHROME(单色); 2 - DMCOLOR_COLOR(彩色)
  DM_Duplex_d            = 11  # 双面打印,还是单面
  DM_YResolution_d       = 12
  DM_TTOption_d          = 13
  DM_Collate_d           = 14
  DM_FormName_d          = 15
  DM_LogPixels_d         = 16
  DM_BitsPerPel_d        = 17
  DM_PelsWidth_d         = 18
  DM_PelsHeight_d        = 19
  DM_DisplayFlags_d      = 20
  DM_DisplayFrequency_d  = 21




  def initialize(value)
    @info = value.unpack(DM_188_Display_FLAG)
    @model = "Display"
  end

  def Width
    @info[DM_PelsWidth_d]
  end

  def Height
    @info[DM_PelsHeight_d]
  end

  def Value
    @info
  end
end


def get_display_settings
  d_output = ["default", "stretch", "center"]
  eumn = Win32API.new("user32", "EnumDisplaySettings", ['P', 'L', 'P'], 'I')
  info ="\0"*EnumDisplaySetting::DM_188_FLAG_W_LEN
  i=0
  while eumn.call(nil, i, info) == 1 do
      setting = EnumDisplaySetting.new info
      str = ""
      str << "[" << i.to_s << "]"
      str << "width: " <<  setting.Width.to_s << "; "
      str << "height: " << setting.Height.to_s << "; " 
      str << "frequency: " << setting.Value[EnumDisplaySetting::DM_DisplayFrequency_d].to_s << " Hz; "
      oidx = setting.Value[EnumDisplaySetting::DM_DisplayFixedOutput]
      str << "fixed output: " << oidx.to_s << "(" << d_output[oidx] << "); "
    puts str
    i = i+1;    
  end
end

get_display_settings

解释

1. 调用Win32 API的EnumDisplaySettings接口:

    eumn = Win32API.new("user32", "EnumDisplaySettings", ['P', 'L', 'P'], 'I')
    ruby提供了Win32API模块完成对Windows系统函数的调用,相当于得到了一个函数指针;这里需要指定函数所在的DLL、函数名称、形式化参数表、以及返回值;其中形式化参数表是一个字符串数组,数组中的每一项表示这个参数的类型:L - long; P - point; I - int; V -void。例子中的表示这个函数接受类型分别为指针、long、指针的三个参数,返回值是整数。
    eumn.call(nil, i, info)
    调用EnumDisplaySettings函数;

2. API中的struct(结构)的处理

    info ="\0"*EnumDisplaySetting::DM_188_FLAG_W_LEN
    在我的代码中使用的是标准的Win32API函数调用方式,其中有一个DEVMODE的结构块指针作为参数传入API,等待接口函数进行数据填充,但是在标准Ruby中是没有接口定义的,所以这里定义了一个定长字符串,内容是“\0”,把它作为一块内存传给接口函数。所谓struct定义,其实就是理解一块内存的方式,关键点在于这块内存需要多大,这个可以查询MSDN得到,在MSDN上找到EnumDisplaySettings的定义( https://msdn.microsoft.com/en-us/library/windows/desktop/dd162611(v=vs.85).aspx ),而后找到结构DEVMODE的定义就可以推算出内存块的大小了;还有个更简单的办法,在VC++中使用sizeof(DEVMODE)得到大小,这里我得到的是220,也就是说,接口中定义的两个32字节字符串都是使用宽字符(两个字节)的;准备好内存块以后就可以在调用API函数的时候传入了。
    还有一个重点,就是怎么理解这块内存,通过MSDN中的定义我们知道怎么去理解每个字节,但是需要在程序中实现,所以我写了一个EnumDisplaySetting类来做这件事情,这个类接受一个字符串,也就一块内存,而后转换为响应的结构理解。这里用到了Ruby的模板字符处理方式:pack和unpack:
    @info = value.unpack("A32S4L5s5A32SL*")
    pack是按照模板字符串的样式将一个数组打包成字符串,而uppack与之相反,是把pack起来的字符串展开为数组形式。我的代码中的“A32S4L5s5A32SL”就是模板字符串,它代表的语义是:一个32字节的字符串、4个无符号16位整数、5个32位无符号整数、5个有符号16位整数、一个32字节字符串、1个有符号16位整数、其余所有都是32位无符号整数;这是什么?这个其实就是DEVMODE的定义。通过unpack我们就可以把函数填充的struct进行理解了。关于模板字符串的内容参考:http://www.kuqin.com/rubycndocument/man/pack_template_string.html

3. struct中的偏移量

    上面已经把一个DEVMODE结构给变成了数组,接下来就是定义数组的每一个元素是什么了,我在EnumDisplaySetting类型定义了一堆的常量进行数组的索引,或者说是偏移(offset),不过这里有个问题,Windows API函数中大多数struct中都使用了union(联合),C语言的先辈们为了省内存不惜耗费人的大量精力,但是没办法,还得接受。这种情况下,我们定义的结构偏移就不好算了,只能重复定义,按照不同的理解逻辑去解析,也就是说在做unpack的时候,模板字符串需要根据不同情况去适配。
    我上面的处理是一种比较暴力、没有考虑兼容性的处理方式,更加良好的方式是怎么做呢?在DEVMODE中, dmFields (包括)之前的所有域是固定的,应对对结构体字符串(内存)进行两次解析,第一遍得到字符串的长度( dmSize )和填充域(dmFields),而后根据dmFields中指示的结构域内容进行解析。这里dmFields是一个掩码域,后面的数据填充了不同的域,相应的位就会被置为1。

OK,解释完毕!


你可能感兴趣的:(Ruby编程)