查看: 5440|回复: 13

[原创发布] RMXP多窗体 v2.0

[复制链接]
梦石
0
星屑
13562
在线时间
2753 小时
注册时间
2014-10-4
回帖
705

R考场第七期纪念奖

发表于 2017-11-1 20:21:41 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 SixRC 于 2017-11-2 08:05 编辑

新开一主题 因为改了很多 解决了很多上一版本的问题
这次写了好多天 写到今天终于感觉 几乎是差不多了吧
果然代码还是要慢慢慢慢写 涂涂改改涂涂改改才好发布的
那么 现在由我来介绍 这次版本的优点
1.窗口独立 大小随意 这次是真独立
独立的鼠标键盘消息获取 独立的窗口焦点 独立的关闭
和主窗口没有关联了 你可能会担心窗口失去焦点带来的不刷新的问题
所以第二点
2.支持后台运行 你可以指定后台运行与否
即使失去焦点也会继续刷新
你也可以直接不要显示窗口 当作多线程来用..
3.支持消息传递
虽然只支持四个整数的传递 不过可以传指针 实际上是没什么问题的
4.内存释放优化
这个想了很久 不过大多失败了
不过后来发现简单的做法似乎是最有效的
5.使用方便 很方便了
只要给它运行的代码 告诉他窗口多大放哪里显不显示等等
用完了判断一下是否结束就可以了

具体使用说明请见脚本
[fold=RMXP多窗体 v2.0][pre lang="ruby"]#-----------------------------------------------------------------
#                      ----SixRC   于   2017.11.1
#              基本介绍及基本使用说明
#     这个玩意让你可以随意创建新窗口
#   每个窗口都是由RGSS脚本控制的 每个窗口都是独立的
#   窗口支持后台运行 音乐播放与主窗口不冲突
#   每个窗口可以随意打开随意关闭 没什么特别需要注意的
#   主窗口和建立的窗口可以有消息沟通
#     新建一个窗口,需要新建一个实例 不需要传参
#   然后根据需要对特定属性赋值  之后 x.run开始运行
#   不提供强杀方法 但是提供主窗口和次窗口之间消息传递的方法
#   可以在新建窗口中判断然后退出
#     判断 x.ok? true则表明已经关闭 并会自动清理资源
#   知道为true后就不用管它了
#   下面是所有的属性和简介   所有属性都有默认值 所以不太了解的可以不管
#   x.code   是运行的rgss脚本 可以是脚本或rxdata文件如"Data/123.rxdata"
#   x.from_rxdata             bool 表明脚本是否来自 x.rxdata文件
#   x.show_window             bool 表明是否显示窗口
#   x.title                   窗口标题
#   x.x x.y x.width x.height  指明新建窗口的位置和宽高
#   x.exstyle  x.style        是窗口风格
#   x.class_style             窗口类的风格
#   x.hicon  x.hcursor        窗口图标和鼠标资源的句柄
#   后台运行方法
#   x.back_groundrunning(bool) 不指定默认为true 不调用此方法默认不后台运行
#   下面是消息传递的方法
#   主窗口下 x.send_message x.get_message  如 x.send_message(1,2,3,4)
#   注意最多传递四个值 必须为整数 有更多需要可以传递指针然后你懂的
#   获取的消息是一个四位的数组 传值时没指定的默认为0
#   次窗口下  Ewindow.send_message   Ewindow.get_message 同上
#
#     假如有任何建议和问题或者需要拓展改进的 请联系我
#     https://rpg.blue/home.php?mod=space&uid=2633638
#       不过不保证修改
#     不过你大可以自行修改
#-----------------------------------------------------------------     
class Ewindow
  #---------------------------------------------------------------
  LoadLibrary=Win32API.new('kernel32','LoadLibraryA','p','l')
  GetProcAddress=Win32API.new('kernel32','GetProcAddress','lp','l')
  #---------------------------------------------------------------
  User32=LoadLibrary.call("user32")
  DefWindowProc=GetProcAddress.call(User32,'DefWindowProcA')
  CreateWindow=GetProcAddress.call(User32,'CreateWindowExA')
  #---------------------------------------------------------------
  Kernel32=LoadLibrary.call("kernel32")
  ExitThread=GetProcAddress.call(Kernel32,'ExitThread')
  #---------------------------------------------------------------
  WriteProcessMemory=Win32API.new('kernel32','WriteProcessMemory','llpll','l')
  CreateThread=Win32API.new('kernel32','CreateThread','llppll','l')
  CloseHandle=Win32API.new('kernel32','CloseHandle','l','l')
  RegisterClass=Win32API.new('user32','RegisterClassA','p','l')
  GetClassInfo=Win32API.new('user32','GetClassInfo','lpp','l')
  FreeLibrary=Win32API.new('kernel32','FreeLibrary','l','l')
  CWP=Win32API.new('user32.dll','CallWindowProc','pppll','l')
  WideCharToMultiByte=Win32API.new('kernel32','WideCharToMultiByte','llplplll','l')
  MultiByteToWideChar=Win32API.new('kernel32','MultiByteToWideChar','llplpl','l')
  #---------------------------------------------------------------
  DLL="RGSS103J.dll"
  Exit="Ewindow.dispose"
  Hinstance=Win32API.new('kernel32','GetModuleHandleA','l','l').call(0)
  wndclass="\0"*40
  GetClassInfo.call(Hinstance,"RGSS Player",wndclass)
  Hicon,Hcursor=wndclass.unpack('l*')[5,2]
  #---------------------------------------------------------------
  Dir.mkdir("Ewindow") unless Dir.glob("Ewindow")[0]
  Dir.glob("Ewindow/*.rxdata").each{|i|File.delete(i)}
  Dir.glob("Ewindow/*.dll").each{|i|File.delete(i)}
  #---------------------------------------------------------------
Pre=<<EOF
$E_bitmaps=[]
$E_sprites=[]
def exit
  Ewindow.exit
end
class Bitmap
  alias e_initialize initialize
  def initialize(*args)
    $E_bitmaps.push(e_initialize(*args))
  end
end
class Sprite
  alias e_initialize initialize
  def initialize(*args)
    $E_sprites.push(e_initialize(*args))
  end
end
module Ewindow
  module_function
  CWP=Win32API.new('user32.dll','CallWindowProc','pllll','l')
  CWPlp=Win32API.new('user32.dll','CallWindowProc','plpll','l')
  Get_hwnd=[139,68,36,4,139,64,4,194,16,0].pack('C*')
  Hwnd=CWP.call(Get_hwnd,$E_shared,0,0,0)
  Win32API.new('user32','ShowWindow','ll','l').call(Hwnd,1) if $E_show
  def exit
    Win32API.new('user32','SendMessageA','llll','l').call(Hwnd,0x10,0,0)
  end
  def dispose
    $E_sprites.each{|i|i.dispose}
    $E_bitmaps.each{|i|i.dispose}
    Audio.bgm_stop
    Audio.bgs_stop
    Audio.me_stop
    Audio.se_stop
  end
  Send=[139,124,36,4,131,199,24,139,116,36,8,185,4,0,0,0,252,243,165,195].pack('C*')
  def send_message(s1=0,s2=0,s3=0,s4=0)
    message=[s1,s2,s3,s4].pack('llll')
    CWPlp.call(Send,$E_shared,message,0,0)
  end
  Get=[139,116,36,4,131,198,8,139,124,36,8,185,4,0,0,0,252,243,165,195].pack('C*')
  def get_message
    message=[0].pack('l')*4
    CWPlp.call(Get,$E_shared,message,0,0)
    message.unpack('llll')
  end
end
EOF
  #---------------------------------------------------------------
  attr_accessor:code,:from_rxdata,:show_window,:title,:x,:y,:width,:height
  attr_accessor:exstyle,:style,:class_style,:hicon,:hcursor
  def initialize
    @id=Time.now.to_i.to_s+Time.now.usec.to_s
    @code=""
    @from_rxdata=false
    @show_window=true
    @title="Ewindow"+@id
    @x=@y=@width=@height=0
    @exstyle=0x100
    @style=0xca0000
    @class_style=8
    @hicon=Hicon
    @hcursor=Hcursor
    @shared="\0"*40
    @ok=false
    make_dll
  end
  #------------------------------------------------------------------
  def run
    inject
    from_rxdata ? make_rxdata2 : make_rxdata1
    start_rgss
  end
  #-------------------------------------------------------------------
  def make_dll
    dll=open("Ewindow/#{@id}.dll","wb")
    dll.syswrite(open(DLL,"rb").sysread(File.size(DLL)))
    dll.close
    @dll=LoadLibrary.call("Ewindow/#{@id}.dll")
  end
  #-----------------------------------------------------------------
  def inject
    @window_procedure="\x83\x7C\x24\x08\x10\x74\x0D\x8B\x0D"+
    [@dll+0x12B6C4].pack('l')+"\xB8"+[@dll+0x2EC0].pack('l')+"\xFF\xE0\x68"+
    [Exit].pack('p')+"\xB8"+[@dll+0x3C10].pack('l')+"\xFF\xD0\x50\xC6\x05"+
    [@shared].pack('p')+"\x01\xB8"+[ExitThread].pack('l')+"\xFF\xD0"
    x="\xB9"+[@window_procedure].pack('p')+"\xFF\xE1"
    WriteProcessMemory.call(-1,@dll+0x3190,x,7,0)
    WriteProcessMemory.call(-1,@dll+0xAC25,"\x90"*5,5,0)
    WriteProcessMemory.call(-1,@dll+0x87724,[0x50,0xB8,width,0xBA,height,0x90,0x90].pack('CClClCC'),13,0)
  end
  #-----------------------------------------------------------------
  def make_rxdata1
    script="$E_shared=#{[@shared].pack('p').unpack('l')[0]}\n$E_show=#{@show_window}\n"+Pre+@code
    rxdata=open("Ewindow/#{@id}.rxdata","wb")
    rxdata.syswrite(Marshal.dump([[0,"",Zlib::Deflate.deflate(script)],[2,"",Zlib::Deflate.deflate("exit")]]))
    rxdata.close   
    Graphics.update
  end
  #-----------------------------------------------------------------
  def make_rxdata2
    s=load_data(@code)
    script="$E_shared=#{[@shared].pack('p').unpack('l')[0]}\n$E_show=#{@show_window}\n"+Pre
    s.unshift([0,"",Zlib::Deflate.deflate(script)])
    s.push([1,"",Zlib::Deflate.deflate("exit")])
    rxdata=open("Ewindow/#{@id}.rxdata","wb")
    rxdata.syswrite(Marshal.dump(s))
    rxdata.close
    Graphics.update
  end
  #-----------------------------------------------------------------
  def start_rgss
    wndclass=[@class_style,DefWindowProc,0,0,@dll,@hicon,@hcursor,0,0,@id].pack("lllllllllp")
    rclass=RegisterClass.call(wndclass)
    rclass=[rclass].pack('l').unpack('S')[0]
    @unknown="\0"*0x400
    set=GetProcAddress.call(@dll,'RGSSSetupRTP')
    init=GetProcAddress.call(@dll,'RGSSInitialize')
    gmain=GetProcAddress.call(@dll,'RGSSGameMain')   
    rxdata="Ewindow/#{@id}.rxdata"
    ini=utf8_to_ascii(Dir.pwd+"/Game.ini")
    code="\x31\xD2\x52\x68"+[@dll].pack('l')+"\x52\x52\x52\x52\x68"+
    [@y].pack('l')+"\x68"+[@x].pack('l')+"\x68"+[@style].pack('l')+"\x68"+
    [@title].pack('p')+"\x68"+[rclass].pack('l')+"\x68"+[@exstyle].pack('l')+
    "\xB8"+[CreateWindow].pack('l')+"\xFF\xD0\x89\xC3\xB8"+
    [@shared].pack('p')+"\x89\x58\x04"+"\x68\x00\x04\x00\x00\x68"+
    [@unknown].pack('p')+"\x68"+[ini].pack('p')+"\xB8"+[set].pack('l')+"\xFF\xD0\x83\xC4\x0C\x68"+
    [@dll].pack('l')+"\xB8"+[init].pack('l')+"\xFF\xD0\x58\x68"+[@unknown].pack('p')+
    "\x68"+[rxdata].pack('p')+"\x53\xB8"+[gmain].pack('l')+"\xFF\xD0\xC3"
    CloseHandle.call(CreateThread.call(0,0,code,0,0,0))
  end
  #-----------------------------------------------------------------
  def utf8_to_ascii(str)
    len=MultiByteToWideChar.call(0xFDE9,0,str,-1,0,0)
    str2="\0"*len
    MultiByteToWideChar.call(0xFDE9,0,str,-1,str2,len)
    len=WideCharToMultiByte.call(0,0,str2,-1,0,0,0,0)
    str="\0"*len
    WideCharToMultiByte.call(0,0,str2,-1,str,len,0,0)
    str
  end
  #------------------------------------------------------------------
  def ok?
    return true if @ok
    @ok=@shared.unpack('l')[0]==1 && clean
  end
  #------------------------------------------------------------------
  def clean
    FreeLibrary.call(@dll)
    File.delete("Ewindow/#{@id}.rxdata")
    File.delete("Ewindow/#{@id}.dll")
  end
  #------------------------------------------------------------------
  def back_groundrunning(x=true)
    n= x ? "\0" : "\x46"
    WriteProcessMemory.call(-1,@dll+0x2E48,n,1,0)
  end
  #------------------------------------------------------------------
  Send=[139,124,36,4,131,199,8,139,116,36,8,185,4,0,0,0,252,243,165,195].pack('C*')
  def send_message(s1=0,s2=0,s3=0,s4=0)
    message=[s1,s2,s3,s4].pack('llll')
    CWP.call(Send,@shared,message,0,0)
  end
  #------------------------------------------------------------------
  def get_message
    @shared.unpack("llllllllll")[6,4]
  end
  #-------------------------------------------------------------------
end[/pre][/fold]

然后是一些实现上的细节
模仿了exe加载rgss的方式加载了新的rgss 开始问题很多 不过慢慢都解决了
在RGSSGameMain那里我直接pass掉了控制线程的建立(原来用来检查10s和关闭进程用)
注入了rgss原来的窗口消息处理机制 所以收到 0x10 的VM_CLOSE可以自己控制怎么关掉
后台运行由改掉一个跳转实现 rgss收到 0x1C WM_ACTIVATEAPP  会把一个变量改成1 判断为1则刷新否则继续循环
窗口的建立综合了createwindowex和rgss 在前者确定x和y 后面确定宽高
在加载RSS103J的时候dllmain会建立一个heap freelibrary的时候它又会释放掉 所以只需要free就能解决很大程度上的内存泄漏
位图和精灵则通过dispose 音频暂时没有处理 余下的大约是增加了3M 其中1M是实例带来的 所以实际上只有2M没释放 可以说是很少了
我猜是gdi对象 大概字体啥的 没释放吧
等等等等暂时没想那么多了就这样吧

欢迎提出建议 不过不一定会解决就是了

然后是一个范例:
RMXP多窗体v2.0范例.zip (746.22 KB, 下载次数: 189)
能很好的帮助理解脚本的使用
范例配图
无标题.png

评分

参与人数 4星屑 +100 赞 +4 收起 理由
Tian_Ya + 1 精品文章
浮云半仙 + 1 塞糖
fux2 + 1 塞糖
RyanBern + 100 + 1 精品文章

查看全部评分

梦石
0
星屑
14555
在线时间
22062 小时
注册时间
2010-10-24
回帖
302
发表于 2017-11-1 20:53:27 | 显示全部楼层
本帖最后由 imsy 于 2017-11-1 23:25 编辑

虽然对“当作多线程来用”非常感兴趣但是范例闪退……

无论是双击game.exe、从工程测试还是管理员身份运行,都直接窗口出现一两秒后直接没有了,什么内容都还没显示就消失了……
Ewindow文件夹里有两个dll和两个rxdata。
32位Win7系统,也许是个例吧,楼主可以等等看别的坛友

在main里到处加p找是在哪里出问题的,然后定位在了p1.run这里,这以上的p都p出来了,后面的没出来就闪退了。


楼主更新了主楼的脚本,现在已经可以使用了


点评

解决 结论 ruby调用Win32API 有些api传回短型 不过用l会反一个l回来就炸了 用i不确定 还是l返回来后用pack unpack转换一下  发表于 2017-11-1 23:08
可私聊?  发表于 2017-11-1 21:34
能提供一些详细的信息吗 比方怎么个闪退法 没有任何信息 无法做出判断啊..毕竟只有一台电脑 而这里正常..  发表于 2017-11-1 21:15
噫
回复

使用道具 举报

老黄鸡

梦石
4
星屑
45700
在线时间
7948 小时
注册时间
2009-7-6
回帖
13338

MZ评测员RM创作大赛01组委会开拓者贵宾

发表于 2017-11-2 10:15:44 | 显示全部楼层

我还以为是黑Graphics在多个窗口上画,没想到这么高端,连内存都独立了

点评

明明黑Graphics更加复杂吧!我就单纯的想 重复加载dll 载入rgss 一个rgss对应一个窗口 用完了扔掉 简单易操作!  发表于 2017-11-2 11:28
RGDirect - DirectX驱动的RGSS,点我了解.
(排满,暂停)RM全系列成套系统定制请联系QQ1213237796
不接受对其他插件维护的委托
回复

使用道具 举报

梦石
0
星屑
349
在线时间
31 小时
注册时间
2018-1-9
回帖
17
发表于 2018-2-5 23:52:30 | 显示全部楼层
关于前辈的RMXP多窗体 v2.0
请问能否实现,生成的窗体跟随主窗体的移动而移动
要如何做到呢?

点评

应该可以 我会尝试实现的 不过暂时有点事 完成了会再次通知的  发表于 2018-2-6 13:26
回复

使用道具 举报

梦石
0
星屑
13562
在线时间
2753 小时
注册时间
2014-10-4
回帖
705

R考场第七期纪念奖

 楼主| 发表于 2018-2-6 16:40:42 | 显示全部楼层
本帖最后由 SixRC 于 2018-2-6 16:43 编辑
Tian_Ya 发表于 2018-2-5 23:52
关于前辈的RMXP多窗体 v2.0
请问能否实现,生成的窗体跟随主窗体的移动而移动
要如何做到呢? ...


[pre lang="ruby"]#随主窗口移动而移动
hm = Win32API.new('kernel32','GetModuleHandleA','p','l').call('RGSS103J')
code = [139,68,36,4,139,128,196,182,18,0,139,64,8,194,16,0].pack('C*')
$main_hwnd = Win32API.new('user32.dll','CallWindowProc','pllll','l').call(code,hm,0,0,0)
xy = ' '*16
Win32API.new('user32','GetWindowRect','lp','v').call($main_hwnd,xy)
$main_xy = xy.unpack('ll')
class << Graphics
  GetWindowRect = Win32API.new('user32','GetWindowRect','lp','v')
  MoveWindow = Win32API.new('user32','MoveWindow','llllll','l')
  alias e_update update
  def update
    xy = ' '*16
    GetWindowRect.call($main_hwnd,xy)
    if (xy1 = xy.unpack('ll')) != $main_xy
      GetWindowRect.call(Ewindow::Hwnd,xy)
      xy2 = xy.unpack('llll')
      MoveWindow.call(Ewindow::Hwnd,xy2[0]+xy1[0]-$main_xy[0],
                                   xy2[1]+xy1[1]-$main_xy[1],
                                   xy2[2]-xy2[0],
                                   xy2[3]-xy2[1],0)
      $main_xy = xy1
    end
  e_update
  end
end[/pre]

把它插到子窗口的脚本前面就可以了
哪个或哪些个子窗口需要随着主窗口动而动就插过去
子窗口需要是后台运行的
原理是检测主窗口的位置 他动了子窗口就跟着动 所以每次update会刷新位置
子窗口自己移动不会影响主窗口 测试了下效率和效果蛮好的

点评

前辈厉害了  发表于 2018-2-7 14:37
回复 1 0

使用道具 举报

梦石
0
星屑
349
在线时间
31 小时
注册时间
2018-1-9
回帖
17
发表于 2018-2-7 14:59:44 | 显示全部楼层
SixRC 发表于 2018-2-6 16:40
#随主窗口移动而移动
hm = Win32API.new('kernel32','GetModuleHandleA','p','l').call('RGSS103J')
code ...

同样的窗口x,y坐标在不同分辨率的电脑生成的位置不同

点评

当然不一样了..你需要获取桌面分辨率然后判断一下写个调整的式子什么的  发表于 2018-2-7 17:52
回复

使用道具 举报

梦石
0
星屑
504
在线时间
43 小时
注册时间
2018-2-19
回帖
60
发表于 2018-2-21 20:33:47 | 显示全部楼层
不错,感谢楼主分享。
梦想是谁创造的,梦想是自己创造的。站起来吧。
                      梦想由我们自己来创造。
  无论未来有多么困难,在下都会尽全力走下去。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部