Win32 C程序跨机运行时设置剪贴板数据触发无报错崩溃
剪贴板跨机运行崩溃问题修复
问题现象
循环向客户端计算机剪贴板写入指定字符串时,服务端、客户端程序在同一台机器运行时功能可正常执行,客户端程序迁移至其他独立机器运行时,程序无任何错误码直接崩溃,需要实现稳定的客户端剪贴板循环写入功能,避免运行崩溃。
原始代码
服务端代码
while (1) { HANDLE buf = malloc(1048); r = recv(s, (char*)buf, 1048, 0); if (r == SOCKET_ERROR) { printf("error receiving clipboard data\n"); goto jumpThree; } printf("%s\n", (char*)buf); if (strncmp((char*)buf, "test", 4) == 0)// if we got the respond we expected { break; } free(buf); Sleep(1000); }
客户端原始代码
while (1) { const wchar_t* strData = L"I am spamming you"; if (OpenClipboard(0)) { HANDLE h = GetClipboardData(CF_TEXT); if (strncmp((char*)h, "test", 4) == 0) { printf("TEST: %s\n", (char*)h); int r = send(ClientSocket, (char*)h, 75, 0); if (r == SOCKET_ERROR) { printf("error sending clipboard data\n"); goto jumpThree; } CloseClipboard(); } HGLOBAL hClipboardData; hClipboardData = GlobalAlloc(GMEM_DDESHARE, sizeof(WCHAR) * (wcslen(strData) + 1)); WCHAR* pchData; pchData = (WCHAR*)GlobalLock(hClipboardData); wcscpy(pchData, strData); GlobalUnlock(hClipboardData); SetClipboardData(CF_UNICODETEXT, hClipboardData); CloseClipboard(); } Sleep(1000); }
排查备注:根据官方文档说明,若调用
OpenClipboard()时传入NULL参数同时调用EmptyClipboard()会导致SetClipboardData()触发崩溃,原始代码未调用EmptyClipboard(),已排除该诱因;最终确认问题根源为if条件分支语句逻辑错误。
根因明细
代码中存在多处未定义行为,同机运行时内存环境巧合没有触发异常,跨机运行时环境差异直接触发崩溃:
- 空指针访问:
GetClipboardData(CF_TEXT)调用后未校验返回值,当目标机器剪贴板中不存在ANSI格式文本数据时,接口返回NULL,直接传入strncmp会触发内存访问违例导致进程直接崩溃。 - 资源重复释放:检测到"test"字符串的if分支内提前调用了
CloseClipboard(),分支执行完成后外层逻辑会再次调用CloseClipboard(),重复关闭剪贴板句柄触发未定义行为。 - 剪贴板操作流程违规:调用
SetClipboardData写入数据前未调用EmptyClipboard()取得剪贴板所有权,不符合Windows API调用规范,会导致写入失败或进程崩溃。 - 内存越界访问:
send调用硬编码发送75字节数据,未按照剪贴板返回的实际字符串长度计算发送长度,跨机器运行时内存布局差异会触发越界访问。 - 服务端字符串越界:
recv调用拿到网络数据后未手动添加字符串终止符\0,直接传入printf会读取缓冲区外的野内存。
修复后代码
服务端修复点
- 接收数据后手动添加字符串终止符,避免打印越界
- 修复后服务端循环代码:
while (1) { char* buf = (char*)malloc(1048); r = recv(s, buf, 1047, 0); // 预留1字节存终止符 if (r == SOCKET_ERROR) { printf("error receiving clipboard data\n"); free(buf); goto jumpThree; } buf[r] = '\0'; // 手动补字符串终止符 printf("%s\n", buf); if (strncmp(buf, "test", 4) == 0) { free(buf); break; } free(buf); Sleep(1000); }
客户端修复点
- 所有Win32 API调用后增加返回值校验,空指针直接跳过对应逻辑不继续执行
- 调整
CloseClipboard()调用位置,保证每次成功打开剪贴板后仅配对调用一次关闭 - 按API规范写入剪贴板前调用
EmptyClipboard()取得所有权 - 发送网络数据时按实际字符串长度计算发送字节,删除硬编码长度
- 访问剪贴板内存块时配对调用
GlobalLock/GlobalUnlock,避免内存访问违规 - 修复后客户端循环代码:
while (1) { const wchar_t* strData = L"I am spamming you"; int exitFlag = 0; if (OpenClipboard(NULL)) { // 读取剪贴板ANSI文本 HANDLE hRaw = GetClipboardData(CF_TEXT); if (hRaw != NULL) { char* pText = (char*)GlobalLock(hRaw); if (pText != NULL) { if (strncmp(pText, "test", 4) == 0) { printf("TEST: %s\n", pText); size_t sendLen = strlen(pText) + 1; int r = send(ClientSocket, pText, (int)sendLen, 0); if (r == SOCKET_ERROR) { printf("error sending clipboard data\n"); exitFlag = 1; } } GlobalUnlock(hRaw); } } // 写入剪贴板逻辑 if (!exitFlag) { if (EmptyClipboard()) { size_t dataSize = (wcslen(strData) + 1) * sizeof(WCHAR); HGLOBAL hClipMem = GlobalAlloc(GMEM_DDESHARE, dataSize); if (hClipMem != NULL) { WCHAR* pWrite = (WCHAR*)GlobalLock(hClipMem); if (pWrite != NULL) { wcscpy(pWrite, strData); GlobalUnlock(hClipMem); SetClipboardData(CF_UNICODETEXT, hClipMem); } } } } // 统一关闭剪贴板,避免重复调用 CloseClipboard(); } if (exitFlag) { goto jumpThree; } Sleep(1000); }
内容的提问来源于stack exchange,提问作者Y K
相关产品推荐
相关产品推荐

