Project RC

一种标准阿西的设计与实现。

记一次关于 C++ 多线程写文件操作的错误修复

创建于
分类:Dev
标签:C++Multithreading

前段时间酷 Q HTTP API 插件有用户报错说,连续请求两次 API 发送同一张图(通过 URL),会发生第二张图发不出去的问题。想了一下,问题很显然,因为插件在处理第一个请求的时候,开始下载图片,以 URL 的 md5 作为文件名,然后第二个请求到的时候(多线程处理),图还没有下完,但是同名文件已经存在了,插件以为是缓存,于是试图直接发送下到一半的文件,自然是发不出去了。

最简单的修复方法是让插件先把图片下载到一个临时文件,下载完成后再改名。但是这样的话,两个请求都会造成文件下载,但实际上这是不必要的,因为它们实际发的是同一张图。那么自然要想到让第二个请求发现已经有请求在发送同一张图的时候,等待其完成,然后直接发送已缓存的图。

一开始想到用一个 std::map<std::string, std::mutex> 来按文件名保存互斥锁,每个线程首先算出文件名,然后尝试获取文件名对应的锁。这个方法看起来虽然好像可以,但实际上仔细一想发现了问题,std::map 本身就不是线程安全的数据结构,两个线程很可能在创建和删除锁的时候发生冲突,而如果再给 map 加个锁,逻辑就开始有些混乱了,有发生死锁的风险。

于是去 Stack Overflow 搜了一圈,发现了 std::condition_variable 这么个东西,通过 waitnotify_onenotify_all 方法来实现让线程等待和唤醒等待中的线程,基本上满足了我的需求。

最终修复代码如下:

static unordered_set<string> files_in_process;
static mutex files_in_process_mutex;
static condition_variable cv;

if (!filename.empty() && make_file != nullptr) {
    unique_lock<mutex> lk(files_in_process_mutex);
    // wait until there is no other thread processing the same file
    cv.wait(lk, [=] {
        return files_in_process.find(filename) == files_in_process.cend();
    });
    files_in_process.insert(filename);
    lk.unlock();

    // we are now sure that only our current thread is processing the file
    if (make_file()) {
        // succeeded
        segment.data["file"] = filename;
    }

    // ok, we can let other threads play
    lk.lock();
    files_in_process.erase(filename);
    lk.unlock();
    cv.notify_all();
}

使用一个 std::unordered_set<std::string> 保存了当前正在处理的请求对应的文件名,同时对应有一个互斥锁。

第一部分首先获取互斥锁,然后调用 std::condition_variablewait 方法,这个方法会首先判断传入的谓词(第二个参数)是否满足(在拥有锁的情况下调用),如果不满足,释放锁,然后等待(阻塞当前线程)。在第一个 API 调用时,显然并没有其它线程正在处理同名文件,所以它会往下执行,把当前文件名放到集合,然后释放锁(这是很重要的,在实际下载文件开始之前需要释放锁,否则下载别的文件的线程也得等这个线程执行完才能执行)。

第二部分就是下载文件操作了,这里之所以不用拥有锁,是因为我们在第一部分已经确定没有其它线程正在操作这个文件,而一旦文件名插入到集合中(拥有锁的时候插入的),别的下载同名文件的线程就会在 wait 方法里阻塞了。因此第二部分我们可以确定每个文件只有一个线程在下载。

第三部分就是下载完成后从文件名集合中删除当前文件名了,在拥有锁的情况下删除,然后立即释放锁,接着调用 notify_all,还记得其它需要发送同名文件的线程都在 wait 中等待吗,调用 notify_all 之后它们会被唤醒,然后依次获取锁、判断谓词是否满足,当第二个发送同一图片的线程执行到第二部分的时候,它会发现图片已经缓存了,于是直接发送缓存了的图片。

至此完美解决了一开始的问题。

记一次关于 C++ 多线程写文件操作的错误修复

真正零门槛:使用 IFTTT 和酷 Q 制作你的专属 QQ 通知机器人(不需要写任何代码!)

创建于
分类:Fun
标签:IFTTT酷QQQ Bot聊天机器人

如标题所说,本教程是真正的零基础教程,你不需要会编程,也不需要对聊天机器人的原理有什么深刻的理解,你只需要一台装有 Windows 7 或更新版的电脑,然后照着教程做完,就可以做出一个可以说是有一定实用性的 QQ 通知机器人,比如通过 QQ 推送特定用户的新微博,推送 Tumblr 账号的更新(你懂的),定时推送天气预报或其它提醒。

虽然本教程的步骤看起来有点多,但每个步骤都会尽量写得简单易懂,并在适当的地方配上截图,所以照着教程操作的话,基本上不会有太大困难,请放心^_^

0. 一些简介

对于本文用到的一些服务、技术,你有可能会有些陌生,不要紧,这一节会首先给出一些基本的介绍,在后面用到的时候,也会一步一步教你使用。

0.1 什么是 IFTTT?

IFTTT 是一个国外的网络服务平台(官网),它提供的服务是通过接入网络上的各种其它平台的服务来触发一定的自定义操作,例如在发送微博时自动同步发送到 Twitter、当 iOS 上新增联系人时自动同步到 Google 联系人账号、每天早上 8 点发送当天的天气预报到社交网络等。可以看出它的服务可以概括为「当一件事情发生时,触发另一件事情」,这也正是 IFTTT 这个名字以及他们的口号「if this then that」的由来。

0.2 什么是酷 Q?

由于 QQ 官方并没有直接支持普通用户制作 QQ 机器人,因此要实现 QQ 机器人,通常需要使用一些方式来登录 QQ 机器人的账号,从而可以接受和自动发送消息。酷 Q 正是一种可以登录 QQ 号的软件(官网),并且向软件开发者提供一系列接收和发送消息的接口,不过在本教程中,你不需要知道这些接口是怎么工作的。

0.3 什么是内网穿透?

「内网穿透」是一种将没有公网 IP 的设备上的网络服务共享到公网的技术,简单来说,就是可以让互联网上其它设备访问你的电脑上的某个服务,本教程后面会使用内网穿透来将酷 Q 的接口暴露到公网,从而可以让 IFTTT 来触发,这听起来可能有点复杂,不过你并不需要知道这具体是怎么工作的,因为工具使用起来其实非常简单。

1. 安装酷 Q 和 HTTP API 插件

你可能要问了,刚刚不是就说了酷 Q 吗,这个 HTTP API 插件又是啥?不要慌,理论上讲,它是一个「把酷 Q 提供的接口转换到 HTTP」的插件,但这跟我们这个教程的主题没有什么主要关系,只需要简单安装一下就可以。

首先到酷 Q 官方论坛下载酷 Q Air,网址 https://cqp.cc/t/23253,我们这里选择「酷 Q Air 图灵版」:

你可以自行查看一下版本区别(实际上区别不大)。

下载之后直接解压:

CQA.exe 就是酷 Q 的主程序,先不急着运行,我们先前往 https://github.com/richardchien/coolq-http-api/releases 下载最新版本(比如目前最新版为图中的 v2.1.2)的 io.github.richardchien.coolqhttpapi.cpk 文件:

这就是标题中所说的「HTTP API 插件」,下载好之后直接把它放到刚刚酷 Q 的 app 文件夹。

接着还不要急,再前往 https://www.visualstudio.com/zh-hans/downloads/?q=redist 下载安装 VC++ 2017 运行库,注意一定要选择「x86」:

装好之后,就可以运行 CQA.exe 了,首次启动会有如下提示:

点确定即可到登录界面:

输入你要作为 QQ 机器人的 QQ 号和密码,登录,如果只是先尝试一下,还没有专门的号,就随便用一个小号登录吧,或者去 https://ssl.zc.qq.com/chs/index.html 注册一个新的 QQ 号。

登录之后可以看到桌面上有一个悬浮窗:

并且酷 Q 提示你进行一个互动式教程,你可以跟着教程玩一次,也可以选择「下次再说」。

然后右击悬浮窗,选择「应用」-「应用管理」进入插件管理界面:

如果你不想玩那个互动式教程,那么在这里最好选择「互动式教程」,并选择「停用」。然后选择「HTTP API」,并选择而「启用」,酷 Q 会提示你一些关于语音模块和插件权限的问题,全选「是」即可。完成之后 HTTP API 插件即启用了,可以右击悬浮窗,选择「日志」,查看是否有输出「HTTP API 插件已启用」。

到目前为止,酷 Q 这边的操作已经全部做完,现在你的 QQ 机器人已经在线了,不过它还没有功能,你可以先在浏览器中打开 http://127.0.0.1:5700/send_private_msg?user_id={你的QQ号}&message=主人你好 来测试酷 Q 及 HTTP API 插件是否正常运行,其中 {你的QQ号} 要换成你的 QQ 号。如果一切 OK,你的机器人会给你发送一条消息「主人你好」。

2. 配置内网穿透

这一步对于没有什么计算机基础知识的同学来说可能看起来非常可怕,但其实本质上是非常简单的。

我们这里使用一个叫「魔法隧道」的服务来配置内网穿透,首先去 官网 注册一个账号,然后进入「控制中心」:

我这里已经开了一个隧道,所以下面可以看到已经有一个项。你需要点击上面的加号来创建隧道,「地域」随便选,「隧道名称」随便填,「隧道协议」选「http+https」,「IP 地址和端口」填 127.0.0.1:5700,二级域名不用填,然后点「立即创建」即可创建成功。

创建完成之后,去 http://www.mofasuidao.cn/rest/page/download 下载魔法隧道的客户端,如果你不知道你的系统是多少位,就下「32 位」即可。

下好之后直接就是一个 mofasuidao.exe,先不急着点开,先打开记事本,然后粘贴下面的代码:

CreateObject("WScript.Shell").Run "C:\Users\richard\Desktop\mofasuidao.exe xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",1

这里的 C:\Users\richard\Desktop\mofasuidao.exe 要换成你刚刚下载的 mofasuidao.exe 所在的路径,然后 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 替换成你创建的魔法隧道给出的 token:

然后把这个文件随便保存到一个地方,命名为 mofasuidao.vbs,注意后缀是 .vbs,记事本默认可能是 .txt 需要改一下:

上面那段代码是用来快速启动魔法隧道客户端的,你不需要知道它具体在干什么。现在双击 mofasuidao.vbs 会打开一个黑的命令行窗口,稍等片刻当你看到窗口上显示有「suidaoStat online」时,即表示启动成功:

这个时候,找到魔法隧道给你的隧道域名:

在后面加上 /send_private_msg?user_id={你的QQ号}&message=主人你好,然后到浏览器打开,就像前面的 http://127.0.0.1:5700/send_private_msg?user_id={你的QQ号}&message=主人你好 一样,只不过变成了 http://xxxx.mofasuidao.cn/send_private_msg?user_id={你的QQ号}&message=主人你好

如果还是一切 OK,此时你的机器人应该会又给你发了一条消息。

至此内网穿透就配置好了,那个命令行窗口关闭即可关闭魔法隧道客户端,再次打开也是运行 mofasuidao.vbs

3. 配置 IFTTT

前往 IFTTT 官网 https://ifttt.com/ 注册一个账号并登录,进入账号的「Settings」页面:

往下滚动修改时区为北京:

然后前往 Maker Webhooks,点击「Connect」,之后可以看到如下页面:

右上角有「Settings」表示已经成功开启 Maker Webhooks。

然后进入「My Applets」页面:

我们马上就要开始创建核心功能了。

下面提供了两个示例玩法,你可以任选或都选,当然这两个不是所有的可能,更多有趣的玩法还要你自己去发掘。

3.1 自动推送每天的天气预报

首先进入「Services」页面,找到并进入「Weather Underground」,点右上角「Settings」进入服务设置页面:

然后点「Edit connection」,在跳转到的页面中搜索(用拼音)并选择你需要关注天气的城市,然后点「Connect」。

然后回到「My Applets」,点击「New Applet」,再点「this」:

搜索并选择「Weather Underground」服务:

然后选择「Today's weather report」,并设定推送时间:

然后点击「Create trigger」。

继续点「that」,搜索并选择「Maker Webhooks」:

选择「Make a web request」,「URL」填写 http://xxxx.mofasuidao.cn/send_private_msg,这里的 xxxx.mofasuidao.cn 替换成魔法隧道给你的隧道域名,「Method」选「POST」,「Content Type」选「application/x-www-form-urlencoded」,「Body」填:

user_id={你的QQ号}&message=<<<今天最高气温 {{HighTempCelsius}}°C,最低气温 {{LowTempCelsius}}°C,现在气温 {{CurrentTempCelsius}}°C>>>

这里 {你的QQ号} 换成你要接收推送的 QQ 号(必须是机器人号的好友),<<<>>> 中的内容也就是推送内容,可以自由修改,点击「Add ingredient」可以添加你想要的其它天气信息,注意所有内容一定都要在 <<<>>> 中间,且内容中不能出现其它尖括号。填写完如下:

最后点「Create action」、「Finish」大功告成,可以等着在你指定的时间收到推送啦!不过注意这个推送可能会有些许延迟,属于正常现象。

3.2 新微博 QQ 提醒

首先在浏览器打开要通知到 QQ 的微博用户的主页,我们以薛之谦的微博为例:http://weibo.com/xuezhiqian,然后按 F12 进入「开发者工具」,进入「控制台」标签,在下方的文本框中粘贴如下代码并回车:

/uid=(\d+)/. exec(document.querySelector('.opt_box .btn_bed').getAttribute('action-data'))[1]

可以看到输出了一串数字:

将这个数字复制出来,先暂时保存起来(或者页面先不要关)。

然后点 IFTTT 的「New Applet」,点「this」,找到并选择「RSS Feed」:

然后点击「Connect」(如果出现的话),接着选择「New feed item」,在「Feed URL」中填写:

https://api.prprpr.me/weibo/rss/{微博博主的uid}

其中 {微博博主的uid} 换成刚刚在微博用户主页输出的那串数字:

然后点「Create trigger」创建。

继续点「that」,搜索并选择「Maker Webhooks」:

选择「Make a web request」,「URL」填写 http://xxxx.mofasuidao.cn/send_private_msg,这里的 xxxx.mofasuidao.cn 替换成魔法隧道给你的隧道域名,「Method」选「POST」,「Content Type」选「application/x-www-form-urlencoded」,「Body」填:

user_id={你的QQ号}&message=<<<薛之谦又发段子了,去微博看看吧 {{EntryUrl}}>>>

这里 {你的QQ号} 换成你要接收推送的 QQ 号(必须是机器人号的好友),<<<>>> 中的内容也就是推送内容,可以自由修改,不过注意所有内容一定都要在 <<<>>> 中间,且内容中不能出现其它尖括号。填写完如下:

最后点「Create action」、「Finish」大功告成,等薛之谦发微博之后,你在 QQ 上就可以收到机器人的推送了!

不过,这个玩法的延迟比较高,在发出微博后,大约需要等 5 到 10 分钟左右才会收到推送,不过也不错啦~

4. 特别提醒

需要注意的一点是,IFTTT 的这些推送生效的前提条件是,酷 Q 和魔法隧道客户端都一直在运行,假设你设置了上午 7 点推送,要能够正确收到推送,这个时间点,你的酷 Q 和 魔法隧道必须是运行着的。

通常情况下,建议使用一台可以长期开机不关闭的电脑来运行酷 Q 和魔法隧道。当然你也可以把酷 Q 和魔法隧道添加到开机启动,对于酷 Q,首先右击悬浮窗,在你的账号那个选项中,勾选「快速登录」,提示是否创建快捷方式,选「是」,然后将这个快捷方式加入开机启动,对于魔法隧道,直接将 mofasuidao.vbs 加入开机启动。

5. 锦上添花

上面的示例全都是发送推送给自己,当然也是可以发送到 QQ 群的,只需要把「Make a web request」中填写的「URL」的 send_private_msg 换成 send_group_msg,并把「Body」中的 user_id={你的QQ号} 换成 group_id={群号} 就行了。

另外,IFTTT 所支持的大部分服务都是国外的,例如 Facebook、Twitter、Instagram 等,如果你也玩这些社交网站的话,那么可以搞出的功能就更多了,总之更多功能还看你的创造力。

最后,如果你有一些编程基础的话,甚至可以在「Make a web request」中不直接调用发送消息的接口,而是 POST 代码到一些在线运行代码的网站(例如 https://compiler.run/),在这个代码中再去执行更复杂的逻辑,然后再 POST 到发消息的接口,这样自由度将会更大(当然你也可以让 IFTTT 直接 POST 到你自己的服务端)。

总之将 IFTTT 和酷 Q 搭配起来会是一种非常有趣的玩法,可以创造很多可能性,而且整个过程不需要自己配置服务器运行环境。

如果觉得光推送通知太无聊的话,也可以启用酷 Q 的图灵机器人插件,还记得当时我们下载的是图灵版吗,在「应用管理」中找到「图灵机器人」,启用,然后点「菜单」-「设置」,根据他的提示申请一个图灵机器人的 API Key 并填入,就可以获得聊天自动回复功能了。另外,图灵机器人的网站上也提供了一些有趣的自定义的途径,同样可以发挥你的创造力来做不一样的聊天机器人。

真正零门槛:使用 IFTTT 和酷 Q 制作你的专属 QQ 通知机器人(不需要写任何代码!)

使用 PyInstaller 将 Python 程序打包成无依赖的可执行文件

创建于
分类:Dev
标签:PythonPyInstaller

本文以 Windows 为例,其它系统上应该坑会更少一点。

安装

首先一点,截至目前(17 年 6 月),PyInstaller 还不兼容 Python 3.6,根据官方的说明,目前支持的版本是 2.7 和 3.3 到 3.5。当你看到这篇文章时,可能已经支持更新版本了,建议查看官方 repo 的 README:pyinstaller/pyinstaller

用 pip 安装:

pip install pyinstaller

在 Windows 上,pip 会同时安装 pypiwin32 包,这是 PyInstaller 在 Windows 上的一个依赖。

这里就有了第一个坑,根据 Requirements

It requires either the PyWin32 or pypiwin32 Python extension for Windows.

理论上应该默认情况下安装了 pypiwin32 就可以运行的,但其实并不行,直接用的话会报错 ImportError: DLL load failed: The specified module could not be found.

根据 #1840 这个 issue,再安装个 PyWin32 就可以了。

前往 Python for Windows Extensions,点进 pywin32 目录里面的最新 build,找到对应当前 Python 版本的 exe,下载安装。

使用

比如我们现在有一个脚本文件 main.py,要将它打包成可执行文件,直接运行:

pyinstaller -F main.py

这将会在 main.py 所在目录下生成一些其它目录,最终的可执行文件就在 dist 目录中。这条命令中 -F 表示生成单个可执行文件,如果不加 -F,则默认生成一个目录,其中除了可执行文件,还包括其它依赖文件。

总体来说用起来是非常简单的,不过在打包使用了 requests 包的程序时,出现了 ImportError: No module named 'queue' 的报错,发现 这里 也有遇到了同样的问题。

这个问题只需要在打包时加入 --hidden-import 参数即可:

pyinstaller -F --hidden-import queue main.py

更多用法请参考 Using PyInstaller

使用 PyInstaller 将 Python 程序打包成无依赖的可执行文件

从 DLL 文件生成 LIB 文件

创建于
分类:Dev
标签:动态链接库Windows酷Q

标题可能有一定误导嫌疑,首先这里的 lib 文件不是指静态编译的 lib 文件,而是和 dll 配套使用的用来通过链接的 lib 文件;另外也不是从 dll 生成,而是直接生成,只是需要先通过 dll 来查看有哪些导出函数。

故事背景

在做酷 Q 插件开发的时候,发现官方的 C++ SDK 很久没有更新了,找了一圈发现其实它的函数实现都在安装目录的 CQP.dll,我要做的就是能让 C++ 代码去调用里面的函数,根据官方 SDK 的结构,光使用头文件来声明是没有用的,还需要一个 .lib 文件,研究了一圈发现其实 SDK 所谓的 .lib 文件并不是真正的函数实现,而只是用来通过编译的一个东西,它内部实际上是对 dll 中的函数的一些描述。于是就有了后面的折腾。

方法和坑……

需要说明的是,这里主要说的是针对酷 Q 的 dll 的情况,似乎编译 dll 的时候,函数的修饰符会影响很多结果,我也不太清楚酷 Q 具体是怎么修饰的导出函数,所以,这里说的方法可能不具有普适性。

我本来以为那个 .lib 文件是把 dll 转换成静态编译的,但似乎这其实是做不到的(或者我没找到办法),然后发现只是一个对 dll 里函数的描述,于是找到了生成 lib 文件的办法。

获取 DLL 文件的导出函数列表

首先需要知道代码里面会调用那些函数的函数名称和参数列表,这也就需要知道 dll 里面有哪些函数。打开 VS 附带的一个「Visual Studio Developer Command Prompt」(可以在开始菜单搜索到),然后进入 dll 文件所在目录,运行 dumpbin.exe /exports Some.dll > dump 即可把导出函数等一堆信息 dump 到 dump 文件中,类似下面这样:

Microsoft (R) COFF/PE Dumper Version 14.10.25019.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file .\CQP.dll

File Type: DLL

  Section contains the following exports for CQP.dll

    00000000 characteristics
    590B3029 time date stamp Thu May  4 21:44:09 2017
        0.00 version
           1 ordinal base
          36 number of functions
          36 number of names

    ordinal hint RVA      name

          1    0 0006F0D2 CQ_addLog
          2    1 0006F1D2 CQ_getAppDirectory
          3    2 0006F12C CQ_getCookies
          4    3 0006F160 CQ_getCsrfToken

......

从这里就可以知道 dll 的导出函数有哪些,不过只有函数名,我这里遇到的情况,还需要知道参数列表(准确地说是参数列表所占用的字节数)。

手动编辑 DEF 文件

然后就是把这些函数名写到一个 .def 文件中,如:

LIBRARY Some
EXPORTS
CQ_addLog@16
CQ_getAppDirectory@4
CQ_getCookies@4
CQ_getCsrfToken@4

这里,前两行是固定的,Some 换成你的链接库的名字。第三行开始,每行一个函数,结尾的 @ 加数字是参数列表占用的字节数,应该是用于在调用时的参数压栈。

生成 LIB 文件

有了 .def 文件之后,使用 lib.exe 命令来生成 .lib 文件:

lib.exe /def:Some.def /out:Some.lib

网上搜到的所有文章都是这么说,然而对于酷 Q 这个情况,到这里是不行的,生成的 lib 可以通过链接,但酷 Q 无法加载。

于是我 dump 了这个生成的 lib 文件和酷 Q 官方 SDK 的 lib 文件,来看区别,发现了问题所在。

我生成的 lib:

Microsoft (R) COFF/PE Dumper Version 14.10.25019.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file .\CQP.lib

File Type: LIBRARY

Archive member name at 8: /
591022E1 time/date Mon May  8 15:48:49 2017
         uid
         gid
       0 mode
     11E size
correct header end

    11 public symbols

      2C6 __IMPORT_DESCRIPTOR_CQP
      4E0 __NULL_IMPORT_DESCRIPTOR
      612 CQP_NULL_THUNK_DATA
      75C _CQ_addLog@16
      75C __imp__CQ_addLog@16
      7C2 _CQ_getAppDirectory@4
      7C2 __imp__CQ_getAppDirectory@4

......

String Table Size = 0x19 bytes

Archive member name at 75C: CQP.dll/
591022E1 time/date Mon May  8 15:48:49 2017
         uid
         gid
       0 mode
      2A size
correct header end

  Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: 591022E1 Mon May  8 15:48:49 2017
  SizeOfData   : 00000016
  DLL name     : CQP.dll
  Symbol name  : _CQ_addLog@16
  Type         : code
  Name type    : no prefix
  Hint         : 0
  Name         : CQ_addLog@16

Archive member name at 7C2: CQP.dll/
591022E1 time/date Mon May  8 15:48:49 2017
         uid
         gid
       0 mode
      32 size
correct header end

  Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: 591022E1 Mon May  8 15:48:49 2017
  SizeOfData   : 0000001E
  DLL name     : CQP.dll
  Symbol name  : _CQ_getAppDirectory@4
  Type         : code
  Name type    : no prefix
  Hint         : 1
  Name         : CQ_getAppDirectory@4

......

酷 Q 的 lib:

Microsoft (R) COFF/PE Dumper Version 14.10.25019.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file .\CQP-Good.lib

File Type: LIBRARY

Archive member name at 8: /
558FB3E8 time/date Sun Jun 28 16:44:24 2015
         uid
         gid
       0 mode
     792 size
correct header end

    67 public symbols

      FAE __IMPORT_DESCRIPTOR_CQP
     11C8 __NULL_IMPORT_DESCRIPTOR
     12FA CQP_NULL_THUNK_DATA
     1444 _CQ_addLog@16
     1444 __imp__CQ_addLog@16
     14AA _CQ_getAppDirectory@4
     14AA __imp__CQ_getAppDirectory@4

......

String Table Size = 0x19 bytes

Archive member name at 1444: CQP.dll/
558FB3E8 time/date Sun Jun 28 16:44:24 2015
         uid
         gid
       0 mode
      2A size
correct header end

  Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: 558FB3E8 Sun Jun 28 16:44:24 2015
  SizeOfData   : 00000016
  DLL name     : CQP.dll
  Symbol name  : _CQ_addLog@16
  Type         : code
  Name type    : undecorate
  Hint         : 0
  Name         : CQ_addLog

Archive member name at 14AA: CQP.dll/
558FB3E8 time/date Sun Jun 28 16:44:24 2015
         uid
         gid
       0 mode
      32 size
correct header end

  Version      : 0
  Machine      : 14C (x86)
  TimeDateStamp: 558FB3E8 Sun Jun 28 16:44:24 2015
  SizeOfData   : 0000001E
  DLL name     : CQP.dll
  Symbol name  : _CQ_getAppDirectory@4
  Type         : code
  Name type    : undecorate
  Hint         : 1
  Name         : CQ_getAppDirectory

......

对比这两者,发现函数的「Symbol name」其实是没有问题的,问题出在「Name type」和「Name」,酷 Q 的 lib 文件,「Name type」都是 undecorate,「Name」也没有结尾的 @ 加数字。

一开始我觉得可能是 lib.exe 的参数不太对,或者 def 文件写的不太对,于是搜了好久,尝试了各种方法,例如函数别名、删掉 @、使用 MinGW 的 dlltool.exe 等等,但无一例外没有什么用,使用函数别名只会在生成一个单独的函数描述,不会改变「Name type」,删掉 @ 也就导致函数「Symbol name」不对,MinGW 的 dlltool.exe 更是似乎生成的结构完全不对(可能不兼容)。

当我准备放弃的时候,搜到了 这篇 帖子,内心惊呼卧槽,这发帖人的描述就和我想要的情况一样啊!也是要让「Symbol name」带 @ 而「Name」不带,虽然底下的回答没有什么有价值的信息,但发帖人倒是给出了一种办法——把函数描述区域的某个字节从 0x08 改成 0x0C

于是用 Hex 编辑器查看了我生成的 lib 和酷 Q 的 lib 文件,找了一圈,找到了这个字节的位置,,并发现通过 0x08 0x00 0x5F 这个序列即可直接确定它们的位置,其中 0x5F 是下划线 _,也就是生成 lib 文件之后自动加的前缀,这样一来事情就简单很多,直接写一个脚本来替换即可:

def fix_lib_file():
    with open('Some.lib', 'rb') as lib_f:
        binary = lib_f.read()

    binary = binary.replace(b'\x08\x00\x5f', b'\x0c\x00\x5f')

    with open('Some.lib', 'wb') as lib_f:
        lib_f.write(binary)

至此,修复之后的 lib 文件终于可以用了。

总结

这玩意折腾了我整整两天时间……不过时间也没有白费,最后也成功了,然后写了个完整的脚本来从 .h 头文件生成酷 Q .lib 文件,放在 richardchien/coolq-cpp-sdk
了。

参考资料

从 DLL 文件生成 LIB 文件

把博客迁移到 VeriPress 了

创建于
分类:Misc
标签:BlogVeriPress

好几天前就写好了 VeriPress 的初始版本(说初始版本是因为有一些功能在写之前就打算放在第二版来实现,不过目前看来,这个就当个 flag 好了……),一直没空把自己博客迁移过来,刚刚抽空把 VPS 全整理了一下,然后终于迁移了博客。

不得不说我之前真的蠢,竟然直接拿 Flask 的 run() 来跑,这次是用 uWSGI 和 nginx 跑的 Flask 的 app 对象,并开启了 HTTP/2 支持。

不过由于新的 VeriPress 和原来的 BlogA 在给评论框提供文章的唯一标识不一样了,原来的 Disqus 评论在新的上也就看不到了,虽然 Disqus 官方有一个迁移工具,但我想本来也没有几个评论,也就不折腾了吧,况且 Disqus 被墙了,感觉其实不是一个好的选择,就先这样吧。

再说一下我现在跑 VeriPress 的方法,首先在 VPS 用 virtualenv 来安装,VeriPress 实例放在主目录,然后通过 uWSGI 指定 virtualenv 的路径和工作目录,文章和页面在本地电脑上保存,写新文章之后用 sftp 同步到 VPS。这样整个流程基本上用起来还算舒服,缺点就是需要手动同步文章等数据,但现在实在懒得配置 GitHub 的 webhook 来自动更新了。

其实还是非常想后面有空给 VeriPress 加上数据库存储支持的,同时加上后台管理页面,这样也就不用费心同步文件了。

另外就是博客主题,由于不是很有空,就先用着之前写的默认主题了,后面有机会要重新写个好一点的主题。

嗯,来数数这篇文章里一共立了多少 flag。

把博客迁移到 VeriPress 了