最近用QT+大漠插件开发的一个游戏脚本,自己用的不打算开源了,记录一下注意事项。

关于QT的信号和槽

QT的信号参数不要用WIN32风格的,像bool不要用BOOL会报错。如果普通的C++类想要使用QT的信号机制,必须继承QObject,并声明QObject宏。

关于QString转C字符串

QString转wchar_t,不要用QString,C语言原生API都需要TCHAR*类型的参数,如果非要用可以用下面的方法转换一下,注意参数2是一个TCHAR **类型。,计算大小也可以判断是否小于0,因为utf-8的中文二进制补码第一位都是1

UNICODE只是一个字符集,而UTF-8规定了它的储存方式,和TCHAR没关系,CHAR和WCHAR只是储存字符的长度不一样。CHAR也可以储存utf-8

关于编码相关可以参考我的这篇文章:
www.btkrc.com/posts/7e8cdef5/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "tool.h"
/*QString转WCHAR*/
BOOL Tool::QString2Wchar(QString buf, TCHAR **szStr) {
TCHAR *pTemp = (TCHAR *)reinterpret_cast<const TCHAR *>(buf.utf16());
DWORD length = lstrlen(pTemp);
*szStr = new TCHAR[length + 2];
memset(*szStr, 0, length + 2);

wcscpy_s(*szStr, length + 2, pTemp);
return TRUE;
}

/*QString转CHAR*/
BOOL Tool::QString2Char(QString buf, CHAR **szStr) {
std::string str = buf.toLocal8Bit();
const CHAR *pTemp = str.c_str();
DWORD size = strlenUtf8(pTemp);
*szStr = new CHAR[size];
strcpy_s(*szStr, size, pTemp);
return TRUE;
}

/*计算UTF-8编码实际占用字节*/
DWORD Tool::strlenUtf8(CONST CHAR *szStr) {
UCHAR ch;
CONST CHAR *p = szStr;
DWORD size=0;
while (*p) {
ch = *p;
if (ch > 0 && ch <= 0x7F) {
size += 1;
p += 1;
}
else if (ch >= 0x80 && ch <= 0x7FF) {
size += 2;
p += 2;
}
else if (ch >= 0x800 && ch <= 0xFFFF) {
size += 3;
p += 3;
}
else if (ch >= 0x10000 && ch <= 0x10FFFF) {
size += 4;
p += 4;
}
}

return size + 1;
}

QT中使用多线程

QT中多线程可以使用QThread类,重写父类的run()成员实现,但如果要自己调用API的话要注意了,C++中成员函数有一个隐式的this指针,会导致线程函数参数错误。而且线程函数必须是全局的。如果不想用QThread类的话,可以把线程函数用友元装饰,然后在接口中调用CreateThread()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*创建线程功能函数,给接口使用,传入线程函数的地址*/
HANDLE Control::newThread(PVOID pFunc) {
/*把当前对象地址传进参数,友元不属于类成员*/
HANDLE hThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)pFunc,
this,
0,
NULL
);

if (!hThread) {
MessageBox(hwnd, TEXT("CreateThread Error\n"), NULL, MB_OK | MB_ICONEXCLAMATION);
}

return hThread;
}

如上,在接口中创建线程,线程地址必须是友元,如果直接写全局函数是不能访问类的private成员的。


在QT中处理Windows消息队列

1
2
/*重写事件 处理windows消息*/
bool nativeEvent(const QByteArray &eventType, void *message, long *result)override;

关于QT中使用API创建子进程

CreateProcess的第一个参数必须是可写的,否则会报错,或者参数也行,只要保证堆栈存在。并且当前开发环境使用unicode字符集时,CreateProcess的第一个必须指向一个实例字符数组,在unicode版本的CreateProcess中需要修改字符串的内容。所以,这个参数不能为指向只读内存的指针。如果这个参数是一个常量,函数将产生内存访问错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*功能函数,创建子进程*/
BOOL Control::createProc(HANDLE *hProc, LPCTSTR szFileName) {
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);

if (CreateProcess(
szFileName,
NULL,
NULL,
NULL,
FALSE,
CREATE_UNICODE_ENVIRONMENT,
NULL,
NULL,
&si,
&pi
)) {
*hProc = pi.hProcess;
CloseHandle(pi.hThread);
return TRUE;
}
return FALSE;
}

如上,UNICODE版本需要设置一个flag,si的cb成员一定要设置正确,否则会报错


互斥体句柄

在程序退出前不要关闭互斥对象句柄,它会在最后一个句柄被关闭时自行销毁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DWORD preventMultiple(LPCTSTR mutex) {
HANDLE hMutex;
hMutex = CreateMutex(NULL, FALSE, mutex);
DWORD ret = GetLastError();

/*判断互斥体令牌是否存在*/ /*返回值只能判断函数是否执行成功*/
if (!hMutex) {
return MUTEXTOKEN_ERROR;
}
if (ret == ERROR_ALREADY_EXISTS) {
return MUTEXTOKEN_EXISTS;
}
else {
return 0;
}

}

其他

PROCESS_INFORMATION、STARTUPINFO、PROCESSENTRY32这些结构体的dwSize字段必须被设置正确,否则相关api无法正常执行。

COLORREF转RGB

windows系统是小端储存的,而且COLORREF储存的RGB是反过来的,要转换一下使用,给每个字节调换个位置,,如下:

1
2
3
4
5
6
7
8
9
10
11
12
//这只是示范,正常开发用大漠插件就行了
pControl->moveToWaitOrGame(hwndHOS, &pControl->params.waitRect, 1);
while (TRUE) {
COLORREF color = GetPixel(hdc, pControl->params.RGBPointerX, pControl->params.RGBPointerY);

/*将COLORREF转换为RGB*/
DWORD transColor = (*(pb) << 16) + ((*(pb + 1)) << 8) + (*(pb + 2));
if (transColor == pControl->params.mRGB) {
...
}

}