DES五种加密模式MFC实现:源码解析与工程落地指南
简介一份基于MFC实现的DES五种加密模式源码面向需要理解分组密码算法和Windows界面编程的开发者完整实现ECB、CBC、CFB、OFB、CTR五种工作模式可用于对比不同模式在文件加密中的表现。压缩包共18个文件以C头文件、源文件与MFC资源脚本为主包含对话框控件定义、算法逻辑、工程配置及可执行文件整体仅57KB结构清晰。源码界面友好注释完善已删除Debug目录程序退出时的警告仅作为提示不影响任何加解密操作。开发者可以借助现有框架快速运行体验并利用注释方便地扩展为三重DES阅读代码还能了解MFC对话框程序的基本组织方式便于在此基础上增加输入密钥、选择文件等交互功能适合算法学习、课程设计或安全编码参考。目前已有1090人学习/下载是一份教学与实践中可用的样本。1. DES五种加密模式MFC实现源码在讲什么一个桌面工具的实际价值“DES五种加密模式MFC实现源码”这个标题最常被拿来问的不是 DES 算法本身而是怎么把它塞进一个还在维护的 MFC 对话框程序里。实际场景往往是要给本地配置文件加密、保护数据库里的敏感字段、把导出的临时文件换一种存储格式很多团队的第一反应仍然是 DES。理由很现实——老代码在这里、不用引第三方库、开发环境里现成的工具链能直接编译。这套源码的价值是把五种分组加密模式的数据流讲清楚给出能落到 MFC 工程里的类接口同时把填充、IV、字符集这类坑提前标出来。适合两类读者接手老 MFC 项目想给数据加一层保护的开发者以及想通过一个完整可编译的 DES Demo 理解 ECB、CBC、CFB、OFB、CTR 模式差异的初学者。前者可以照着工程结构改代码后者可以用标准测试向量验证每一步。2. DES核心实现置换表、S盒与MFC工程里的模块边界拿到“源码”后第一件事不是去拖界面控件而是先把核心算法从窗口逻辑里剥离出来。MFC 工程里如果界面代码和算法代码写在一起调试阶段会非常痛苦。一个加密按钮背后要同时处理输入框取值、字节转换、填充、模式选择、密文显示任何一步出错都很难快速定位。所以常见做法是先把 DES 核心做成一个独立的类再封装出五种模式最后才是界面调用。2.1 先看清密钥调度16轮子密钥怎么生成DES 加密主流程由 16 轮迭代组成每一轮用到一个 48 位的子密钥。这些子密钥不是直接取自原始密钥而是经过一次 PC-1 置换去掉奇偶校验位得到 56 位有效密钥再拆成左右各 28 位按轮数循环左移后做 PC-2 压缩置换。整个过程就是密钥调度。// 密钥调度64位原始密钥 - 16个48位子密钥 void DesCore::GenerateSubKeys(const BYTE key[8], BYTE subKeys[16][6]) { BYTE kp[7] {0}; // PC-1 之后是 56 位用 7 字节存储 Permute(key, PC1, 56, kp); // 去掉奇偶校验位按 PC-1 表打乱位序 BYTE c[4] {0}, d[4] {0}; // 左半 28 位、右半 28 位 SplitHalf(c, d, kp); // 注意这里是按位拆不是按字节拆 for (int round 0; round 16; round) { RotateLeft(c, SHIFT_SCHEDULE[round]); // 每轮左移 1 位或 2 位 RotateLeft(d, SHIFT_SCHEDULE[round]); JoinHalf(c, d, kp); // 合并回 56 位 Permute(kp, PC2, 48, subKeys[round]); // 压缩成 48 位子密钥 } }这份代码里 SHIFT_SCHEDULE 就是那张左移位数表前几轮是 1 位中间大部分是 2 位最后一轮又是 1 位标准表为{1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}。PC1 和 PC2 是两张固定的索引映射表PC1 把 64 位密钥映射到 56 位PC2 把 56 位映射到 48 位。Permute 函数的通用写法是遍历目标长度的每一位从源数据的指定索引处取值并放到目标位置。实现时最容易出错的是 SplitHalf 和 JoinHalf因为 28 位不是整字节边界涉及跨字节取位。如果不想在这里花时间可以按字节展开写成 7 个字节逐位操作可读性差一点但是不容易绕晕。密钥调度正确性是整个 DES 实现的根基。第一轮和第十六轮的子密钥即使只错一位最终密文也会完全不一样。验证密钥调度最简单的方式是先用标准测试向量过一遍而不是急着接界面。2.2 数据块处理的三个边界问题位序、填充与大小端DES 的单块加密固定处理 8 字节明文输出 8 字节密文。三个边界问题几乎每个初学者都会踩位序定义、填充规则、多字节数据在内存里的存放顺序。位序方面DES 标准里说的“第 1 位”是最有效位。实现时如果按字节数组存储索引 0 的最高位是第 1 位索引 0 的最低位是第 8 位。很多实现翻车的根源不是置换表写错而是位序取反了导致换表后结果和标准向量对不上。我的习惯是固定用“MSB 优先”的位序所有置换表都按这个语义写不做任何运行时转换。这样虽然代码里位移多一点但至少能保证可复现。填充规则取决于模式。ECB 和 CBC 是分组模式数据长度不是 8 的倍数时要填充。常见做法是 PKCS#7缺 n 字节就补 n 个值为 n 的字节如果数据正好是 8 的倍数也要额外补 8 个字节。CFB、OFB、CTR 是流式模式不需要填充但要注意它们按 8 字节块为单位生成密钥流待加密数据不要求补齐。大小端的问题主要出现在把整数转成字节数组时。MFC 程序跑在 x86 平台上内存是小端布局一个 32 位整数的低字节在低地址。做 CTR 模式时要维护一个 64 位计数器直接把 long long 转成字节数组需要确认低地址放的是低字节还是高字节。统一按“小端字节序”写入计数器可以省掉很多联调时的麻烦。// 单块加解密接口输入 8 字节输出 8 字节 void DesCore::EncryptBlock(const BYTE in[8], BYTE out[8]) { BYTE ip[8] {0}; Permute(in, IP, 64, ip); // 初始置换 BYTE l[4] {0}, r[4] {0}; SplitHalf(l, r, ip); for (int round 0; round 16; round) { BYTE feistelOut[4] {0}; Feistel(r, subKeys[round], feistelOut); // 扩展、异或、S盒、P置换 XorBytes(l, feistelOut, r, feistelOut); // 左右交换 // 这一轮结束后l、r 的角色互换 SwapCopy(l, r); } BYTE preOutput[8] {0}; JoinHalf(r, l, preOutput); // 最后一次交换后输出顺序是 R16L16 BYTE tmp[8] {0}; Permute(preOutput, IP_INV, 64, tmp); memcpy(out, tmp, 8); }Feistel 函数内部要做 E 扩展、与子密钥异或、S 盒代换、P 置换四步。S 盒是最容易写错的地方标准 S 盒是 8 个 4 行 16 列的矩阵输入 6 位首尾两位决定行号中间 4 位决定列号输出 4 位。实现时可以把 8 个 S 盒直接展开成一张二维查询表也可以用三维数组一次性定义。要注意 S 盒的行列索引到底从 0 还是从 1 开始标准文档里行号写的是 0 到 3列号是 0 到 15直接用位运算算出行列再查表就好。2.3 把核心封装成不依赖窗口的 DesCore 类为了让后面接 MFC 界面时不返工DesCore 类要满足三个约束不引用任何窗口句柄、不依赖 CString、不持有全局状态。这样它既可以被 MFC 对话框调用也可以被一个控制台测试工程直接编译。#pragma once #include windows.h class DesCore { public: DesCore(); ~DesCore(); // 设置 8 字节密钥内部生成 16 轮子密钥 void SetKey(const BYTE key[8]); // 单块加解密输入输出各 8 字节 void EncryptBlock(const BYTE in[8], BYTE out[8]); void DecryptBlock(const BYTE in[8], BYTE out[8]); private: BYTE subKeys[16][6]; // 工具函数置换、循环左移、异或、S盒代换 void Permute(const BYTE* input, const BYTE* table, int outBitLen, BYTE* output); void Feistel(const BYTE r[4], const BYTE key[6], BYTE out[4]); void GenerateSubKeys(const BYTE key[8]); };这里把 subKeys 作为成员变量好处是密钥设置一次后可以反复加密多块数据不用每次加密都重新生成子密钥。GenerateSubKeys 的调用放在 SetKey 内部避免外部重复调用。Feistel 接收右侧 4 字节和 6 字节子密钥输出 4 字节。整体接口设计保持对称后续封装 ECB 模式只需要循环调用 EncryptBlock封装 CBC 模式只需要在循环里加一个 XOR。我一般还会在 DesCore 旁边加一个简单的校验函数用来打印每一轮的子密钥和中间状态调试时非常有帮助。这个函数只做输出不影响类接口。等单块加密通过标准向量验证后再去写五种模式基本不会碰到“某个模式不通”的问题。3. 五种模式的分工ECB、CBC、CFB、OFB、CTR怎么选DES 核心只解决一个问题把 8 字节变 8 字节。真正的应用场景里数据往往超过 8 字节于是模式决定了相邻数据块之间有无关联、同样的明文块在密文中是否可见、是否引入 IV 和填充。选模式本质上是在选数据块之间的耦合方式。3.1 一次只加密8字节ECB与CBC的显式区别ECB 模式最简单每个 8 字节块独立加密块与块之间没有任何关联。问题在于相同明文块会产生相同密文块如果加密的是位图、结构化数据库页这种模式会直接泄露信息。CBC 模式先把当前明文块与上一个密文块异或再送入 DES 加密第一块用 IV 参与计算形成链路。// CBC 加密每块先与前一密文块异或再调用 DES void DesMode::EncryptCBC(const BYTE* plain, int plainLen, BYTE* cipher, const BYTE* iv) { BYTE feedback[8] {0}; memcpy(feedback, iv, 8); // 初始向量 BYTE block[8] {0}; for (int i 0; i plainLen; i 8) { memcpy(block, plain i, 8); for (int j 0; j 8; j) block[j] ^ feedback[j]; des.EncryptBlock(block, cipher i); memcpy(feedback, cipher i, 8); // 下一轮关联 } }CBC 解密是逆过程先解密得到中间结果再与上一个密文块异或。所以解密循环里存的 feedback 也必须是密文块而不是解密后的明文块。这个顺序被我反复写错过代码里最容易犯的错误是解密时把 feedback 更新成了上一块解密后的明文一旦写错只有第一块能解对后面全乱。3.2 流式模式CFB、OFB与CTR的位移与并行CFB、OFB、CTR 三种模式本质上都是把 DES 当作一个密钥流生成器再把密钥流与明文异或得到密文。CFB 模式中每次加密的是上一个密文块产生密钥流块后与当前明文块异或。解密时同样用加密操作生成密钥流与密文异或得到明文所以解密路径完全复用 EncryptBlock。OFB 模式不同之处在于反馈值是上一次生成的密钥流块一旦密钥和 IV 确定密钥流序列可以预先算好。CTR 模式则维护一个计数器每块用不同的计数值加密生成密钥流块与块之间没有链路天然支持并行。// CTR 模式计数器按小端增长每块用不同计数值做密钥流 void DesMode::EncryptCTR(const BYTE* plain, int plainLen, BYTE* cipher, const BYTE* nonce, unsigned long long startCount) { BYTE counter[8] {0}; memcpy(counter, nonce, 4); // 把起始计数值写入后 4 字节小端序 counter[4] (BYTE)(startCount 0xFF); counter[5] (BYTE)((startCount 8) 0xFF); counter[6] (BYTE)((startCount 16) 0xFF); counter[7] (BYTE)((startCount 24) 0xFF); BYTE keystream[8] {0}; BYTE block[8] {0}; for (int i 0; i plainLen; i 8) { des.EncryptBlock(counter, keystream); int remain min(8, plainLen - i); memset(block, 0, 8); memcpy(block, plain i, remain); for (int j 0; j remain; j) cipher[i j] block[j] ^ keystream[j]; // 计数器自增 for (int j 7; j 0; j--) if (counter[j] ! 0) break; } }CTR 的计数范围要留意如果使用 8 字节计数器加密数据超过 64GB 才会有计数回绕普通桌面应用完全不用考虑。但如果把前 4 字节留给随机 nonce后 4 字节做计数器那么单次会话最多加密 16GB 数据。MFC 桌面应用加密的通常是几十 KB 级别的文本或配置怎么拆都够用。并行化是 CTR 最大的优势每个块的密钥流只依赖 counter 的取值可以开多线程一次预生成所有密钥流。3.3 模式选型速查四类场景怎么定模式填充要求解密并行错误传播典型场景ECB需要支持单块错误只影响本块密钥长度短、随机数加密CBC需要不支持密文块错误影响本块和下一块本地文件加密、数据库字段保护CFB不需要不支持密文块错误影响本块和后续若干块字符流实时加密OFB不需要不支持密文块错误只影响本块对应位需要预生成密钥流的场合CTR不需要支持无传播块间独立多线程场景、大文件分块加密本地文件加密我一般选 CBC既有 IV 的随机性实现也简单。需要多线程处理大文件时选 CTR。CFB 和 OFB 在 MFC 里很少单独用除非对接的旧协议指定了模式。ECB 不是不能用但明文里只要存在重复块密文就会暴露该信息谨慎对待。4. MFC落地对话框工程、模式切换与中国化界面的三个细节核心算法和五种模式封装好之后剩下的问题就是怎么放进 MFC 对话框工程里。这部分不需要太多技巧但要按固定顺序操作否则编译阶段就会一堆报错。4.1 在 MFC 工程里挂上 DES 模块工程组织与字符集设置常见做法是新建一个 MFC 对话框应用然后把 DesCore.cpp、DesModule.cpp 两个文件直接拖进工程。头文件包含路径不要用相对路径到处飞我习惯在工程根目录建一个 crypto 文件夹把 DES 相关文件全部放进去然后在项目设置里加上它的包含路径。这样不管对话框类放在哪个目录都能用#include crypto/DesModule.h引用。字符集设置是 MFC 工程里第一个坑。新版开发环境默认使用 Unicode 字符集意味着 CString 底层是宽字符而 DES 处理的是 BYTE 字节数组。在对话框里接收输入后要先把 CString 转成 UTF-8 字节数组再加密解密后再把字节数组转回 CString。如果工程里还开着“使用多字节字符集”转换方式完全不同。我建议直接把工程设置成 Unicode后面所有转换按宽字符处理。MFC 的字符串类有一个容易迷惑人的地方CString 在不同字符集设置下可能是 CStringA 或 CStringW。代码里如果写了GetBuffer()返回值直接传给memcpy在 Unicode 设置下会复制出两倍长度的数据而且内容也不是预期字节。统一处理方式是把所有从界面取到的文本先转成std::string的 UTF-8 编码再交给 DES 模块。4.2 模式切换与IV输入ComboBox与Edit控件的联动界面上至少要放模式下拉框、密钥输入框、IV 输入框、明文编辑框、密文编辑框、加密按钮、解密按钮。模式下拉框的条目顺序和枚举值要保持一致我是这样初始化的void CDesDemoDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 模式下拉框条目顺序与枚举值一一对应 m_cmbMode.ResetContent(); m_cmbMode.AddString(_T(ECB)); m_cmbMode.AddString(_T(CBC)); m_cmbMode.AddString(_T(CFB)); m_cmbMode.AddString(_T(OFB)); m_cmbMode.AddString(_T(CTR)); m_cmbMode.SetCurSel(1); // 默认 CBC }加密按钮的核心逻辑是取出文本、转字节、调用模式类、转成十六进制显示。注意 IV 只在 ECB 模式下不需要其他模式都要从输入框读取。如果用户没填 IV我习惯用定值0000000000000000但解密时必须有与加密时完全相同的 IV这一点在界面上要写清楚提示文字。void CDesDemoDlg::OnBnClickedBtnEncrypt() { CString strInput, strKey, strIv; GetDlgItemText(IDC_EDT_INPUT, strInput); GetDlgItemText(IDC_EDT_KEY, strKey); GetDlgItemText(IDC_EDT_IV, strIv); int modeIndex m_cmbMode.GetCurSel(); // CString - UTF-8 字节数组 int utf8Len WideCharToMultiByte(CP_UTF8, 0, strInput, -1, NULL, 0, NULL, NULL); std::vectorBYTE plainBytes(utf8Len - 1); WideCharToMultiByte(CP_UTF8, 0, strInput, -1, (char*)plainBytes.data(), utf8Len, NULL, NULL); // 密钥统一 8 字节不足补零超过截断 BYTE key[8] {0}; CStringA keyA(strKey); int keyLen min(8, (int)keyA.GetLength()); memcpy(key, keyA.GetBuffer(), keyLen); keyA.ReleaseBuffer(); // 模式类统一入口 BYTE iv[8] {0}; if (modeIndex ! 0) { CStringA ivA(strIv); int ivLen min(8, (int)ivA.GetLength()); memcpy(iv, ivA.GetBuffer(), ivLen); ivA.ReleaseBuffer(); } desModule.SetKey(key); desModule.SetIv(iv); std::vectorBYTE cipherBytes; if (modeIndex 0) desModule.EncryptECB(plainBytes.data(), (int)plainBytes.size(), cipherBytes); else if (modeIndex 1) desModule.EncryptCBC(plainBytes.data(), (int)plainBytes.size(), cipherBytes); // 其他模式同理 // 密文转十六进制 CString strCipher BytesToHex(cipherBytes.data(), (int)cipherBytes.size()); SetDlgItemText(IDC_EDT_CIPHER, strCipher); }逻辑上要注意几个点UTF-8 转换时WideCharToMultiByte返回的长度包含结尾的空字符所以plainBytes要减掉 1。密钥和 IV 都直接从输入框读取不足 8 字节补零超过 8 字节截断这种做法在 Demo 里没问题生产环境里应当提示用户密钥长度必须为 8 字节。BytesToHex 是一个工具函数把每个字节拆成两位十六进制字符输出到界面。4.3 中文乱码与文件加密的编码处理MFC 的 Edit 控件显示中文时默认按系统语言处理。如果加密前的文本是中文直接转成 BYTE 数组时如果用了本地代码页换一台机器再解密可能变成乱码。这就是为什么加密前要规范化编码。项目内部统一用 UTF-8加密时把 CString 转 UTF-8 字节序列解密后把 UTF-8 字节序列转回 CString这样无论系统区域设置如何都能保证一致。文件加密在 MFC 里要额外处理文件头。直接把整个文件按 CBC 模式加密解密后可能需要判断原始长度来去掉填充。常见做法是在文件头写一个自定义结构保存原始文件长度和填充字节数再用加密后的数据覆盖文件内容。这样解密时先读取文件头再按长度精确还原文件。// CString 转 UTF-8 字节序列 std::vectorBYTE Utf8FromCString(const CString text) { int len WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); std::vectorBYTE bytes(len - 1); WideCharToMultiByte(CP_UTF8, 0, text, -1, (char*)bytes.data(), len, NULL, NULL); return bytes; } // BYTE 序列转回 CString原字节必须是 UTF-8 CString CStringFromUtf8(const BYTE* utf8Data, int len) { int utf16Len MultiByteToWideChar(CP_UTF8, 0, (const char*)utf8Data, len, NULL, 0); CString result; MultiByteToWideChar(CP_UTF8, 0, (const char*)utf8Data, len, result.GetBuffer(utf16Len), utf16Len); result.ReleaseBuffer(); return result; }这两个函数放在一个EncodingUtil.cpp里MFC 对话框和文件加密模块共用。特别注意MultiByteToWideChar的第四个参数要传实际字节长度不要传 -1否则会把输入当作空字符结尾的字符串导致长度判断出问题。文件里如果正好有0x00字节也会被错误截断。5. 避坑与排查DES在MFC里最常见5个翻车现场5.1 前8字节能解后面全乱填充统一问题现象加密后用同一套代码解密前 8 字节正常后面的内容要么变成乱码要么直接解不出。原因加密时用了 PKCS#7 填充解密时没有移除填充或者填充方式不一致。还有一种常见情况是加密时把输入长度按strlen计算中文字符在 UTF-8 下会占 3 字节但界面输入的字符数不是字节数。解决加密前记录原始字节长度加密后保存填充信息解密时先解出全部数据再根据最后一字节的填充值去掉对应长度。如果用块大小 8 的倍数做整块处理不要简单的new BYTE[plainLen]要分配((plainLen 8) / 8) * 8的空间。5.2 Unicode 字符集下崩溃TCHAR 与 BYTE 混用现象程序一开始用多字节字符集编译正常改成 Unicode 后程序启动或点击加密按钮直接崩溃。原因CString 底层从char*变成了wchar_t*GetBuffer()返回的指针宽度变了代码里如果有memcpy(key, strKey.GetBuffer(8), 8)这种写法复制出来的根本不是明文密钥。解决所有从 CString 取字节的操作都先转成CStringA或者调用CW2A宏保证拿到的是单字节字符串再memcpy。同样所有从char*构造 CString 的地方要用CA2W。5.3 DEBUG 正常 RELEASE 失败越界缓冲区的“路过”现象DEBUG 配置下单块加密、CBC 加密都正常切换到 RELEASE 后密文偶尔不对有时解密直接报错。原因缓冲区分配比实际需要小了几个字节DEBUG 下堆管理器会填充哨兵字节掩盖了越界写RELEASE 下的堆布局不同越界写覆盖了相邻数据导致 DES 内部子密钥或 IV 被破坏。解决仔细检查所有new和malloc的长度计算尤其是 CBC、CFB 这种需要多读一个 IV 字节的循环。C 风格的数组参数传递时最好在所有跨模块接口处用std::vectorBYTE替换裸指针同时加上边界断言的 Debug 检查。5.4 CBC 解密用错 IV模式接口设计问题现象加密用了 CBC 模式并指定了随机 IV解密时只传入了密钥结果第一块解出来是乱码。原因CBC 解密第一块需要与加密时完全一致的 IV否则第一块错误并且错误会传播到第二块。很多接口设计把密钥和 IV 分开但调用者容易忘记设置 IV。解决在模式类里把 IV 作为 Encrypt 和 Decrypt 的必传参数不要放到SetIv这种独立接口里。或者在初始化时判断 IV 指针是否为空为空则使用全零 IV并在日志里输出警告。界面上的提示文字也要写清楚同一份密文加密和解密的 IV 不一致时得到的结果是完全无效的。5.5 网页工具能解MFC 解不了HEX 与 Base64 显示不一致现象本地程序加密后把密文复制到网页工具里解密失败或者网页工具加密的密文本地解不了。原因大部分网页工具默认用 Base64 输出密文而 MFC 程序里常常用十六进制字符串显示。另外有些网页工具对密钥输入按 ASCII 字符处理有些按 HEX 解析两边密钥的实际字节值根本不一致。解决在 MFC 程序里同时提供 HEX 和 Base64 两种密文输出并把密钥输入框做成两种模式。更稳妥的做法是在界面上增加“从 HEX 导入密钥”和“从 ASCII 导入密钥”两个选项避免用户直接把字符串当作字节数组输入。6. 用标准向量喂一遍再谈并行优化DES 实现完成后的第一件事不是接界面而是用一组公开的参考向量验证核心函数的正确性。参考向量可以用下面这组密钥和明文、密文都是十六进制表示如果EncryptBlock的输出和预期一致说明置换表、S 盒、密钥调度基本没错。void TestDesCore() { DesCore des; BYTE key[8] {0x13,0x34,0x57,0x79,0x9B,0xBC,0xDF,0xF1}; BYTE plain[8] {0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF}; BYTE expect[8] {0x85,0xE8,0x13,0x54,0x0F,0xAB,0x40,0x50}; BYTE cipher[8] {0}; des.SetKey(key); des.EncryptBlock(plain, cipher); bool ok true; for (int i 0; i 8; i) { if (cipher[i] ! expect[i]) { ok false; char msg[256]; sprintf_s(msg, 第 %d 字节不匹配预期 %02X实际 %02X, i, expect[i], cipher[i]); OutputDebugStringA(msg); } } if (ok) OutputDebugStringA(DES 核心测试通过); }单块正确之后再验证解密函数确保DecryptBlock(EncryptBlock(p)) p。这一步通过后五种模式基本只需要关注填充和 IV 的传输逻辑算法本身的信赖度就高了。接下来可以考虑并行化。CTR 模式天然适合多线程预生成密钥流时每个线程处理连续一段计数器即可。CBC 模式无法并行加密但解密时可以按块并行前提是先拿到完整的密文块。在 MFC 里开std::thread处理大文件时要警惕 UI 线程阻塞常见做法是把加解密放到工作线程通过PostMessage把进度和结果回传给对话框界面。最后说一个我的习惯源代码里所有跨模块的接口统一使用std::vectorBYTE而不是裸指针填充逻辑只放在模式封装层界面层永远不做字节级别的操作。早期我把填充写进按钮消息处理函数里后来维护另一个功能时改动了一次填充逻辑界面和加密模块各改了一半出现了整整一天才排查出来的兼容性问题。这套结构保持到现在同样的功能再也没翻过车。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

oauth2-server 版本演进全解析:从 CHANGELOG 看 PHP OAuth 2.0 服务器十三年能力变迁

oauth2-server 版本演进全解析:从 CHANGELOG 看 PHP OAuth 2.0 服务器十三年能力变迁

后端认证鉴权 【免费下载链接】oauth2-server A spec compliant, secure by default PHP OAuth 2.0 Server 项目地址: https://gitcode.com/gh_mirrors/oa/oauth2-server 点击查看 免费下载 oauth2-server 是一款遵循规范、默认安全的 PHP OAuth 2.0 授权与资源服务…

2026/10/15 16:57:02 阅读更多 →
阿里巴巴Java代码规约落地实战:从IDE插件到CI集成

阿里巴巴Java代码规约落地实战:从IDE插件到CI集成

简介:这份资源面向Java后端开发人员与团队技术负责人,聚焦阿里巴巴代码规约的落地实践,帮助解决代码风格不统一、静态检查缺失、团队协作规范难推行等问题。包内共5个文件,以2份PDF手册、2个XML配置文件和1个zip压缩包为主&#x…

2026/10/15 16:57:02 阅读更多 →
大数运算原理与实现:从字符数组到进制无关的高精度计算引擎

大数运算原理与实现:从字符数组到进制无关的高精度计算引擎

简介:本资源是面向计算机专业本科生的数据结构课程设计实践项目,聚焦大数运算这一经典难点问题,完整实现十进制与二进制双模态下的加、减、乘、除、乘方、取模六大核心运算,适用于算法课设、密码学前置实践及高性能计算入门训练。…

2026/10/15 16:57:02 阅读更多 →

最新新闻

基于C++的USB通信上位机开发:从枚举到批量传输的完整实现

基于C++的USB通信上位机开发:从枚举到批量传输的完整实现

简介:这是一份面向C开发者与嵌入式方向学习者的USB通信上位机完整工程源码,聚焦于通过C实现与USB设备的数据交互,涵盖设备枚举、驱动初始化、异步数据传输、错误处理及图形界面等核心模块,适合希望深入理解USB协议与底层硬件操作的…

2026/10/15 17:49:04 阅读更多 →
language-ext Monad Trait 体系深度解析:从 Monad、MonadIO 到 MonadT 的纯函数式组合指南

language-ext Monad Trait 体系深度解析:从 Monad、MonadIO 到 MonadT 的纯函数式组合指南

后端 【免费下载链接】language-ext C# pure functional programming framework - come and get declarative! 项目地址: https://gitcode.com/gh_mirrors/la/language-ext 点击查看 免费下载 导读 language-ext 是一个 C# 纯函数式编程框架,而 Monad&…

2026/10/15 17:49:04 阅读更多 →
Flutter 与 FastAPI 构建多智能体应用:流式输出与状态管理实战

Flutter 与 FastAPI 构建多智能体应用:流式输出与状态管理实战

多智能体系统这两年从论文里走出来,落到实际项目里的速度比很多人预想的要快。但真到动手阶段,一个很现实的问题摆在面前:后端用 Python 写 Agent 编排逻辑,前端用什么?Web 页面在移动端体验拉胯,原生开发又…

2026/10/15 17:49:04 阅读更多 →
Sprite Atlas性能优化:从图集生成到GPU渲染的全链路实践

Sprite Atlas性能优化:从图集生成到GPU渲染的全链路实践

1. 为什么一张图集(Sprite Atlas)能决定UI加载速度的生死线我第一次在某跨平台系统项目里看到“加载卡顿”问题时,团队里有位前端老手直接打开资源面板,指着内存占用曲线说:“不是逻辑卡,是图没加载完。”—…

2026/10/15 17:49:04 阅读更多 →
Android Studio Tab被顶掉与自动关闭?一文教你配置Editor Tabs

Android Studio Tab被顶掉与自动关闭?一文教你配置Editor Tabs

1. 问题现象与影响分析1.1 Android Studio 里 Tab 被顶掉的典型场景很多人在日常开发中都会遇到一个特别恼火的场景:你正在 Android Studio 里同时开着MainActivity.java、build.gradle.kts、AndroidManifest.xml,然后在左侧 Project 面板里随手点开一个…

2026/10/15 17:49:04 阅读更多 →
VLC 3.0.11 定制版支持 AVS+ 与 DRA 软解实战指南

VLC 3.0.11 定制版支持 AVS+ 与 DRA 软解实战指南

简介:本资源为适配Windows 7及以上系统的VLC媒体播放器3.0.11定制增强版,专为需播放国产音视频编码内容的开发者、广电从业人员及高清影音爱好者设计,解决主流播放器对AVS和DRA格式兼容性不足的问题。压缩包为7z格式,大小130.6MB&…

2026/10/15 17:48:03 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →