[综合] 一个简单图形界面框架XYGui的设计与实现 (二)

(同步个人博客 http://sxysxy.org/blogs/68 到csdn

封装!

上一节最后用了30多行ruby代码做了一个窗口出来,看起来很是麻烦,如何像XYGui里面一样,一行就能创建那样的窗口呢?

实际上这里面是有很深的套路的,本节开始,讲述XYGui对创建窗口的封装

先解决一些技术上的问题:

已编译成的机器语言与ruby语言互相调用

没错,你没有看错,机器语言调用ruby语言!

高版本的ruby(准确来说是ruby2.0之后)标准库里面就有了一个叫做Fiddle的东西,这个东西很强啊,相当于Dll库+指针库(包括函数指针)+一堆奇怪的东西。。。打开 /lib/XYGui/xy_window.rb,找到一大段注释起来的代码

=begin
    TEMP = []
    def wndproc
        _self = self
        _content = @content
        _responder = @responder

        proc = Class.new(Fiddle::Closure) do
            define_method :call do |hwnd, msg, wparam, lparam|
                return _self.callproc(hwnd, msg, wparam, lparam)
                case msg
                    when WM_PAINT then
                        _responder[:ON_BEGINPAINT].call(_self, nil) if _responder[:ON_BEGINPAINT]
                        return 0
                    when WM_DESTROY then
                        _responder[:ON_DESTROY].call(_self, nil) if _responder[:ON_DESTROY]
                        return 0
                    when WM_COMMAND then
                        event = wparam >> 16                         #hiword
                        id = wparam^(wparam>>16)<<16                 #loword
                        _content[id].responder[:ON_COMMAND].call(_self, {:event => event}) if _content[id] && _content[id].responder[:ON_COMMAND]
                        return 0
                    when WM_SIZE then
                        _responder[:ON_BEFORESIZE].call(_self, {:height => WinAPI.hiword(lparam), :width => WinAPI.loword(lparam)}) if _responder[:ON_BEFORESIZE]
                        return 0
                    when WM_KEYDOWN then
                        _responder[:ON_KEYDOWN].call(_self, {:key => wparam}) if _responder[:ON_KEYDOWN]
                    when WM_CREATE then
                        _responder[:ON_CREATE].call(_self, nil) if _responder[:ON_CREATE]
                        return 0
                    else
                        return WinAPI.call("user32", "DefWindowProc", hwnd, msg, wparam, lparam)
                end
            end
        end.new(Fiddle::TYPE_INT, [Fiddle::TYPE_INT]*4)
        TEMP << proc 
        Fiddle::Function.new(proc, [Fiddle::TYPE_INT]*4, Fiddle::TYPE_INT).to_i
    end
=end

这段代码是相当黑科技的了(嗯但是这点才只是个开始呢),展开了讲ruby底层的机制要很多篇幅的。proc是个继承Fiddle::Closure的匿名类(而且是类也是对象),约定一个叫做”call”的方法,里面就是要做成回调函数的函数内容。

不如用下面这个小例子来帮助理解学习:

require 'fiddle'
include Fiddle
#------ High Power Alert!
proc = Class.new(Closure) do
    define_method :call do |a|
        puts "You called me with arg a:#{a}, and I will return 2*a+1"
        2*a+1
    end
end.new(Fiddle::TYPE_INT, [Fiddle::TYPE_INT])
fptr = Fiddle::Function.new(proc, [Fiddle::TYPE_INT], Fiddle::TYPE_INT)

puts fptr.to_i   #See what does output?
puts fptr.call 2      #call with ruby, returns 5

puts fptr.to_i 输出了什么呢?一个看起来很奇怪的数,其实他是一个地址,如果我们用c语言的函数指针去调用这个地址,也会得到You called me with arg a:2, and I will return a*1的输出,并且真的得到了5这个返回值。xy_window.rb里面就是用了这样的方法,制作了窗口过程WndProc函数。(当然现在是被注释掉了,因为后来为了执行效率就改用c语言了),然后把Fiddle::Function.new(proc, [Fiddle::TYPE_INT]*4, Fiddle::TYPE_INT).to_i 传给了窗口类的lpfnWndProc成员,再然后windows就可以回调我的ruby写的”函数”了。

讲点额外的姿势

额外的,搞XYGui不必要的,因为已经有Fiddle了,而且后来窗口过程用c语言写了,自然也没有用上。下面这”额外的姿势”难度会稍高,但是非常有趣。不感兴趣的同学可以跳过。

**不借助**Fiddle等标准库直接提供的方法,如何让windows调用ruby写的函数呢?这里我提供一个方法: 动态生成机器语言,具体做法请看 这里 (这点我还是开一篇新的博客专门讲)

用ruby来做WndProc

所以,稍微修改一下第一节的小程序。这还是一个窗口,不过用我们自己,用ruby写的窗口过程函数来处理消息了。可以看到,这次可以正常点击窗口右上角退出程序了。为了证明我们确实做到了,我还让窗口在接收鼠标右键点击的时候弹出一个MessageBox。

require 'win32api'

TITLE = "Window_Test"  #窗口标题
CLASS_NAME = "Window_Test_Class"  #窗口类

# API 
def callAPI(dll, procname, *arg)
    Win32API.new(dll, procname, arg.map{|e| e.is_a?(String)? "p":"L"}.join, "L").call *arg
end

#prepare
hInstance = callAPI("kernel32", "GetModuleHandle", 0)
DefProc = callAPI("kernel32", "GetProcAddress", callAPI("kernel32", "GetModuleHandle", "user32"), "DefWindowProcA")
#wndproc
require 'fiddle'
include Fiddle
proc = Class.new(Closure) do
    define_method :call do |hWnd, uMsg, wParam, lParam|
        case uMsg
            when 2 then #WM_DESTROY 0x0002
                callAPI("user32", "PostQuitMessage", 0)
                return 0
            when 0x0204 then #WM_RBUTTONDOWN 0x0204
                callAPI("user32", "MessageBox", 0, "Right Button Clicked!", "Tip", 0)
                return 0
            else
                return callAPI("user32", "DefWindowProc", hWnd, uMsg, wParam, lParam)
        end
    end
end.new(TYPE_INT, [TYPE_INT]*4)
procAddr = Function.new(proc, [TYPE_INT]*4, TYPE_INT).to_i

#window class
wndclass = [0, procAddr, #lpfnWndProc
    0, 0, hInstance,
    callAPI("user32", "LoadIcon", hInstance, 32512), #Icon
    callAPI("user32", "LoadCursor", 0, 32512), #IDC_ARROW
    6, #COLOR_WINDOW_FRAME
    0, CLASS_NAME].pack("LLLLLLLLLp")

if callAPI("user32", "RegisterClass", wndclass) == 0
    callAPI("user32", "MessageBox", 0, "Window class Error", "Error", 0)
    exit 
end

hWnd = callAPI("user32", "CreateWindowEx", 0, CLASS_NAME, TITLE,
        0xcf0000 | 0x10000000, #WS_OVERLAPPEDWINDOW | WS_VISIBLE
        100, 100, 400, 300, 0, 0, hInstance, 0)

msg = "\0"*28 #MSG
while callAPI("user32", "GetMessage", msg, 0, 0, 0) != 0  #here, != 0
    callAPI("user32", "TranslateMessage", msg)
    callAPI("user32", "DispatchMessage", msg)
end

至此,用ruby语言制作窗口过程的技术问题便得到解决了。如果泥自己像做一个gui库,用ruby来处理窗口过程,那这一节的内容可一定要好好看看。XYGui最开始就是用ruby处理窗口过程的…

下一节我会讲用c语言做这个窗口过程(一般来说,是为了执行效率),并讲解如何把ruby的对象与窗口关联起来,这将是”封装”的最关键一步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值