1、当debug模式和realse模式发生的现象不同时,或者用编译器调试和直接运行EXE文件现象不同,这时可以看看是不是某些变量没有初始化导致的问题。
2、在Qt Creator中使用Visual Studio 2019编译的库时,遇到找不到外部符号的问题:
3、QT开发时,假设有A和B两个程序,在程序B中启动A程序,并将A设置为B的子窗口:
int pid = startProcess("A.exe", param.toUtf8().data());
getWindowHwndByPID(pid, &A_hWnd, NULL); // 通过pid获取A程序的窗口句柄
::SetParent(A_hWnd, (HWND)this->winId()); // 将A设置为B的子窗口
这时,如果在A程序中创建一个窗口,并显示:
QDialog imageDialog();
imageDialog.exec();
当点击关闭该弹出窗口的时候,程序会崩溃。解决方法是创建该弹出窗口时,给窗口设置一个父窗口:
QDialog parentWindow; // 要给弹出的对话框父窗口
QDialog imageDialog(&parentWindow);
imageDialog.exec();
这样再点击关闭弹出窗口后,就不会崩溃了。
4、开发一个库给另一个程序调用,库接口实现如下:
typedef struct _SYSTEM_INIT_RES {
string strTest;
} SYSTEM_INIT_RES;
NET_DLLAPI bool __stdcall Lib_WebComm_SystemInit(string clientId, SYSTEM_INIT_RES *pResponse) {
bool ret = false;
// 省略其他代码
return ret;
}
另一个程序调用此库的接口:
SYSTEM_INIT_RES *response = new SYSTEM_INIT_RES;
Lib_WebComm_SystemInit("127.0.0.1", response);
delete response;
response = nullptr;
在接口代码运行完后,会出现崩溃:
解决方法是要把接口库用到的string都改成char数组,例如:
typedef struct _SYSTEM_INIT_RES {
char strTest[256];
} SYSTEM_INIT_RES;
NET_DLLAPI bool __stdcall Lib_WebComm_SystemInit(char *clientId, SYSTEM_INIT_RES *pResponse) {
bool ret = false;
// 省略其他代码
return ret;
}
如上修改了strTest
变量的类型和clientId
形参的类型,这时再调用就不会崩溃了。
5、QT开发中,假设有一个webprocess.exe,它的代码中用到QWebEngineView加载网页,如果想要查看网页的调试信息,可以用以下方式开启:
- QT5.15,使用命令行启动webprocess.exe:
webprocess.exe --remote-debugging-port=53288
然后,可以在Chrome浏览器中访问http://localhost:53288
,打开开发者工具,选择远程目标,即可查看QWebEngineView加载网页的调试信息。
- QT6.6,在webprocess.exe程序代码中,添加一行:
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "53288");
// ......
}
然后,可以在Chrome浏览器中访问chrome://inspect/#devices
,配置一下端口:
之后在这里就会有加载的网页的开发者模式了:
点击“inspect”就可以进入开发者模式了。
6、QT开发中,要知道开发的应用程序依赖了哪些QT库,可以用以下命令:
D:\\QT6.6.1\msvc2019_64\bin\windeployqt6.exe MyApp.exe
就可以把程序依赖的库都复制到程序所在文件夹。
7、有一个结构体如下:
typedef struct _SYSTEM_INIT_DATA {
int type;
char clientId[256];
string systemLanguage;
} SYSTEM_INIT_DATA;
使用该结构体时,不能用memset
来初始化清空脏数据,例如:
SYSTEM_INIT_RES *res = new SYSTEM_INIT_RES;
memset(res, 0, sizeof(SYSTEM_INIT_RES));
因为对于包含std::string
类型成员的结构体,用memset
可能会导致指针成员不再指向有效的内存,从而破坏std::string的内部结构。可以使用结构体的构造函数或者其他安全的初始化方法:
typedef struct _SYSTEM_INIT_DATA {
int type;
char clientId[256];
string systemLanguage;
SYSTEM_INIT_DATA() {
memset(clientId, 0, 256);
}
} SYSTEM_INIT_DATA;
或者:
typedef struct _SYSTEM_INIT_DATA {
int type;
char clientId[256] = {0};
string systemLanguage;
} SYSTEM_INIT_DATA;
8、使用 GetOpenFileNameW 函数对当前工作目录的影响。假设应用程序使用了以下两个功能:
// 读取配置文件:使用 GetPrivateProfileString 函数从配置文件中读取配置信息
LPTSTR lpVersion = new char[50];
GetPrivateProfileString("Info", "Version", "", lpVersion, 50, ".\\config.ini");
delete[] lpVersion;
// 文件选择器:使用 GetOpenFileNameW 函数,弹出文件选择框让用户选择文件
WCHAR szFilePath[MAX_PATH] = {0};
OPENFILENAMEW ofn = {0};
ofn.lStructSize = sizeof(OPENFILENAMEW);
ofn.hwndOwner = GetForegroundWindow(); // 父窗口
ofn.lpstrFile = szFilePath;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter = L"All Files\0*.*\0"; // 过滤器,允许选择所有文件
ofn.nFilterIndex = 1;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER; // 文件必须存在
if (GetOpenFileNameW(&ofn)) {
string file = szFilePath;
}
在某些情况下,调用了 GetOpenFileNameW 后,再调用 GetPrivateProfileString 函数无法正确找到配置文件。这个问题的核心在于 GetOpenFileNameW 函数会改变当前的工作目录,GetPrivateProfileString 函数在没有指定完整路径的情况下,默认在当前工作目录中查找配置文件。因此,工作目录变更会导致它无法找到预期的配置文件路径。为了解决这个问题,可以采取以下步骤:
// 在调用 GetOpenFileNameW 之前,获取当前的工作目录
wchar_t buffer[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, buffer);
std::wstring wstrCurrentDir = std::wstring(buffer);
// 在调用 GetOpenFileNameW
GetOpenFileNameW(&ofn);
// 调用了 GetOpenFileNameW 后,恢复原工作目录
SetCurrentDirectoryW(wstrCurrentDir.c_str());
9、std::map 值的更新。c++中,map 容器 key 是唯一的,不允许插入两个具有相同 key 的元素,当使用 insert 方法尝试插入一个已经存在的 key,新值不会被插入。所以如果想更新该 key 的值,应该使用 operator[] 或 emplace 方法:
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> myMap;
// 插入两个具有相同键的元素
myMap.insert({1, "Value1"});
auto result = myMap.insert({1, "Value2"});
if (!result.second) {
std::cout << "插入失败,键1已经存在。\n";
}
// 使用 operator[] 更新值
myMap[1] = "NewValue1";
// 打印 map 内容
for (const auto &pair : myMap) {
std::cout << pair.first << ": " << pair.second << "\n";
}
return 0;
}
输出结果是:
插入失败,键1已经存在。
1: NewValue1
10、字符串结尾的问题。客户端使用 openssl 库对一串字符串进行 AES-128 加密,服务端收到后进行解密,发现字符串的结尾不是’\0’,无法正确解析出内容。原因是客户端调用 openssl 接口时,传递给 openssl 的字符串的长度没有计算上’\0’:
QString CResetpassword_widget::encryptionAES(const QByteArray &rawData, const QByteArray &key, const QByteArray &iv) {
QByteArray encrypted;
encrypted.resize(rawData.size() + 1 + AES_BLOCK_SIZE); // 长度要加1,带上末尾的结束符'\0'
int encrypted_len = 0;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL,
(unsigned char*)key.data(),
(unsigned char*)iv.data());
EVP_EncryptUpdate(ctx, (unsigned char*)encrypted.data(), &encrypted_len,
(unsigned char*)rawData.data(), rawData.size() + 1); // 长度要加1,带上末尾的结束符'\0'
int final_len = 0;
EVP_EncryptFinal_ex(ctx, (unsigned char*)encrypted.data() + encrypted_len, &final_len);
encrypted.resize(encrypted_len + final_len);
EVP_CIPHER_CTX_free(ctx);
// 编码成base64
QByteArray base64Data = encrypted.toBase64();
return QString::fromLatin1(base64Data);
}