扫描二维码关注官方公众号
返回列表
+ 发新帖
查看: 140|回复: 0

[转载发布] [XP/VX] 精确获取窗口句柄(解决部分 API 脚本潜在问题)

[复制链接]
累计送礼:
0 个
累计收礼:
0 个
  • TA的每日心情
    开心
    昨天 18:01
  • 签到天数: 114 天

    连续签到: 4 天

    [LV.6]常住居民II

    2339

    主题

    404

    回帖

    1万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    VIP
    6
    卡币
    10635
    OK点
    16
    推广点
    0
    同能卷
    0
    积分
    13406

    灌水之王

    发表于 2024-4-19 18:17:05 | 显示全部楼层 |阅读模式
    当下 6R 有很多调用 API 的脚本都需要获取 RM 游戏主窗口的句柄,从而进行各种窗口相关的操作。这些脚本无外乎就通过以下三种方法来获取:
      通过 GetPrivateProfileString 函数获取 Game.ini 中的窗口标题,然后调用 FindWindow 获取匹配该标题和 RGSS Player 类名的窗口句柄 通过 GetActiveWindow 函数获取活动状态的、且属于调用线程的窗口句柄 通过 GetForegroundWindow 函数获取系统中的前台(拥有焦点的)窗口

    然而无论哪种方法都有潜在的问题——
    首先是第一种。前天精灵在群里提到了一个关于窗口句柄的问题——如果当窗口的标题随时在变动,那么目前通过 FindWindow 获取窗口的脚本都将无法正确获取窗口句柄(因为传递给 FindWindow 的字符串必须得是板上钉钉的,不可匹配部分字符串),导致整个脚本瘫痪。那么我们自然而然地就会想到,不匹配窗口标题,仅匹配窗口类名可以吗?是的,只要传递一个 NULL 给 FindWindow 的第二个参数,就可以仅匹配窗口类名。但是有没有想过,当你开着多个 RMXP 的游戏窗口,这些窗口的窗口类名都是 RGSS Player,那岂不是其中的任意一个窗口都能匹配?再退一步想,即使我们的窗口标题是固定的,难道我们不能打开多个相同的 Game.exe 应用程序实例?这些相同程序的不同进程所创建的窗口标题和类名都是一样的,其中的任意一个窗口也都能匹配,只要我们在窗口创建后切换到另一个匹配的窗口(FindWindow 在 Z 次序中从高到低搜索窗口,这时刚切换到的窗口在 Z 次序中的排位高于我们刚创建的窗口,所以 FindWindow 第一个找到的是这个刚切换到的窗口),我们最后获取到的句柄就变成了这个匹配的窗口的句柄,而不是我们预期的刚创建的窗口句柄……

    然后是第二种。GetActiveWindow 的问题是:当这个函数被调用时,如果调用线程(也就是 RM 的主线程)所创建的 RM 的窗口并不是活动状态的(不在前台),那么它就会返回 0。所以,只要我们在 RM 窗口出现之后,这个函数调用之前立刻切换到其它窗口,使用这个函数的 API 脚本也无法正确获取句柄。

    最后是第三种。GetForegroundWindow 获取的是目前系统中处在前台的窗口,它的问题也是一目了然——只要在调用之前切换到其它窗口,那么它获取到的句柄就是你刚切换到的窗口的句柄了……

    这几个问题一般是出现在 RM 初始化时获取窗口句柄的情况下,因为在初始化开始到调用 FindWindow 等函数之间,你有时间可以切换到其他窗口。如果是在平时调用,比如截图存档脚本,从你按下“存档”到调用 FindWindow 获取窗口句柄的这个时间间距实在是太短了,普通人的动作没有那么快,能赶在调用之前切换到其他窗口……所以下面这个脚本主要是解决了在 RM 初始化时获取句柄的潜在问题。

    其实这个问题我很早就注意到了,但是由于其严重性不高,所以一直懒得提出。正好赶上精灵提出这个问题,觉得严重性猛地提升了不少,于是就把这个脚本发出来了……
    1. #==============================================================================# ■ Kernel#------------------------------------------------------------------------------#  该模块中定义了可供所有类使用的方法。Object 类中包含了该模块。#==============================================================================module Kernel  #--------------------------------------------------------------------------  # ● 需要的 Windows API 函数  #--------------------------------------------------------------------------  GetWindowThreadProcessId = Win32API.new("user32", "GetWindowThreadProcessId", "LP", "L")  GetWindow = Win32API.new("user32", "GetWindow", "LL", "L")  GetClassName = Win32API.new("user32", "GetClassName", "LPL", "L")  GetCurrentThreadId = Win32API.new("kernel32", "GetCurrentThreadId", "V", "L")  GetForegroundWindow = Win32API.new("user32", "GetForegroundWindow", "V", "L")  #--------------------------------------------------------------------------  # ● 获取窗口句柄  #--------------------------------------------------------------------------  def get_hWnd    # 获取调用线程(RM 的主线程)的进程标识    threadID = GetCurrentThreadId.call    # 获取 Z 次序中最靠前的窗口    hWnd = GetWindow.call(GetForegroundWindow.call, 0)    # 枚举所有窗口    while hWnd != 0      # 如果创建该窗口的线程标识匹配本线程标识      if threadID == GetWindowThreadProcessId.call(hWnd, 0)        # 分配一个 11 个字节的缓冲区        className = " " * 11        # 获取该窗口的类名        GetClassName.call(hWnd, className, 12)        # 如果匹配 RGSS Player 则跳出循环        break if className == "RGSS Player"      end      # 获取下一个窗口      hWnd = GetWindow.call(hWnd, 2)    end    return hWnd  endend复制代码
    复制代码
    窗口的标题名和类名都不能用来表示一个窗口的唯一性,那么窗口的什么才是唯一的呢?主要有三个:窗口的应用程序实例句柄 hInstance、创建窗口的进程标识 processID、创建窗口的线程标识 threadID。这个脚本用的就是线程的标识,其原理是:枚举桌面上的所有窗口,看看创建它的线程是否匹配当前 RM 的主线程,并且其窗口类名是 RGSS Player(因为 RM 的主线程不只创建了 RM 游戏主窗口这一个窗口),如果是,那么当前获取到的窗口句柄就是我们预期的句柄了~
    用法:直接在任意处调用 get_hWnd 就能获取到当前 RM 窗口的句柄~


    应用实例,美兽更改窗体分辨率真实版
    这个脚本就是在 RM 初始化时调用 FindWindow 来获取窗口句柄。你可以显式地去引发其潜在的问题:先打开一个 Game.exe,窗口分辨率被增加到 800 × 600;再打开一个 Game.exe,窗口出现后立刻切换到 800 × 600 的窗口,就会发现该窗口分辨率又被增加到了 960 × 720 了……
    注释掉这个脚本中如下的绿色部分,再添加红色部分,就能解决这个问题:

    if $myfirst == nil

      $myfirst = 'myGod'
      宽度=800
      高度=600
    #  游戏ini名=".\\Game.ini"
    #  val = "\0"*256
    #  gps = Win32API.new('kernel32', 'GetPrivateProfileString','pppplp', 'l')
    #  gps.call("Game", "Title", "", val, 256, 游戏ini名)
    #  val.delete!("\0")
    #  title = val
    #  fw = Win32API.new('user32', 'FindWindow', 'pp', 'i')
    #  hWnd = fw.call("RGSS Player", title)


      hWnd = get_hWnd

      swp = Win32API.new('user32', 'SetWindowPos', 'lliiiii', 'i')

      pointwds = [0,0,0,0].pack('llll')
      pointcet = [0, 0].pack('ll')

      wdsrect = Win32API.new('user32.dll', 'GetWindowRect', 'lp', 'l')
      client_screen = Win32API.new("user32", "ClientToScreen", 'ip', 'i')

      wdsrect.call(hWnd,pointwds)
      client_screen.call(hWnd, pointcet)

      wds = pointwds.unpack('llll')
      cet = pointcet.unpack('ll')

      addw =  wds[2] - wds[0] - 640
      addh =  wds[3] - wds[1] - 480

      x = wds[0] - (宽度 - 640) / 2
      y = wds[1] - (高度 - 480) / 2

      swp.call(hWnd, 0, x, y, 宽度 + addw, 高度 + addh, 0x20)

    end
                 本帖来自P1论坛作者紫苏,因Project1站服务器在国外有时候访问缓慢不方便作者交流学习,经联系P1站长fux2同意署名转载一起分享游戏制作经验,共同为国内独立游戏作者共同创造良好交流环境,原文地址:https://rpg.blue/forum.php?mod=viewthread&tid=133018  若有侵权,发帖作者可联系底部站长QQ在线咨询功能删除,谢谢。

    送花

    喜欢

    晕倒

    路过

    雷人

    得意

    丢鸡蛋

    高兴
    天天去同能,天天有童年!
    回复 送礼论坛版权

    使用道具 举报

    文明发言,和谐互动
    文明发言,和谐互动
    高级模式
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    关闭

    幸运抽奖

    社区每日抽奖来袭,快来试试你是欧皇还是非酋~

    立即查看

    聊天机器人
    Loading...

    QQ|Archiver|手机版|小黑屋|同能RPG制作大师 ( 沪ICP备12027754号-3 )

    GMT+8, 2025-3-15 15:30 , Processed in 0.151033 second(s), 57 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表