Category Archives: Windows

Winsock 发送 HTTP 请求和接收响应

这是一段极其简单的代码,调用 Windows Socket API(Winsock)发送了一段 HTTP 请求,并把相应的 HTTP 响应输出到了标准输出设备上。如果编译时定义了 _UNICODE 或 UNICODE 宏,GetAddrInfo 展开后会成为 GetAddrInfoW,而这是一个新函数,Windows Vista、Windows XP SP2 或 Windows Server 2003 以后的操作系统才支持。实际应用的时候,需要处理各种函数可能导致的错误。HTTP 请求报头也会随着需求改变而不同,这里是向 http://www.66rpg.com:80 发送请求,之后服务器端会发送回正文包含了 66rpg 主页 HTML 文档的 HTTP 响应。BUF_LEN 这个宏指定了每次接收数据用的缓冲区大小,这里使用了一个小数字,方便测试循环的正确性。

#include <stdio.h>
#include <winsock2.h>
#include <Ws2tcpip.h>

#define BUF_LEN 16

void main() {
    WSADATA wsa_data;
    ADDRINFOT *addr_info;
    SOCKET sock;
    char *http_request;
    char buf[BUF_LEN];
    int byte_count;

    WSAStartup(0x0202, &wsa_data);
    GetAddrInfo(TEXT("www.66rpg.com"), TEXT("http"), NULL, &addr_info);

    sock = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol);
    connect(sock, addr_info->ai_addr, addr_info->ai_addrlen);

    http_request =
        "GET http://www.66rpg.com/ HTTP/1.1\n"
        "Host: www.66rpg.com\n"
        "Connection: close\n"
        "\n";
    printf("%s\n", http_request);
    send(sock, http_request, strlen(http_request), 0);

    do {
        byte_count = recv(sock, buf, BUF_LEN, 0);
        fwrite(buf, 1, byte_count, stdout);
    } while (byte_count > 0);
    printf("\n");

    freeaddrinfo(addr_info);
    closesocket(sock);
    WSACleanup();
}

输出:

GET http://www.66rpg.com/ HTTP/1.1
Host: www.66rpg.com
Connection: close


HTTP/1.1 200 OK
Connection: close
Date: Mon, 27 Dec 2010 20:52:33 GMT
Content-Length: 1299
Content-Type: text/html
Content-Location: http://www.66rpg.com/index.htm
Last-Modified: Tue, 28 Sep 2010 22:28:28 GMT
Accept-Ranges: bytes
ETag: "16641a795c5fcb1:107f6"
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET

<html>
<title>66RPG</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<meta name="keywords" content="格斗纹章,柳柳,66RPG,战棋,新战棋,游戏,单机游戏,原
创游戏" />
<head>
<style type="text/css">
<!--
a:link { text-decoration: none;color: blue}
a:active{ text-decoration:blink}
a:hover{ text-decoration:underline; color: red}
a:visited{ text-decoration:none; color:green}
-->
</style>
</head>
<body bgcolor="#DDDDFF">
<p align=center>66RPG</p>
<p align=center>这里只是一个链接汇总,想去哪里,请点击下面的传送门。</p>
<p align=center><a href=http://bbs.66rpg.com/ target="_blank"><font color=red>★
 66RPG 论坛 ★</font></a><br><font color=gray>http://bbs.66rpg.com/</font></p>
<p align=center><a href=http://bbs.66rpg.com/portal.php target="_blank"><font co
lor=red>★ 66RPG 主站(建设中) ★</font></a><br><font color=gray>http://bbs.66r
pg.com/portal.php</font></p>
<p align=center><a href=http://bbs.66rpg.com/thread-153776-1-1.html target="_bla
nk"><font color=red>★ 66RPG 资源帖 ★</font></a><br><font color=gray>http://bbs
.66rpg.com/thread-153776-1-1.html</font></p>
<p align=center><a href=http://www.66rpg.com/GD target="_blank"><font color=red>
★ 格斗纹章 传送门 ★</font></a><br><font color=gray>http://www.66rpg.com/GD</fo
nt></p>
</body>
</html>

Forward Slashes in the Paths Passed to SHFileOperation

Under Windows XP and NTFS, The windows API function SHFileOperation does not seem to be compatible with forward slashes being the directory separators. It also behaves strangely when relative paths are given. According to the Remark section of this MSDN documentation, passing relative paths to this function is not thread-safe. The best way to prevent compatibility issues is to use the good old backward slashes and absolute paths.

Form#submit Failure in Internet Explorer 6

If you are going to submit a form through the anchor tag <a>, watch out for the href attribute. the HTTP response could be rejected by Internet Explorer 6 if you use something like javascript:void(0) as the value of the href attribute. For example, with an anchor tag like this:

<a id="bar" href="javascript:void(0)" onclick="onFoo();">Foo</a>

and with onFoo() handling the form submission (i.e. call submit() on the form object), the HTTP response will not be handled by Internet Explorer 6.

One may suggest replacing javascript:void(0) with #:

<a id="bar" href="#" onclick="onFoo();">Foo</a>

However, this will most likely cause the client to scroll to the top of the page. Instead, use a hash (dummy value) as the anchor name which is not defined anywhere in the document. The client will not scroll up this time.

<a id="bar" href="#doesnotexist" onclick="onFoo();">Foo</a>

Notice that this approach will cause the # sign and the hash to be appended to the URL shown on some clients’ address bars. If you do not like this, add return false; to the onclick event, which prevents the original behavior of the element from functioning (i.e. the hyperlink no longer redirects).

<a id="bar" href="can_be_anything_here" onclick="onFoo(); return false;">Foo</a>

This only works if the onFoo() function does not throw an error.

If the only function you want to implement with the hyperlink is to submit a form (execute onNext()), just get rid of the onclick attribute and go for href.

<a id="bar" href="javascript:onFoo();">Foo</a>

[RMXP] Hangup 异常根除

通用 FSL 信息

脚本说明

灼眼的夏娜曾经发布过一个解决 RMXP 10 秒不刷新就抛出 Hangup 异常,提示“脚本已备份”(这个错误信息是汉化失误)问题的脚本,其原理是创建一个 Ruby 线程,定期调用 Graphics.update,这样就能防止异常的抛出。这个解决方法有一个弊端,就是当 RM 进程阻塞时,Ruby 线程也会停止运行,超过 10 秒后仍如果进程从阻塞中恢复,仍然会抛出 Hangup 异常。我们自然而然地会想:难道不能从根本杜绝 Hangup 的抛出吗?

前不久在写精确获取窗口句柄的时候,发现 RMXP 游戏进程创建了不止一个的 Windows 线程,通过一个一个地结束线程发现,游戏进程最后创建的一个线程恰好就是控制 Hangup 异常抛出的线程。估计 10 秒的计时也是在这个线程内部的用户代码中进行的。那我们简单地、暴力地把这个线程咔嚓掉,不就搞定了吗?我这么做了,结果发现:原来处理程序最终化(即退出时)的也是这个线程,在我把这个线程咔嚓之后,无论是点窗口右上角的关闭按钮,所有脚本都解释完毕,还是在异常抛出到 Ruby 顶层后的正常退出都失效了,只能通过结束进程来关闭游戏。

想了几天,终于想到了解决方法:在程序初始化的时候就暂停掉这个线程,直到游戏需要退出的时候再恢复其用户代码的运行,这样,游戏过程中所有本来应该抛出 Hangup 异常的地方都被鞋盒掉了,这就是这个脚本的暴力之处。但关键就是——怎么在所有需要退出的场合下都进行线程的恢复?点窗口关闭按钮的场合很容易,可以重新定义 exit;所有脚本解释完毕的场合也很容易,可以在 main 脚本下面做相关的处理;而异常抛出到顶层的场合,似乎就不那么么简单了。你说可以在 main 脚本的 begin … rescue … end 那里捕获其它所有异常?那万一在前面定义 Game_Temp 啊 Scene_Title 啊之类的地方发生了异常(也就是解释到 main 脚本之前)怎么办呢?

现在的办法是这样的:游戏运行的时候,RM 会把脚本读取到一个全局数组 $RGSS_SCRIPTS 中,而实际在解释脚本的时候也是在访问这个数组的内容。我们可以越俎代庖,在 RM 解释所有脚本之前(不包括越俎代庖的这个脚本)先把脚本解释完了,然后直接退出程序。换句话说,就是通过 Ruby 的 eval 函数去代替了 RM 的 RGSSEval 函数,解释了所有的脚本。这样一来,在解释脚本过程中所有的异常(当然也不包括越俎代庖脚本中的异常,下面发布是经过调试后的脚本,应该不会有错误了,不过为了保险起见还望大家多多测试)都可以通过 rescue 来捕获到了!当然有利也有弊,这样做了之后,当异常抛出时,提示的消息就只有“eval:#{出错行数}#{出错信息}”,而没有为用户提供脚本的标题,并在脚本编辑器中把光标指向出错的地方了,这是这个脚本的副作用。

当然,发生错误的时候你完全可以暂时屏蔽掉这个脚本,这样就可以把异常后的效果恢复到从前那样了,反正调试的时候你也不一定需要这个脚本。另外,这个脚本在捕获到异常,恢复了线程的运行之后就直接把异常再次抛出给了顶层,所以异常错误信息没有脚本的标题。但其实在循环中,完全可以通过 $RGSS_SCRIPT[subscript] 的第二个元素获取到当前的脚本标题,有心人可以自己改一下,让脚本弹出消息框告诉你这些信息,而不是直接交给 RM 的异常处理机制来处理。

最后,这个脚本完全有可能引起未知的问题,因为被屏蔽掉的线程对我们普通 RM 用户来说还是未知的,万一它还处理了什么其它的东西呢?我们还需要更多的测试。

更新历史

  • 1.2.0827 By 紫苏

    • 更改了配置模块名
    • 更改了 FSL 注释信息
  • 1.2.0805 By 紫苏

    • 脚本开始遵循 FSL
    • 全局范围内改变了脚本结构
  • 1.1.1101 By 紫苏

    • 修正了脚本在 Windows XP 平台下失效的问题
  • 1.0.0927 By 紫苏

    • 初始版本完成

脚本源码

#==============================================================================
# ■  Hangup 异常根除
#    Hangup Exception Eradication
#----------------------------------------------------------------------------
#
#    Hangup 异常是 RMXP 底层引擎内置的一个异常类,游戏进程会在 Graphics.update
#    没有调用超过 10 秒时抛出这个异常。这个脚本使用了 Windows API 暴力地解除
#    了这个限制。
#    使用方法:Hangup 异常根除脚本必须插入到脚本编辑器的最顶端,所有脚本之前,无
#    例外。
#
#----------------------------------------------------------------------------
#
#    更新作者: 紫苏
#    许可协议: FSL -MEE
#    项目版本: 1.2.0827
#    引用网址: 
#    http://bbs.66rpg.com/forum.php?mod=viewthread&tid=134316
#    https://szsu.wordpress.com/2010/08/09/hangup_eradication
#
#----------------------------------------------------------------------------
#
#    - 1.2.0827 By 紫苏
#      * 更改了配置模块名
#      * 更改了 FSL 注释信息
#
#    - 1.2.0805 By 紫苏
#      * 脚本开始遵循 FSL
#      * 全局范围内改变了脚本结构
#
#    - 1.1.1101 By 紫苏
#      * 修正了脚本在 Windows XP 平台下失效的问题
#
#    - 1.0.0927 By 紫苏
#      * 初始版本完成
#
#==============================================================================

$__jmp_here.call if $__jmp_here

#----------------------------------------------------------------------------
# ● 登记 FSL。
#----------------------------------------------------------------------------
$fscript = {} if !$fscript
$fscript['HangupEradication'] = '1.2.0827'

#==============================================================================
# ■ FSL
#------------------------------------------------------------------------------
#  自由RGSS脚本通用公开协议的功能模块。
#==============================================================================

module FSL
  module HangupEradication
    #------------------------------------------------------------------------
    # ● 定义需要的 Windows API。
    #------------------------------------------------------------------------
    OpenThread = Win32API.new('kernel32', 'OpenThread', 'LIL', 'L')
    CloseHandle = Win32API.new('kernel32', 'CloseHandle', 'L', 'I')
    Thread32Next = Win32API.new('kernel32', 'Thread32Next', 'LP', 'I')
    ResumeThread = Win32API.new('kernel32', 'ResumeThread', 'L', 'L')
    SuspendThread = Win32API.new('kernel32', 'SuspendThread', 'L', 'L')
    Thread32First = Win32API.new('kernel32', 'Thread32First', 'LP', 'I')
    GetCurrentProcessId = Win32API.new('kernel32', 'GetCurrentProcessId', 'V', 'L')
    CreateToolhelp32Snapshot = Win32API.new('kernel32', 'CreateToolhelp32Snapshot', 'LL', 'L')
  end
end

#==============================================================================
# ■ HangupEradication
#------------------------------------------------------------------------------
#  处理根除 Hangup 异常的类。
#==============================================================================

class HangupEradication
  include FSL::HangupEradication
  #--------------------------------------------------------------------------
  # ● 初始化对像。
  #--------------------------------------------------------------------------
  def initialize
    @hSnapShot = CreateToolhelp32Snapshot.call(4, 0)
    @hLastThread = OpenThread.call(2, 0, self.getLastThreadId)
    #@hLastThread = OpenThread.call(2097151, 0, threadID)
    ObjectSpace.define_finalizer(self, self.method(:finalize))
  end
  #--------------------------------------------------------------------------
  # ● 获取当前进程创建的最后一个线程的标识。
  #--------------------------------------------------------------------------
  def getLastThreadId
    threadEntry = [28, 0, 0, 0, 0, 0, 0].pack("L*")
    threadId = 0                                          # 线程标识
    found = Thread32First.call(@hSnapShot, threadEntry)   # 准备枚举线程
    while found != 0
      arrThreadEntry = threadEntry.unpack("L*")           # 线程数据解包
      if arrThreadEntry[3] == GetCurrentProcessId.call    # 匹配进程标识
        threadId = arrThreadEntry[2]                      # 记录线程标识
      end
      found = Thread32Next.call(@hSnapShot, threadEntry)  # 下一个线程
    end
    return threadId
  end
  #--------------------------------------------------------------------------
  # ● 根除 Hangup 异常。
  #     2       : “暂停和恢复线程访问权限”代码;
  #     2097151 : “所有可能的访问权限”代码(Windows XP 平台下无效)。
  #--------------------------------------------------------------------------
  def eradicate
    SuspendThread.call(@hLastThread)
  end
  #--------------------------------------------------------------------------
  # ● 恢复 Hangup 异常。
  #--------------------------------------------------------------------------
  def resume
    while ResumeThread.call(@hLastThread) > 1; end        # 恢复最后一个线程
  end
  #--------------------------------------------------------------------------
  # ● 最终化对像。
  #--------------------------------------------------------------------------
  def finalize
    CloseHandle.call(@hSnapShot)
    CloseHandle.call(@hLastThread)
  end
end

hangupEradication = HangupEradication.new
hangupEradication.eradicate

callcc { |$__jmp_here| }                                  # F12 后的跳转标记

#==============================================================================
# ■ 游戏主过程
#------------------------------------------------------------------------------
#  游戏脚本的解释从这个外壳开始。
#==============================================================================

for subscript in 1...$RGSS_SCRIPTS.size
  begin
    eval(Zlib::Inflate.inflate($RGSS_SCRIPTS[subscript][2]))
  rescue Exception => ex
    # 异常发生并抛出给解释器时恢复线程。
    hangupEradication.resume unless defined?(Reset) and ex.class == Reset
    raise ex
  end
end

hangupEradication.resume
exit

已知 BUG 与冲突

  • 脚本在 Windows XP 平台下失效(已修复)

rand_s

大家都知道如何用 C/C++ 中的 rand 配合随机数种子来生成伪随机数。本文介绍另一种不需要每次通过系统时间来设置伪随机序列起始位置的方法(仅针对 Window XP 及其后的 Windows 系列操作系统),那就是使用同样在 <stdlib.h> 中的 rand_s 函数。

有的人说,一看这个函数的名字就知道了,不就是带 CRT 安全增强版的 rand 函数嘛!的确,这个函数是附带 CRT 安全增强的,不过和其它带 CRT 安全增强的函数有所不同——即便你在程序中仍然使用 rand ,编译器也不会有安全警告,因为在 XP 之前的操作系统只能用 rand() 函数。这主要是因为 rand_s 使用的是操作系统来生成加密安全的伪随机数,而不是像 rand 那样使用 srand 生成的种子。所以,使用 rand_s 生成随机数的话就不需要每次都去调用 srand 了。

现在来看看这个函数的声明:

errno_t rand_s( unsigned int* randomValue );

函数的参数是一个整型的指针,指向一个用于接收随机数的整数类型。啥?为啥不直接返回随机数?我个人的猜测是因为 rand_s 直接把这个指针传递给了操作系统用于生成随机数的 API 函数 RtlGenRandom (大家知道 API 函数都是用缓冲区来接收数据),算是写代码的人偷了一个懒吧!

函数成功的话,返回值是 0;否则的话返回错误代码。生成的随机数范围是 0-UINT_MAX。

而最重要的是:如果想使用这个函数,必须在 这个头文件被包含之前定义一个宏:_CRT_RAND_S,也就是如下:

#define _CRT_RAND_S
#include <stdlib.h>

如果没有这样做,在编译的时候就会提示找不到 rand_s 这个标识符!

这也就衍生了一个比较容易被忽略的问题,而这个问题在 MSDN 中并没有提示——如果你的程序包含了一个“包含 的头文件”,那么即便你在之后写上面那两行也是没用的,因为在宏被定义前 就已经被包含了。这个问题最有可能在使用了预编译头的 MFC 程序中出现——很多人没有意识到 MFC 的那两个头文件(<afxwin.h>、<afxext.h>)已经包含了 ,所以在预编译头后面写上这两句是没有作用的。正确的做法应该是在 MFC 的头文件被包含之前(预编译头中)定义 _CRT_RAND_S 这个宏。

下面就用这个函数来实现一个在 min-max 范围中(不包括 max)生成随机数的过程:

unsigned randInt(int min, int max) {
    unsigned u;
    rand_s(&u);
    return (unsigned)((double)u / ((__int64)UINT_MAX + 1) * (max - min) + min);
}