Windows开发

Windows,DotNET和C#

今日: 0 主题: 246 回复: 1223版主:phpskycn

分类: (暂无分类)

选用标准:能够同时用于98/Me和2000的。 【常用WinAPI】文件管理篇 一、文件读写(需要句柄) 打开文件:CreateFile(OpenFile) 文件大小:GetFileSize 文件类型:GetFileType 读取文件:ReadFile/ReadFileEx 写入文件:WriteFile/WriteFileEx 文件寻址:SetFilePointer 截断文件:SetEndOfFile 锁定内容:LockFile 解锁内容:UnlockFile 获取信息:GetFileInformationByHandle 同步缓冲:FlushFileBuffers 取消I/O:CancelIo 二、文件映射(需要句柄) 从文件名打开映射:OpenFileMapping 从句柄打开映射:CreateFileMapping 建立映射:MapViewOfFile/MapViewOfFileEx 同步映射:FlushViewOfFile 取消映射:UnmapViewOfFile 三、文件管理 移动文件:MoveFile 复制文件:CopyFile 删除文件:DeleteFile 设置属性:SetFileAttributes 获取属性:GetFileAttributes/GetFileAttributesEx 四、查找文件 查找第一个:FindFirstFile 查找下一个:FindNextFile 结束查找:FindClose 五、路径管理 设置编码:SetFileApisToANSI/SetFileApisToOEM 获取编码:AreFileApisANSI 获取8.3短路径:GetShortPathName 获取Win95长路径:GetLongPathName 获取绝对路径:GetFullPathName 六、临时文件 获取临时文件路径:GetTempPath 生成临时文件名:GetTempFileName 七、文件压缩 获取解压后的文件名:GetExpandedName 打开文件:LZOpenFile 分配资源:LZInit 读取文件:LZRead 文件寻址:LZSeek 直接解压:LZCopy 关闭文件:LZClose 八、目录管理 获取当前目录:GetCurrentDirectory 设置当前目录:SetCurrentDirectory 创建目录:CreateDirectory/CreateDirectoryEx 删除目录:RemoveDirectory 九、目录监控(配合WaitForSingleObject等函数使用) 获取第一个更改通知事件:FindFirstChangeNotification 获取下一个更改通知事件:FindNextChangeNotification 关闭更改通知事件:FindCloseChangeNotification 十、驱动器管理 获取可用驱动器:GetLogicalDrives/GetLogicalDriveStrings 获取信息:GetVolumeInformation 获取映射:QueryDosDevice 设置卷标:SetVolumeLabel 十一、磁盘管理 获取可用空间:GetDiskFreeSpace/GetDiskFreeSpaceEx 来源:MSDN Library for Visual Studio 2005

【常用WinAPI】Windows Internet 第一部分:数据转换 InternetCreateUrl:创建URL InternetCrackUrl:分析URL InternetCombineUrl:将相对URL合并到绝对URL InternetCanonicalizeUrl:处理URL中的不允许字符 InternetTimeFromSystemTime:转换系统时间到Internet时间 InternetTimeToSystemTime:转换Internet时间到系统时间 第二部分:通用Internet访问 InternetOpen:打开初级Internet句柄 InternetConnect:建立连接 InternetOpenUrl:打开URL InternetCloseHandle:关闭句柄 InternetQueryDataAvailable:确认数据大小 InternetReadFile:读取文件 InternetWriteFile:写入文件 InternetReadFileEx:读取文件(可异步) InternetSetFilePointer:设置文件指针 InternetLockRequestFile:锁住请求的文件 InternetUnlockRequestFile:解锁请求的文件 InternetGetCookie:获取Cookie InternetSetCookie:设置Cookie InternetGetCookieEx:获取第三方或特殊的Cookie InternetSetCookieEx:设置第三方或特殊的Cookie InternetQueryOption:返回句柄的选项 InternetSetOption:设置句柄的选项 InternetSetStatusCallback:设置状态回调 InternetFindNextFile:查找下一个文件 InternetGetLastResponseInfo:返回最近的响应信息 第三部分:网络连接类 InternetCheckConnection:检查网络连接 InternetInitializeAutoProxyDll:初始化自动代理DLL InternetDeInitializeAutoProxyDll:反初始化自动代理DLL InternetGetConnectedState:获取网络连接状态 InternetGetConnectedStateEx:获取指定网络连接状态 InternetGetProxyInfo:获取访问指定URL的代理信息 DetectAutoProxyUrl:探测自动代理URL 第四部分:网络连接UI InternetDial:进行拨号 InternetHangUp:挂断拨号 InternetAutodial:进行自动拨号 InternetAutodialHangup:断开自动拨号 InternetAttemptConnect:尝试网络连接 第五部分:通用其它UI InternetGoOnline:通知用户确认连接指定URL InternetConfirmZoneCrossing:通知用户确认HTTP/HTTPS跨越 InternetErrorDlg:显示错误对话框 第六部分:HTTP特定API HttpOpenRequest:打开请求 HttpAddRequestHeaders:添加请求头部 HttpQueryInfo:获取信息 HttpSendRequest:发送请求 HttpSendRequestEx:扩展的发送请求 HttpEndRequest:结束迭代式请求 第七部分:FTP特定API FtpCommand:发送命令 FtpCreateDirectory:创建目录 FtpRemoveDirectory:移除目录 FtpGetCurrentDirectory:返回当前目录 FtpSetCurrentDirectory:设置当前目录 FtpFindFirstFile:查找第一个文件 FtpGetFile:下载文件 FtpPutFile:上传文件 FtpGetFileSize:获取文件大小 FtpOpenFile:打开文件 FtpRenameFile:重名名文件 FtpDeleteFile:删除文件 第八部分:Gopher特定API GopherAttributeEnumerator GopherCreateLocator GopherFindFirstFile GopherGetAttribute GopherGetLocatorType GopherOpenFile 第九部分:缓存API CommitUrlCacheEntry:设置指定URL的缓存文件 CreateUrlCacheEntry:创建指定URL的缓存文件 DeleteUrlCacheEntry:删除指定URL的缓存文件 FindFirstUrlCacheEntry:查找第一个缓存文件 FindNextUrlCacheEntry:查找下一个缓存文件 FindFirstUrlCacheEntryEx:选择性查找第一个缓存文件 FindNextUrlCacheEntryEx:选择性查找下一个缓存文件 FindCloseUrlCache:关闭查找句柄 GetUrlCacheEntryInfo:返回缓存文件信息 SetUrlCacheEntryInfo:设置缓存文件信息 GetUrlCacheEntryInfoEx:考虑脱机模式下的重定向并返回缓存文件的信息 RetrieveUrlCacheEntryFile:锁住缓存文件 RetrieveUrlCacheEntryStream:打开缓存文件 ReadUrlCacheEntryStream:读取缓存文件 CreateUrlCacheGroup:创建缓存组 DeleteUrlCacheGroup:删除缓存组 SetUrlCacheEntryGroup:向缓存组添加或删除缓存文件 FindFirstUrlCacheGroup:查找第一个缓存组 FindNextUrlCacheGroup:查找下一个缓存组 GetUrlCacheGroupAttribute:返回缓存组的信息 SetUrlCacheGroupAttribute:设置缓存组信息 第十部分:其它 CreateMD5SSOHash:创建Microsoft Passport密码MD5摘要


Visual C++程序应该使用什么字符集,应该根据使用的需求来决定。 一、学习C/C++语言基础,编写可移植程序 如果是这个目的的话,使用ANSI C/C++函数和默认字符集就可以接受了,没必要使用其它字符集。因为对于大多数中国人来说,GBK字符集已经够用,并且默认字符集用char数组就可以表示,可以避免大多数麻烦的情况。(实际上,GBK当年是按“暂时代替Unicode”的字符集来设计的) 二、编写现代Windows程序 1.程序内码 程序内码应选择UTF-16,并使用wchar_t数组存储字符串。默认字符集(称为ANSI字符集)是为了兼容16位平台和Win9x平台存在的。现代Windows都是以UTF-16为内码的。在Windows中,使用老旧的默认字符集(GBK等)会频繁操作进程的堆内存用以转换字符串,降低程序效率。同时,不能在默认字符集表示的字符会变为问号,影响程序的稳定性。 使用UTF-16的好处显而易见,新加入的WinAPI、所有的COM组件、.NET和Java都使用UTF-16,使用UTF-16可以更方便地调用这些组件。UTF-16字符串处理起来也更加简单。 2.文件编码 保存文件时应根据需要选择ANSI、UTF-8、UTF-16。例如批处理文件和vbs文件必须以ANSI编码,INI配置文件可以用ANSI或UTF-16编码,而XML文件则通常以UTF-8编码。如果是你自己使用的文件,用什么编码都无所谓。要注意ANSI编码可能导致信息丢失,而UTF-8或UTF-16则不会。 3.头文件 在引用windows.h时,应该提前将预处理器符号UNICODE设为1,以避免误用ANSI函数导致编译错误: #define UNICODE 1 // 强制使用Unicode,对于windows.h #include <windows.h> 对于其它头文件(包括C运行库、MFC和ATL),应该将_UNICODE设为1,同时要保证_MBCS不被定义。在所有头文件之前可这样定义: #undef _MBCS #define UNICODE 1 // 强制使用Unicode,对于windows.h #define _UNICODE 1 // 强制使用Unicode,对于所有其它头文件 4.入口点 传统的main和WinMain使用ANSI,新程序应该使用wmain和wWinMain。其中wmain的形式是 int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) 而wWinMain的形式是 int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR lpCmdLine, int nCmdShow); 对于MinGW等其它编译器,C++模式下可能对wmain和wWinMain无法正确识别而编译为带重载修饰的符号,导致链接器报错无法找到_wmain或wWinMain。始终在前面添加extern "C"前缀是好的习惯: extern "C" int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) extern "C" int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR lpCmdLine, int nCmdShow); 5.运行库的调整 在Visual C++运行库中,有不少ANSI标准没有提及的宽字符函数(如_wfopen等),大多数系统相关函数都以_w开头,可以在宽字符环境下使用,并且支持Unicode字符。Visual C++中所有的宽字符函数见我的另一个帖子: http://bbs.kechuang.org/read/78249 对于Visual C++运行库,初始状态下区域设定为L"C",在Windows下表示纯英文,系统在进行字符转换时,无法正确处理中文。因此必须在程序开始时手动读取系统的区域设定(需要包含locale.h): _wsetlocale(LC_ALL, L""); 6.控制台程序 对于控制台程序,默认情况下是调用ReadFile/WriteFile或ReadConsoleA/WriteConsoleA进行输入输出的,依赖于控制台代码页。Visual C++ 2005以后,运行库支持使用Unicode模式的控制台,使用ReadConsoleW/WriteConsoleW进行输入输出,但需要自己设置模式(需要包含io.h和fcntl.h): _setmode(_fileno(stdin), _O_WTEXT); // VC2015和VC2005均测试通过,VC2010SP1乱码 _setmode(_fileno(stdout), _O_WTEXT); _setmode(_fileno(stderr), _O_WTEXT); 另一种可行的解决方案,是直接使用ReadConsole(W)和WriteConsole(W)进行输出,并使用swscanf和swprintf手动进行格式化。这种方法不受运行库版本的影响,但需要自己分配缓冲区。使用ReadConsole要注意三点,一是ReadConsole可以指定要读取的字符数,一次读取不完可以重复读取;二是读取后缓冲区是开放的,需要自己在buf[chrread]处添加L'\0'以终结字符串;三是这样读取的字符串回车符是L"\r\n"而不是L"\n"。 7.文本文件读写 打开文件一定要使用_wfopen或_wopen,以支持Unicode路径。_wfopen和_wopen的第二个参数可以指定读取方式,并且有对应关系: L"xxb" _O_BINARY 不转换换行符,原样输入输出【GUI/WinAPI程序建议使用】 L"xx" _O_TEXT 自动转换换行符,总是按ANSI读写(需设置区域以正确转换中文) L"xx, ccs=UNICODE" _O_WTEXT 自动转换换行符,编码按BOM决定(支持UTF-8和UTF-16LE,下同),无BOM按ANSI处理(需设置区域以正确转换中文) L"xx, ccs=UTF-8" _O_U8TEXT 自动转换换行符,编码按BOM决定,无BOM按UTF-8处理 L"xx, ccs=UTF-16LE" _O_U16TEXT 自动转换换行符,编码按BOM决定,无BOM按UTF-16LE处理 如果是GUI/WinAPI程序(包括使用WinAPI手动读写控制台的程序),应总是使用L"xxb"和_O_BINARY读写方式。这样的话,不会自动转换换行符(以避免与WinAPI交互时产生问题),使用fwprintf输出的是UTF-16LE,使用fprintf输出的则是ANSI。在文件的头部要手动添加BOM(\uFEFF)不然记事本中会乱码。 如果是使用C/C++标准库读取输入的控制台程序,可以在最后四种读写方式中选择一个使用。后三种读写方式不能使用fprintf等窄字符函数(会报错,主要是出于自动转换编码的考虑),使用fwprintf等宽字符函数可以正常读写。 8.字符串之间的转换 转换字符串的最简单方法是使用运行库函数: 第一种方法,使用snprintf(buf, bufsize, "%ls", srcstr);可以将宽字符转换为窄字符,使用swprintf和L"%hs"则可以转换为宽字符。(需设置区域以正确转换中文) 第二种方法,使用wcstombs和mbstowcs。(需设置区域以正确转换中文) 以上两种方法不支持UTF-8。 第三种方法,对于ANSI可以使用wstring_convert<codecvt<wchar_t, char, mbstate_t>>转换(需设置区域),对于UTF-8可以使用wstring_convert<codecvt_utf8_utf16<wchar_t>>转换(需新版编译器)。 通过标准库进行字符串转换有一个弊端,那就是遇到无效字符程序会崩溃。如果你不介意的话当然可以使用,更好的方法是使用WinAPI进行转换。 在Visual C++中,用WinAPI转换字符串,我们可以有五种方法可以选择: 第一种方法,如果某个WinAPI有ANSI形式(如SetWindowTextA),直接使用这个形式即可。此方法不支持UTF-8。 第二种方法,直接使用MultiByteToWideChar和WideCharToMultiByte进行转换。MBTWC有6个参数,WCTMB有8个参数,实际用到的只有第1,3,5,6四个参数,用法如下: int reqbufsz = MultiByteToWideChar(cp, 0, src, -1, 0, 0); // 获取所需缓冲区大小 MultiByteToWideChar(cp, 0, src, -1, buf, bufsz); // 进行转换 int reqbufsz = WideCharToMultiByte(cp, 0, src, -1, 0, 0, 0, 0); // 获取所需缓冲区大小 WideCharToMultiByte(cp, 0, src, -1, buf, bufsz, 0, 0); // 进行转换 第三种方法,通过_bstr_t这种字符串类型进行转换。_bstr_t以SysAllocString函数UTF-16保存字符串,可以随意从char*或wchar_t*初始化,也可以随意用作char*或wchar_t*类型,它会自动进行转换。此方法不支持UTF-8。 第四种方法,如果安装有ATL或正在使用MFC的话,可以使用CStringA和CStringW构造函数进行转换(需要包含atlbase.h和atlstr.h或使用MFC工程)。此方法不支持UTF-8。 第五种方法,如果安装有ATL或正在使用MFC的话,可以使用CW2A和CA2W构造函数进行转换(需要包含atlbase.h或使用MFC工程),第二个参数可以自定义代码页,支持UTF-8。 9.与.NET Framework的互操作 Visual C++支持与.NET Framework的System::String^的互操作,但是不能直接将System::String^转换为wchar_t*。需要使用msclr/marshal.h msclr/marshal_cppstd.h msclr/marshal_windows.h msclr/marshal_atl.h等头文件中的marshal_as<T>(位于msclr::interop命名空间)函数进行封装和解封装处理。 marshal_as<T>可以将System::String^与CString、CComBSTR、_bstr_t、std::wstring等自释放的C++对象进行自由转换,但是对于wchar_t*、BSTR等需要手动释放的指针来说,则需要先创建一个marshal_context作为容器,并使用它的成员函数marshal_context::marshal_as<T>进行转换。

已经过去了很长一段时间,感觉顶楼的有些地方值得重新考虑,这里重新总结一下。 这里将不会讨论任何与MFC有关的内容。因为MFC也需要熟练的Win32基础才能正确使用,现在来看,掌握Win32远比掌握MFC要重要得多。 https://msdn.microsoft.com/en-us/library/windows/desktop/dd374083(v=vs.85).aspx C/C++在Visual C++中的字符串编码 源代码可以保存为ANSI格式、Unicode格式(含BOM)、Unicode big endian格式(含BOM)、UTF-8(含BOM)。 char[]字符串字面量"xxx"所使用的编码,是编译时系统当前ANSI代码页的编码。简体中文系统中就是GBK。 wchar_t[]字符串字面量L"xxx"所使用的编码,是UTF-16编码。 C/C++运行库使用的编码,是运行时系统当前ANSI代码页的编码。 虽然C/C++运行库也支持wchar_t[],但是仅推荐使用少数功能,因为C90Amendment1大多数功能是在Unicode还没有普及的时候发明的,因此大多数功能实现得不可靠。建议使用WinAPI实现需要wchar_t[]的功能。 WinAPI头文件windows.h Windows系统调用是由Win32 API(WinAPI)进行的。Win32 API的最初版本是1993年的Windows NT,它设计了两套WinAPI: ANSI版本——以-A结尾,使用char[]作为字符串类型,使用ANSI字符集 Unicode版本——以-W结尾,使用wchar_t[]作为字符串类型,使用Unicode字符集 后者(Unicode版本)是Windows NT原生的,前者是Windows NT通过自动转换字符串模拟的。 使用WinAPI需要引入头文件windows.h。 如果在引入windows.h之前定义了UNICODE宏(即选择“使用Unicode字符集”),则会将没有结尾的WinAPI映射到-W结尾的WinAPI上;如果没有定义这个宏(即选择“使用多字节字符集”或“未设置”),则会映射到-A结尾的WinAPI上。 为了表示通用的WinAPI字符类型,头文件定义了一个类型TCHAR,当定义了UNICODE宏,TCHAR它会映射到wchar_t,否则会映射到char;为了重定向字面量,定义了一个宏TEXT(x),当定义了UNICODE宏,TEXT("xxx")会映射到L"xxx",否则会映射到"xxx"。 引入tchar.h后,可以使用_TCHAR类型,以及更简短的_T("xxx")宏,定义_UNICODE宏以后,会映射到L"xxx",否则会映射到"xxx"。 为了windows.h与tchar.h的一致性,定义UNICODE宏的同时需要定义带下划线的_UNICODE宏,以同时使用宽字符映射,没有定义UNICODE宏的情况下同时定义_MBCS宏,以支持GBK等双字节字符串处理。 对字符串进行转换 **字节字符串与Unicode宽字符串之间转换,比较可靠的方法是使用WideCharToMultiByte和MultiByteToWideChar。**它的优点是稳定性强,并且支持ANSI、OEM、UTF-7、UTF-8等多种字符集,用对应的代码页表示。 ANSI代码页:0(CP_ACP)(Windows代码页) OEM代码页:1(CP_OEMCP)(DOS代码页) UTF-7代码页:65000(CP_UTF7) UTF-8代码页:65001(CP_UTF8) 如果在某些地方因为预处理宏冲突等问题不能引入windows.h,可以使用如下函数定义: __declspec(dllimport) int __stdcall MultiByteToWideChar( unsigned int codepage, unsigned long flags, const char *srcstr, int srcsize, wchar_t *dststr, int dstsize); __declspec(dllimport) int __stdcall WideCharToMultiByte( unsigned int codepage, unsigned long flags, const wchar_t *srcstr, int srcsize, char *dststr, int dstsize, const char *defchr, int *useddefchr); 示例代码: // 日常科普:char和wchar_t转换 // wchar_t[]转char[] wchar_t src_wstr[] = L"convert from wchar_t[] to char[]\r\n"; // 短字符串 char dst_str_short[1024] = ""; WideCharToMultiByte(CP_ACP, 0, src_wstr, -1, dst_str_short, 1024, NULL, NULL); OutputDebugStringA(dst_str_short); // 使用char[] // 长字符串 char *dst_str_long = NULL; do // 多步可能失败的操作,用do-while(0)-break { int dst_str_cchsize = WideCharToMultiByte(CP_ACP, 0, src_wstr, -1, NULL, 0, NULL, NULL); if (dst_str_cchsize <= 0) break; dst_str_long = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dst_str_cchsize * sizeof (char)); if (dst_str_long == NULL) break; WideCharToMultiByte(CP_ACP, 0, src_wstr, -1, dst_str_long, dst_str_cchsize, NULL, NULL); // 下面是使用char[]的代码 OutputDebugStringA(dst_str_long); }while(0); if (dst_str_long != NULL) // do-while(0)-break之后只有清理代码 { HeapFree(GetProcessHeap(), 0, dst_str_long); dst_str_long = NULL; } // char[]转wchar_t[] char src_str[] = "from char[] to wchar_t[]\r\n"; // 短字符串 wchar_t dst_wstr_short[1024] = L""; MultiByteToWideChar(CP_ACP, 0, src_str, -1, dst_wstr_short, 1024); OutputDebugStringW(dst_wstr_short); // 使用wchar_t[] // 长字符串 wchar_t *dst_wstr_long = NULL; do // 多步可能失败的操作,用do-while(0)-break { int dst_wstr_cchsize = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0); if (dst_wstr_cchsize <= 0) break; dst_wstr_long = (wchar_t *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dst_wstr_cchsize * sizeof (wchar_t)); if (dst_wstr_long == NULL) break; MultiByteToWideChar(CP_ACP, 0, src_str, -1, dst_wstr_long, dst_wstr_cchsize); // 下面是使用wchar_t[]的代码 OutputDebugStringW(dst_wstr_long); }while(0); if (dst_wstr_long != NULL) // do-while(0)-break之后只有清理代码 { HeapFree(GetProcessHeap(), 0, dst_wstr_long); dst_wstr_long = NULL; } 文本的处理 ANSI文本:建议使用系统的CharNextExA、CharPrevExA或_mbsinc、_mbsdec函数处理,而不要使用自己编写的算法。 UTF-8文本:建议直接转换为Unicode(宽字符)处理。如果要手工处理UTF-8文本,一定要在处理之前把char *指针转换为unsigned char *指针,不然由于char通常是有符号的,往往会导致编写的多字节算法无效。 宽字符文本:一般可以直接使用C运行库的wcs系列函数处理。如果需要获得单个码点的值,或者数码点数,需要使用IS_SURROGATE_PAIR判断UTF-16代理对。如果需要数复杂文字的组合字符数,需要使用CharNextW或CharPrevW函数,或者使用Uniscribe复杂文字分析技术。 引入tchar.h后,可以使用_tcslen、_tcscpy、_tcscat、_tcstol、_tcstoi64、_tcstod等函数,它们随_UNICODE宏和_MBCS宏是否定义而被映射到strxxx、wcsxxx、_mbsxxx函数。其中_tcstod(strtod、wcstod)函数是比较重要的,它可以将字符串转换为浮点数,这个功能很难用WinAPI实现。 引入strsafe.h后,可以使用StringCbXxxxx和StringCchXxxxx系列,前者要求缓冲区的字节长度,后者要求缓冲区的字符长度,它们实际上是包装了运行库函数的缓冲区安全化内联函数。同时,它们和WinAPI的规则是相同的,也使用UNICODE宏以及-W和-A结尾。其中StringCbPrintf和StringCchPrintf函数是比较重要的,它们可以将浮点数转换为字符串,这个功能很难用WinAPI实现。 命令参数 Visual C++支持宽字符入口点,通过它们可以获取获取宽字符命令参数。 int wmain(int argc, wchar_t *argv[]); int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *szCmdLine, int nShowCmd); 引入tchar.h后,可以使用_tmain和_tWinMain宏表示入口点。 int _tmain(int argc, _TCHAR *argv[]); int __stdcall _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _TCHAR *szCmdLine, int nShowCmd); 使用这个入口点以后,只会初始化_wenviron全局变量,environ全局变量并没有被自动初始化,不过,只需调用一次getenv("")即可修复这个问题。 除此之外,使用GetCommandLineW可以在任何地方获取Unicode宽字符命令参数。GetCommandLineW返回的是固定地址,不需要释放。使用GetCommandLineA可以获取相应的多字节版本命令参数。 使用CommandLineToArgvW可以获取Unicode宽字符版本的argv。CommandLineToArgvW使用LocalAlloc分配了一个内存块,使用完需要使用LocalFree释放。它只能在Windows 2000以上操作系统使用。 #include <windows.h> int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *szCmdLine, int nShowCmd) { // 主程序:获取Unicode宽字符版本的命令参数 int argcW = 0; wchar_t **argvW = CommandLineToArgvW(GetCommandLineW(), &argcW); if (argvW) { for (int i = 0; i < argcW; i++) { MessageBox(NULL, argvW[i], L"命令参数", MB_OK); } LocalFree(argvW); } return 0; } 文件I/O 一般来说,使用stdio.h就足够了,可使用fopen、_wfopen、_tfopen打开文件,fclose关闭文件。读写使用fread、fwrite或fgets、fputs等其它函数。 如果想要按照\n读取换行符,则应使用文本模式"r/w/a[+]"。 如果想要按照\r\n读取换行符,则应使用二进制模式"r/w/a[+]b"。 可以考虑使用open、_wopen、_topen、sopen、_wsopen、_tsopen打开文件,使用_read和_write读写文件。如果需要转换\n与\r\n换行符,使用_O_TEXT,否则使用_O_BINARY。 如果需要临时禁止转换换行符,使用_setmode(_fileno(stdxxx), _O_BINARY);。使用_setmode(_fileno(stdxxx), _O_TEXT);恢复转换换行符。在VC++2015之前并不支持freopen(NULL, "xxx");这种做法。 如果需要更高的性能和灵活性,可以使用CreateFile打开文件,使用CloseHandle关闭文件。使用ReadFile和WriteFile读写文件。 要注意的是,stdio.h读写文件,对于普通文件和管道或设备,具有一致的行为,而后两种方法,则对文件和管道或设备具有不一致的行为,更加贴近底层一些。 不要尝试使用", ccs=[UNICODE|UTF-16|UTF-8]"、 O [W|U8|U16]TEXT,它是VC++2005时期微软的中二产物,手工转换虽然麻烦一点,但可靠性高。 标准I/O和控制台I/O I/O编程实际上分为两个方向:面向一般设备(普通文件、管道、conin$/conout$等)、面向控制台(conin$/conout$)。 面向一般设备(普通文件、管道、conin$/conout$等) 直接使用stdio.h中的printf/scanf/fgets/puts/getchar/putchar等函数即可,并且为了和其它部分的C/C++程序统一编码,一般假定它们是ANSI。 不要和attrib/tree/more这些MS-DOS工具链统一编码。微软为了避免用户产生乱码的困惑,因此它们被设计为使用OEM代码页,是仿古程序(仿MS-DOS程序)。但是实际上仿古程序的编程是很复杂的,一般人不需要也没必要知道。 不建议直接使用_read/_write或ReadFile/WriteFile,因为普通文件、管道、conin$/conout$的I/O特性各不相同,如果要同时支持这些东西,相当于重新写了一遍stdio.h的函数。 面向控制台(conin$/conout$) 使用GetStdHandle(x)函数可以直接获取标准输入、标准输出、标准错误的Win32文件句柄,参数x可以是STD_INPUT_HANDLE、STD_OUTPUT_HANDLE、STD_ERROR_HANDLE。它们也可以通过(HANDLE)_get_osfhandle(_fileno(stdxxx))获取,如果你要与标准库同步的话。 除此之外,还可以使用CreateFile自行打开conin$/conout$。一定要使用如下标准形式。 HANDLE hconin = CreateFile(TEXT("conin$"), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); HANDLE hconout = CreateFile(TEXT("conout$"), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); 可以使用ReadConsole/WriteConsole,其中ReadConsoleA/WriteConsoleA可以支持控制台当前代码页(可以通过GetConsoleCP()/GetConsoleOutputCP()获得),而ReadConsoleW/WriteConsoleW支持Unicode。可以支持。使用这些函数要注意几点: 请求字符数不要低于256字符,因为行缓冲最小是256字符(包含\r\n)。 请求字符数不要高于4096字符,C运行库的默认缓冲区大小,因为太大的请求字符数在WinXP/2003/Vista/7上容易RPC失败,4096字符已经能够满足大多数应用需要了。Win8+因为改成NtDeviceIoControlFile了,因此已经没有这个问题,但应该考虑兼容性问题。 ReadConsoleA缓冲区尽量大一个字节,因为双字节代码页(如936)中这玩意的实现有bug,容易缓冲区溢出。 UTF-8代码页65001虽然可以通过chcp切换过去,但是ReadConsoleA会出问题,因为它只是为了单字节代码页(如437)和双字节代码页(如936)设计的。 如何看待奇葩且难以改变的Win32编码体系? 为什么Windows的编码设定这么奇葩?ANSI字符集被淘汰了吗?ANSI字符集是为了Windows 95/98/Me而生的吗?在936之外,需要兼容932、949、950、1251、1252等其它ANSI代码页的系统吗?文本文件的编码情况如何?控制台的编码情况如何?文本文件的编码情况如何? Windows并不是可以随意重编译的开源生态,而是二进制兼容的闭源生态。ANSI的WinAPI,编码无法随意升级,只能停留在上个世纪90年代的水平。Unicode的WinAPI,编码也不能改为UTF-8或UTF-32,只能使用UTF-16。 ANSI字符集并没有被淘汰,仍然是必需的。在编程中,我们一般认为ANSI和Unicode都够用了,使用Unicode更好,使用ANSI也是可以接受的。至于在用户名上使用emoji会发生什么困扰,是用户自己作死的事情。 在基于Windows NT的Windows操作系统中,ANSI字符集和Unicode字符集在Win32编程中地位是相同的。ANSI字符集是为了便于从(当年的)其它平台迁移而生的,远早于Windows 95/98/Me的出现,所以ANSI字符集不是为了Windows 95/98/Me而存在的。 如果程序要求GBK字符集,则没有必要兼容这些代码页,因为即使兼容了这些代码页,中文也会变成一堆问号。如果程序只要求ASCII字符集,可以考虑支持其它代码页。 在Windows中,文本文件分为ANSI、Unicode、Unicode big endian、UTF-8文本文件,其中后三者分别要以\xFF\xFE、\xFE\xFF、\xEF\xBB\xBF这些BOM序列开头,但是也经常遇到不包含BOM的UTF-8。通常的做法是,不带BOM的文件默认为ANSI,带有BOM的支持Unicode、UTF-8两种即可。可以放弃支持ANSI转而支持不带BOM的UTF-8,但是 一定要同时支持带有BOM的UTF-8,不然肯定会对用户产生困扰。 控制台的编码情况比较复杂。控制台交互使用的是Unicode,但是控制台程序的文本流I/O(管道或重定向文件)并没有统一的编码标准,实际使用的可能是GetConsoleCP()和GetConsoleOutputCP()代码页的编码,但也有可能是固定为ANSI或OEM或437代码页的编码,甚至可能是UTF-8编码。对于控制台程序的文本流I/O,一般可以认为是代码页不确定的ASCII文本,应该 提供选项让用户选择可读的编码。


- 以上是置顶 -


VC制作的软件,从VC2005之后的都需安装运行库才可以运行,从xp到win10一直都没改变,而且这货,上下版本还不能互相兼容orz这就是为啥在添加和删除程序里能看到各版本VC运行库的原因。 在某些情况下,比如绿色软件或者在pe下运行的软件,运行库可能带不全或者不好安装,于是制作一个不需要VC运行库的程序就是很有必要的了 首先创建一个VC2015的Win32 Project,选择默认选项创建一个窗体程序 打开菜单Project-> xxx Property(xxx是项目名)或者直接按Alt+F7, 选择Configuration为All Configuration, Platform为All Platform 将Basic Runtime Checks改为Default 将Security Check改为Disable Security Check(/GS-) 在stdafx.h的#progma once下面添加: #ifdef _DEBUG #pragma comment (linker, "/nodefaultlib:msvcrtd.lib") #else #pragma comment (linker, "/nodefaultlib:msvcrt.lib") #endif 这时候编译一般会出现一堆堆的错误 首先解决LNK2001 unresolved external symbol wWinMainCRTStartup, wWinMainCRTStartup是VC的CRT定义的Win32程序入口点,MFC程序为WinMainCRTStartup,控制台和DLL为mainCRTStartup和_DllMainCRTStartup。 没有了运行库就没有了入口点orz没有入口点就创建一个咯,wWinMainCRTStartup不接收任何参数,我写的入口如下,可以放在stdafx.cpp里: int __cdecl wWinMainCRTStartup() {     STARTUPINFO info;     info.cb = sizeof(STARTUPINFO);     GetStartupInfo(&info);        int ret = wWinMain(GetModuleHandle(NULL),         0,         GetCommandLine(),         info.dwFlags & STARTF_USESHOWWINDOW ? info.wShowWindow : SW_SHOWDEFAULT);     ExitProcess(ret);     return ret; } 之后应该就可以顺利编译工程了,这样制作出来的程序和静态链接的程序比起来,体积极小 本方法并不是完美的,有这些缺点: C++的异常不能用了, new delete也不能用了,也就是说这时候C++基本完全废了 C和C++的标准库大部分也都不能用了,不过这个有解决方法,我自己想出来的,可能不是很完美,就是手动实现某些stdlib里的函数,给了些常用的实现,都是互联网上搜索的: void* __cdecl malloc(ULONG_PTR uSize) {     return (void *)HeapAlloc(GetProcessHeap(), 0, uSize); }    void __cdecl free(LPVOID pMemBlock) {     HeapFree(GetProcessHeap(), 0, (LPVOID)pMemBlock); } #pragma function(memcpy) void* memcpy(void * dst, const void * src, size_t count) {     void * ret = dst;    #if defined (_M_IA64)     {         extern void RtlMoveMemory(void *, const void *, size_t count);            RtlMoveMemory(dst, src, count);     } #else /* defined (_M_IA64) */     /*     * copy from lower addresses to higher addresses     */     while (count--) {         *(char *)dst = *(char *)src;         dst = (char *)dst + 1;         src = (char *)src + 1;     } #endif /* defined (_M_IA64) */        return(ret); }    #pragma function(memset) void* __cdecl memset(void* src, int c, size_t count) {     char *tmpsrc = (char*)src;     while (count--)         *tmpsrc++ = (char)c;     return src; } 有的时候编译会看到俩错误,找不到__imp__invalid_parameter_noinfo和__imp__errno,并不晓得这俩是啥,于是空函数代替之: extern "C" void __cdecl __imp__invalid_parameter_noinfo() {    }    extern "C" void __cdecl __imp__errno() {    } 还有个说不清的问题,就是调用COM组件的时候有点奇怪。。 还有,MFC不适用!也就是说,复杂的软件就不要用这种方法咯 参考 http://bbs.kechuang.org/read/80005 http://stackoverflow.com/questions/2938966/how-to-use-vc-intrinsic-functions-w-o-run-time-library




之所以说是常规加速版,是因为DX11还有GPGPU计算加速功能Compute Shader,也就是所谓的DirectCompute,但是这里我们只用到了常规的图形流水线加速功能。 最低配置被设置为D3D_FEATURE_LEVEL_9_3和"ps_4_0_level_9_3"。如果将循环次数改多一点,由于"ps_4_0_level_9_3"不包含条件循环指令,循环操作实际上被展开了,指令数量很容易超过限制,这个时候就需要把最低配置改为D3D_FEATURE_LEVEL_10_0和"ps_4_0"。 // shaders.txt struct VS_OUTPUT { float4 pos : SV_POSITION; float2 tex : TEXCOORD; }; void vs_common(float3 pos : POSITION, float2 tex : TEXCOORD, out VS_OUTPUT vso) { vso.pos = float4(pos, 1.0f); vso.tex = tex; } float4 ps_mandelbrot(VS_OUTPUT vso) : SV_TARGET { float2 zvar = float2(0, 0); float i; for (i = 0; i < 16 && zvar.x * zvar.x + zvar.y * zvar.y <= 4; i++) { float2 zvar2 = zvar; zvar.x = zvar2.x * zvar2.x - zvar2.y * zvar2.y + vso.tex.x; zvar.y = zvar2.x * zvar2.y + zvar2.y * zvar2.x + vso.tex.y; } return float4(i / 16.0, 0.0f, 0.0f, 1.0f); } float4 ps_julia(VS_OUTPUT vso) : SV_TARGET { float2 zvar = vso.tex; float i; for (i = 0; i < 16 && zvar.x * zvar.x + zvar.y * zvar.y <= 4; i++) { float2 zvar2 = zvar; zvar.x = zvar2.x * zvar2.x - zvar2.y * zvar2.y + 0.4f; zvar.y = zvar2.x * zvar2.y + zvar2.y * zvar2.x + 0.3f; } return float4(i / 16.0, 0.0f, 0.0f, 1.0f); } // main.c - 使用了C99语法,可使用VS2013-2017编译 #undef UNICODE #define UNICODE 1 #include <windows.h> #include <assert.h> #include <d3d11.h> #include <d3dcompiler.h> #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "d3dcompiler.lib") #define NOFAIL(x) assert(SUCCEEDED(x)) #define NOTZERO(x) assert(x) #define CLEANUP(x) if (x) { (x)->lpVtbl->Release(x); (x) = NULL; } HINSTANCE g_hinst; HWND g_hmainwnd; // DirectX D3D_FEATURE_LEVEL g_fl; ID3D11Device *g_dev; ID3D11DeviceContext *g_immctx; IDXGISwapChain *g_swpch; ID3D11Texture2D *g_backbuffer; ID3D11RenderTargetView *g_backbuffer_rtv; // DirectX资源 ID3D11VertexShader *g_vs_common; ID3D11PixelShader *g_ps_mandelbrot; ID3D11PixelShader *g_ps_julia; ID3D11PixelShader *g_ps_current; ID3D11InputLayout *g_inlayout; ID3D11Buffer *g_vertexbuffer; LRESULT MainWnd_OnCreate(HWND hWnd, LPCREATESTRUCT lpcs) { // 创建DirectX RECT rc; GetClientRect(hWnd, &rc); DXGI_MODE_DESC backbuf_mode_desc = { rc.right - rc.left, rc.bottom - rc.top, { 60, 1 }, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_SCALING_UNSPECIFIED, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, }; DXGI_SWAP_CHAIN_DESC swpch_desc = { backbuf_mode_desc, { 1, 0 }, DXGI_USAGE_RENDER_TARGET_OUTPUT, 1, hWnd, TRUE, DXGI_SWAP_EFFECT_DISCARD, 0, }; D3D_FEATURE_LEVEL fllist[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, }; if (FAILED(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, fllist, sizeof fllist / sizeof fllist[0], D3D11_SDK_VERSION, &swpch_desc, &g_swpch, &g_dev, &g_fl, &g_immctx))) { NOFAIL(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_WARP, NULL, 0, fllist, sizeof fllist / sizeof fllist[0], D3D11_SDK_VERSION, &swpch_desc, &g_swpch, &g_dev, &g_fl, &g_immctx)); } IDXGIFactory *dxgifac; NOFAIL(g_swpch->lpVtbl->GetParent(g_swpch, &IID_IDXGIFactory, (void**)&dxgifac)); NOFAIL(dxgifac->lpVtbl->MakeWindowAssociation(dxgifac, hWnd, DXGI_MWA_NO_ALT_ENTER)); CLEANUP(dxgifac); NOFAIL(g_swpch->lpVtbl->GetBuffer(g_swpch, 0, &IID_ID3D11Texture2D, (void**)&g_backbuffer)); NOFAIL(g_dev->lpVtbl->CreateRenderTargetView(g_dev, (ID3D11Resource *)g_backbuffer, NULL, &g_backbuffer_rtv)); // 创建绘制资源 // 读取shaders.txt static char shaders_txt[65536]; memset(shaders_txt, 0, sizeof shaders_txt); HANDLE hfile = CreateFile(L"shaders.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (hfile == INVALID_HANDLE_VALUE) { MessageBox(hWnd, L"不能打开shader.txt文件", L"文件错误", MB_OK); DestroyWindow(hWnd); return 0; } DWORD dwret; ReadFile(hfile, shaders_txt, sizeof shaders_txt - 1, &dwret, NULL); CloseHandle(hfile); // 编译shaders.txt并创建相关资源 ID3DBlob *bcode_ps_mandelbrot, *bcode_ps_julia, *bcode_vs_common, *errmsg; if (FAILED(D3DCompile(shaders_txt, strlen(shaders_txt), "shaders.txt", NULL, NULL, "ps_mandelbrot", "ps_4_0_level_9_3", 0, 0, &bcode_ps_mandelbrot, &errmsg)) || FAILED(D3DCompile(shaders_txt, strlen(shaders_txt), "shaders.txt", NULL, NULL, "ps_julia", "ps_4_0_level_9_3", 0, 0, &bcode_ps_julia, &errmsg)) || FAILED(D3DCompile(shaders_txt, strlen(shaders_txt), "shaders.txt", NULL, NULL, "vs_common", "vs_4_0_level_9_3", 0, 0, &bcode_vs_common, &errmsg))) { NOTZERO(errmsg); MessageBoxA(hWnd, errmsg->lpVtbl->GetBufferPointer(errmsg), "D3DCompile Error", MB_OK); CLEANUP(errmsg); CLEANUP(bcode_ps_mandelbrot); CLEANUP(bcode_ps_julia); CLEANUP(bcode_vs_common); DestroyWindow(hWnd); return 0; } NOFAIL(g_dev->lpVtbl->CreatePixelShader(g_dev, bcode_ps_mandelbrot->lpVtbl->GetBufferPointer(bcode_ps_mandelbrot), bcode_ps_mandelbrot->lpVtbl->GetBufferSize(bcode_ps_mandelbrot), NULL, &g_ps_mandelbrot)); NOFAIL(g_dev->lpVtbl->CreatePixelShader(g_dev, bcode_ps_julia->lpVtbl->GetBufferPointer(bcode_ps_julia), bcode_ps_julia->lpVtbl->GetBufferSize(bcode_ps_julia), NULL, &g_ps_julia)); NOFAIL(g_dev->lpVtbl->CreateVertexShader(g_dev, bcode_vs_common->lpVtbl->GetBufferPointer(bcode_vs_common), bcode_vs_common->lpVtbl->GetBufferSize(bcode_vs_common), NULL, &g_vs_common)); D3D11_INPUT_ELEMENT_DESC inputelements[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; NOFAIL(g_dev->lpVtbl->CreateInputLayout(g_dev, inputelements, sizeof inputelements / sizeof inputelements[0], bcode_vs_common->lpVtbl->GetBufferPointer(bcode_vs_common), bcode_vs_common->lpVtbl->GetBufferSize(bcode_vs_common), &g_inlayout)); CLEANUP(bcode_ps_mandelbrot); CLEANUP(bcode_ps_julia); CLEANUP(bcode_vs_common); float vertexdata[] = { -1.0f, -1.0f, 1.0f, -2.2f, -2.2f, -1.0f, 1.0f, 1.0f, -2.2f, 2.2f, 1.0f, -1.0f, 1.0f, 2.2f, -2.2f, 1.0f, 1.0f, 1.0f, 2.2f, 2.2f, }; D3D11_BUFFER_DESC bd = { sizeof vertexdata, D3D11_USAGE_DEFAULT, 0, 0, 0 }; D3D11_SUBRESOURCE_DATA srd = { vertexdata, 0, 0 }; NOFAIL(g_dev->lpVtbl->CreateBuffer(g_dev, &bd, &srd, &g_vertexbuffer)); g_ps_current = g_ps_mandelbrot; return 0; } LRESULT MainWnd_OnDestroy(HWND hWnd) { if (g_immctx) g_immctx->lpVtbl->ClearState(g_immctx); // 清理资源 CLEANUP(g_vertexbuffer); CLEANUP(g_inlayout); CLEANUP(g_vs_common); CLEANUP(g_ps_julia); CLEANUP(g_ps_mandelbrot); // 清理DirectX CLEANUP(g_backbuffer_rtv); CLEANUP(g_backbuffer); CLEANUP(g_swpch); CLEANUP(g_immctx); CLEANUP(g_dev); // 产生WM_QUIT消息,结束消息循环 PostQuitMessage(0); return 0; } LRESULT MainWnd_OnSize(HWND hWnd, WPARAM wParam, POINTS pts) { if (g_swpch && g_immctx && g_dev) { g_immctx->lpVtbl->ClearState(g_immctx); CLEANUP(g_backbuffer_rtv); CLEANUP(g_backbuffer); RECT rc; GetClientRect(hWnd, &rc); NOFAIL(g_swpch->lpVtbl->ResizeBuffers(g_swpch, 1, rc.right - rc.left, rc.bottom - rc.top, DXGI_FORMAT_UNKNOWN, 0)); NOFAIL(g_swpch->lpVtbl->GetBuffer(g_swpch, 0, &IID_ID3D11Texture2D, (void**)&g_backbuffer)); NOFAIL(g_dev->lpVtbl->CreateRenderTargetView(g_dev, g_backbuffer, NULL, &g_backbuffer_rtv)); } return 0; } LRESULT MainWnd_OnPaint(HWND hWnd) { // GDI例行任务 PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); // DirectX任务 // 清除背景 float backcolor[] = { 0.0f, 1.0f, 1.0f, 1.0f }; g_immctx->lpVtbl->ClearRenderTargetView(g_immctx, g_backbuffer_rtv, backcolor); // 设置状态并绘图 RECT rc; GetClientRect(hWnd, &rc); float ratio = (float)(rc.right - rc.left) / (float)(rc.bottom - rc.top); float vertexdata[] = { -1.0f, -1.0f, 1.0f, -2.2f * ratio, -2.2f, -1.0f, 1.0f, 1.0f, -2.2f * ratio, 2.2f, 1.0f, -1.0f, 1.0f, 2.2f * ratio, -2.2f, 1.0f, 1.0f, 1.0f, 2.2f * ratio, 2.2f, }; g_immctx->lpVtbl->UpdateSubresource(g_immctx, (ID3D11Resource *)g_vertexbuffer, 0, NULL, vertexdata, 0, 0); UINT stride = sizeof(float) * 5, offset = 0; g_immctx->lpVtbl->IASetVertexBuffers(g_immctx, 0, 1, &g_vertexbuffer, &stride, &offset); g_immctx->lpVtbl->IASetInputLayout(g_immctx, g_inlayout); g_immctx->lpVtbl->IASetPrimitiveTopology(g_immctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); g_immctx->lpVtbl->VSSetShader(g_immctx, g_vs_common, NULL, 0); D3D11_VIEWPORT viewport = { 0.0f, 0.0f, rc.right - rc.left, rc.bottom - rc.top, 0.0f, 1.0f }; g_immctx->lpVtbl->RSSetViewports(g_immctx, 1, &viewport); g_immctx->lpVtbl->PSSetShader(g_immctx, g_ps_current, NULL, 0); g_immctx->lpVtbl->OMSetRenderTargets(g_immctx, 1, &g_backbuffer_rtv, NULL); g_immctx->lpVtbl->Draw(g_immctx, 4, 0); // 上屏 NOFAIL(g_swpch->lpVtbl->Present(g_swpch, 0, 0)); return 0; } LRESULT MainWnd_OnLButtonDown(HWND hWnd, WPARAM wParam, POINTS pts) { g_ps_current = g_ps_mandelbrot; InvalidateRect(hWnd, NULL, FALSE); return 0; } LRESULT MainWnd_OnRButtonDown(HWND hWnd, WPARAM wParam, POINTS pts) { g_ps_current = g_ps_julia; InvalidateRect(hWnd, NULL, FALSE); return 0; } LRESULT __stdcall WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: return MainWnd_OnCreate(hWnd, (LPCREATESTRUCT)lParam); case WM_DESTROY: return MainWnd_OnDestroy(hWnd); case WM_SIZE: return MainWnd_OnSize(hWnd, wParam, MAKEPOINTS(lParam)); case WM_LBUTTONDOWN: return MainWnd_OnLButtonDown(hWnd, wParam, MAKEPOINTS(lParam)); case WM_RBUTTONDOWN: return MainWnd_OnRButtonDown(hWnd, wParam, MAKEPOINTS(lParam)); case WM_PAINT: return MainWnd_OnPaint(hWnd); default: return DefWindowProc(hWnd, msg, wParam, lParam); } } int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR szCmdLine, int nShowCmd) { WNDCLASSEX wcex = { sizeof wcex, CS_VREDRAW|CS_HREDRAW, WndProc, 0, 0, g_hinst = hInstance, LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW), NULL/*空画刷防闪烁*/, NULL, L"MainWndClass", LoadIcon(NULL, IDI_APPLICATION) }; RegisterClassEx(&wcex); RECT rc = { 0, 0, 480, 480 }; AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); g_hmainwnd = CreateWindowEx(0, wcex.lpszClassName, L"D3D11主窗口", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL); ShowWindow(g_hmainwnd, nShowCmd); UpdateWindow(g_hmainwnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }

之所以说是常规加速版,是因为DX11还有GPGPU计算加速功能Compute Shader,也就是所谓的DirectCompute,但是这里我们只用到了常规的图形流水线加速功能。 最低配置被设置为D3D_FEATURE_LEVEL_9_3和"ps_4_0_level_9_3"。如果将循环次数改多一点,由于"ps_4_0_level_9_3"不包含条件循环指令,循环操作实际上被展开了,指令数量很容易超过限制,这个时候就需要把最低配置改为D3D_FEATURE_LEVEL_10_0和"ps_4_0"。 // shaders.txt struct VS_OUTPUT { float4 pos : SV_POSITION; float2 tex : TEXCOORD; }; void vs_common(float3 pos : POSITION, float2 tex : TEXCOORD, out VS_OUTPUT vso) { vso.pos = float4(pos, 1.0f); vso.tex = tex; } float4 ps_mandelbrot(VS_OUTPUT vso) : SV_TARGET { float2 zvar = float2(0, 0); float i; for (i = 0; i < 16 && zvar.x * zvar.x + zvar.y * zvar.y <= 4; i++) { float2 zvar2 = zvar; zvar.x = zvar2.x * zvar2.x - zvar2.y * zvar2.y + vso.tex.x; zvar.y = zvar2.x * zvar2.y + zvar2.y * zvar2.x + vso.tex.y; } return float4(i / 16.0, 0.0f, 0.0f, 1.0f); } float4 ps_julia(VS_OUTPUT vso) : SV_TARGET { float2 zvar = vso.tex; float i; for (i = 0; i < 16 && zvar.x * zvar.x + zvar.y * zvar.y <= 4; i++) { float2 zvar2 = zvar; zvar.x = zvar2.x * zvar2.x - zvar2.y * zvar2.y + 0.4f; zvar.y = zvar2.x * zvar2.y + zvar2.y * zvar2.x + 0.3f; } return float4(i / 16.0, 0.0f, 0.0f, 1.0f); } // main.c - 使用了C99语法,可使用VS2013-2017编译 #undef UNICODE #define UNICODE 1 #include <windows.h> #include <assert.h> #include <d3d11.h> #include <d3dcompiler.h> #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "d3dcompiler.lib") #define NOFAIL(x) assert(SUCCEEDED(x)) #define NOTZERO(x) assert(x) #define CLEANUP(x) if (x) { (x)->lpVtbl->Release(x); (x) = NULL; } HINSTANCE g_hinst; HWND g_hmainwnd; // DirectX D3D_FEATURE_LEVEL g_fl; ID3D11Device *g_dev; ID3D11DeviceContext *g_immctx; IDXGISwapChain *g_swpch; ID3D11Texture2D *g_backbuffer; ID3D11RenderTargetView *g_backbuffer_rtv; // DirectX资源 ID3D11VertexShader *g_vs_common; ID3D11PixelShader *g_ps_mandelbrot; ID3D11PixelShader *g_ps_julia; ID3D11PixelShader *g_ps_current; ID3D11InputLayout *g_inlayout; ID3D11Buffer *g_vertexbuffer; LRESULT MainWnd_OnCreate(HWND hWnd, LPCREATESTRUCT lpcs) { // 创建DirectX RECT rc; GetClientRect(hWnd, &rc); DXGI_MODE_DESC backbuf_mode_desc = { rc.right - rc.left, rc.bottom - rc.top, { 60, 1 }, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_MODE_SCALING_UNSPECIFIED, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, }; DXGI_SWAP_CHAIN_DESC swpch_desc = { backbuf_mode_desc, { 1, 0 }, DXGI_USAGE_RENDER_TARGET_OUTPUT, 1, hWnd, TRUE, DXGI_SWAP_EFFECT_DISCARD, 0, }; D3D_FEATURE_LEVEL fllist[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, }; if (FAILED(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, fllist, sizeof fllist / sizeof fllist[0], D3D11_SDK_VERSION, &swpch_desc, &g_swpch, &g_dev, &g_fl, &g_immctx))) { NOFAIL(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_WARP, NULL, 0, fllist, sizeof fllist / sizeof fllist[0], D3D11_SDK_VERSION, &swpch_desc, &g_swpch, &g_dev, &g_fl, &g_immctx)); } IDXGIFactory *dxgifac; NOFAIL(g_swpch->lpVtbl->GetParent(g_swpch, &IID_IDXGIFactory, (void**)&dxgifac)); NOFAIL(dxgifac->lpVtbl->MakeWindowAssociation(dxgifac, hWnd, DXGI_MWA_NO_ALT_ENTER)); CLEANUP(dxgifac); NOFAIL(g_swpch->lpVtbl->GetBuffer(g_swpch, 0, &IID_ID3D11Texture2D, (void**)&g_backbuffer)); NOFAIL(g_dev->lpVtbl->CreateRenderTargetView(g_dev, (ID3D11Resource *)g_backbuffer, NULL, &g_backbuffer_rtv)); // 创建绘制资源 // 读取shaders.txt static char shaders_txt[65536]; memset(shaders_txt, 0, sizeof shaders_txt); HANDLE hfile = CreateFile(L"shaders.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (hfile == INVALID_HANDLE_VALUE) { MessageBox(hWnd, L"不能打开shader.txt文件", L"文件错误", MB_OK); DestroyWindow(hWnd); return 0; } DWORD dwret; ReadFile(hfile, shaders_txt, sizeof shaders_txt - 1, &dwret, NULL); CloseHandle(hfile); // 编译shaders.txt并创建相关资源 ID3DBlob *bcode_ps_mandelbrot, *bcode_ps_julia, *bcode_vs_common, *errmsg; if (FAILED(D3DCompile(shaders_txt, strlen(shaders_txt), "shaders.txt", NULL, NULL, "ps_mandelbrot", "ps_4_0_level_9_3", 0, 0, &bcode_ps_mandelbrot, &errmsg)) || FAILED(D3DCompile(shaders_txt, strlen(shaders_txt), "shaders.txt", NULL, NULL, "ps_julia", "ps_4_0_level_9_3", 0, 0, &bcode_ps_julia, &errmsg)) || FAILED(D3DCompile(shaders_txt, strlen(shaders_txt), "shaders.txt", NULL, NULL, "vs_common", "vs_4_0_level_9_3", 0, 0, &bcode_vs_common, &errmsg))) { NOTZERO(errmsg); MessageBoxA(hWnd, errmsg->lpVtbl->GetBufferPointer(errmsg), "D3DCompile Error", MB_OK); CLEANUP(errmsg); CLEANUP(bcode_ps_mandelbrot); CLEANUP(bcode_ps_julia); CLEANUP(bcode_vs_common); DestroyWindow(hWnd); return 0; } NOFAIL(g_dev->lpVtbl->CreatePixelShader(g_dev, bcode_ps_mandelbrot->lpVtbl->GetBufferPointer(bcode_ps_mandelbrot), bcode_ps_mandelbrot->lpVtbl->GetBufferSize(bcode_ps_mandelbrot), NULL, &g_ps_mandelbrot)); NOFAIL(g_dev->lpVtbl->CreatePixelShader(g_dev, bcode_ps_julia->lpVtbl->GetBufferPointer(bcode_ps_julia), bcode_ps_julia->lpVtbl->GetBufferSize(bcode_ps_julia), NULL, &g_ps_julia)); NOFAIL(g_dev->lpVtbl->CreateVertexShader(g_dev, bcode_vs_common->lpVtbl->GetBufferPointer(bcode_vs_common), bcode_vs_common->lpVtbl->GetBufferSize(bcode_vs_common), NULL, &g_vs_common)); D3D11_INPUT_ELEMENT_DESC inputelements[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; NOFAIL(g_dev->lpVtbl->CreateInputLayout(g_dev, inputelements, sizeof inputelements / sizeof inputelements[0], bcode_vs_common->lpVtbl->GetBufferPointer(bcode_vs_common), bcode_vs_common->lpVtbl->GetBufferSize(bcode_vs_common), &g_inlayout)); CLEANUP(bcode_ps_mandelbrot); CLEANUP(bcode_ps_julia); CLEANUP(bcode_vs_common); float vertexdata[] = { -1.0f, -1.0f, 1.0f, -2.2f, -2.2f, -1.0f, 1.0f, 1.0f, -2.2f, 2.2f, 1.0f, -1.0f, 1.0f, 2.2f, -2.2f, 1.0f, 1.0f, 1.0f, 2.2f, 2.2f, }; D3D11_BUFFER_DESC bd = { sizeof vertexdata, D3D11_USAGE_DEFAULT, 0, 0, 0 }; D3D11_SUBRESOURCE_DATA srd = { vertexdata, 0, 0 }; NOFAIL(g_dev->lpVtbl->CreateBuffer(g_dev, &bd, &srd, &g_vertexbuffer)); g_ps_current = g_ps_mandelbrot; return 0; } LRESULT MainWnd_OnDestroy(HWND hWnd) { if (g_immctx) g_immctx->lpVtbl->ClearState(g_immctx); // 清理资源 CLEANUP(g_vertexbuffer); CLEANUP(g_inlayout); CLEANUP(g_vs_common); CLEANUP(g_ps_julia); CLEANUP(g_ps_mandelbrot); // 清理DirectX CLEANUP(g_backbuffer_rtv); CLEANUP(g_backbuffer); CLEANUP(g_swpch); CLEANUP(g_immctx); CLEANUP(g_dev); // 产生WM_QUIT消息,结束消息循环 PostQuitMessage(0); return 0; } LRESULT MainWnd_OnSize(HWND hWnd, WPARAM wParam, POINTS pts) { if (g_swpch && g_immctx && g_dev) { g_immctx->lpVtbl->ClearState(g_immctx); CLEANUP(g_backbuffer_rtv); CLEANUP(g_backbuffer); RECT rc; GetClientRect(hWnd, &rc); NOFAIL(g_swpch->lpVtbl->ResizeBuffers(g_swpch, 1, rc.right - rc.left, rc.bottom - rc.top, DXGI_FORMAT_UNKNOWN, 0)); NOFAIL(g_swpch->lpVtbl->GetBuffer(g_swpch, 0, &IID_ID3D11Texture2D, (void**)&g_backbuffer)); NOFAIL(g_dev->lpVtbl->CreateRenderTargetView(g_dev, g_backbuffer, NULL, &g_backbuffer_rtv)); } return 0; } LRESULT MainWnd_OnPaint(HWND hWnd) { // GDI例行任务 PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); // DirectX任务 // 清除背景 float backcolor[] = { 0.0f, 1.0f, 1.0f, 1.0f }; g_immctx->lpVtbl->ClearRenderTargetView(g_immctx, g_backbuffer_rtv, backcolor); // 设置状态并绘图 RECT rc; GetClientRect(hWnd, &rc); float ratio = (float)(rc.right - rc.left) / (float)(rc.bottom - rc.top); float vertexdata[] = { -1.0f, -1.0f, 1.0f, -2.2f * ratio, -2.2f, -1.0f, 1.0f, 1.0f, -2.2f * ratio, 2.2f, 1.0f, -1.0f, 1.0f, 2.2f * ratio, -2.2f, 1.0f, 1.0f, 1.0f, 2.2f * ratio, 2.2f, }; g_immctx->lpVtbl->UpdateSubresource(g_immctx, (ID3D11Resource *)g_vertexbuffer, 0, NULL, vertexdata, 0, 0); UINT stride = sizeof(float) * 5, offset = 0; g_immctx->lpVtbl->IASetVertexBuffers(g_immctx, 0, 1, &g_vertexbuffer, &stride, &offset); g_immctx->lpVtbl->IASetInputLayout(g_immctx, g_inlayout); g_immctx->lpVtbl->IASetPrimitiveTopology(g_immctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); g_immctx->lpVtbl->VSSetShader(g_immctx, g_vs_common, NULL, 0); D3D11_VIEWPORT viewport = { 0.0f, 0.0f, rc.right - rc.left, rc.bottom - rc.top, 0.0f, 1.0f }; g_immctx->lpVtbl->RSSetViewports(g_immctx, 1, &viewport); g_immctx->lpVtbl->PSSetShader(g_immctx, g_ps_current, NULL, 0); g_immctx->lpVtbl->OMSetRenderTargets(g_immctx, 1, &g_backbuffer_rtv, NULL); g_immctx->lpVtbl->Draw(g_immctx, 4, 0); // 上屏 NOFAIL(g_swpch->lpVtbl->Present(g_swpch, 0, 0)); return 0; } LRESULT MainWnd_OnLButtonDown(HWND hWnd, WPARAM wParam, POINTS pts) { g_ps_current = g_ps_mandelbrot; InvalidateRect(hWnd, NULL, FALSE); return 0; } LRESULT MainWnd_OnRButtonDown(HWND hWnd, WPARAM wParam, POINTS pts) { g_ps_current = g_ps_julia; InvalidateRect(hWnd, NULL, FALSE); return 0; } LRESULT __stdcall WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: return MainWnd_OnCreate(hWnd, (LPCREATESTRUCT)lParam); case WM_DESTROY: return MainWnd_OnDestroy(hWnd); case WM_SIZE: return MainWnd_OnSize(hWnd, wParam, MAKEPOINTS(lParam)); case WM_LBUTTONDOWN: return MainWnd_OnLButtonDown(hWnd, wParam, MAKEPOINTS(lParam)); case WM_RBUTTONDOWN: return MainWnd_OnRButtonDown(hWnd, wParam, MAKEPOINTS(lParam)); case WM_PAINT: return MainWnd_OnPaint(hWnd); default: return DefWindowProc(hWnd, msg, wParam, lParam); } } int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR szCmdLine, int nShowCmd) { WNDCLASSEX wcex = { sizeof wcex, CS_VREDRAW|CS_HREDRAW, WndProc, 0, 0, g_hinst = hInstance, LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW), NULL/*空画刷防闪烁*/, NULL, L"MainWndClass", LoadIcon(NULL, IDI_APPLICATION) }; RegisterClassEx(&wcex); RECT rc = { 0, 0, 480, 480 }; AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); g_hmainwnd = CreateWindowEx(0, wcex.lpszClassName, L"D3D11主窗口", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL); ShowWindow(g_hmainwnd, nShowCmd); UpdateWindow(g_hmainwnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }


在Windows API中只开放了两个允许区分大小写的API: CreateFile,使用FILE_FLAG_POSIX_SEMANTICS开关; FindFirstFileEx,使用FIND_FIRST_EX_CASE_SENSITIVE开关。 不过我们可以通过SetFileInformationByHandle来实现文件/文件夹的改名和删除。 此外,在Native API中InitializeObjectAttributes时不指定OBJ_CASE_INSENSITIVE开关,也可以实现区分大小写的功能。 所有的这些开关只有注册表键值obcaseinsensitive设为DWORD:0并且【重新启动】之后才有效。obcaseinsensitive的具体位置是HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel。 这个键值只影响Windows API(kernel32.dll)和Native API(ntdll.dll)的行为,Windows Subsystem for Linux是通过未公开的内核调用来实现的,不受这个注册表键值影响。 // ConsoleApplication1.cpp: 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <windows.h> #include <stdio.h> int main() { HKEY hsubkey; DWORD valtype, obcaseinsensitive, dwret; LSTATUS lstatus; ULONGLONG renbuf1[64], renbuf2[64]; FILE_RENAME_INFO *ren1, *ren2; FILE_DISPOSITION_INFO del1, del2; HANDLE hdir1, hdir2; HANDLE hfind; WIN32_FIND_DATA fd; char ansipath[MAX_PATH]; // 检测系统是否打开了Win32的大小写敏感接口 hsubkey = NULL; RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel"), &hsubkey); valtype = obcaseinsensitive = 0; dwret = sizeof obcaseinsensitive; lstatus = RegQueryValueEx(hsubkey, TEXT("obcaseinsensitive"), NULL, &valtype, (LPBYTE)&obcaseinsensitive, &dwret); RegCloseKey(hsubkey); if (lstatus != ERROR_SUCCESS || valtype != REG_DWORD || dwret != sizeof obcaseinsensitive || obcaseinsensitive != 0) { printf("HKEY_LOCAL_MACHINE\\CurrentControlSet\\Control\\Session Manager\\kernel\\obcaseinsensitive" " must be set to REG_DWORD:0, and you must restart your computer before trying again.\n"); return 1; } // 创建两个文件夹 CreateDirectory(TEXT("G:\\CaseDir_1"), NULL); CreateDirectory(TEXT("G:\\casedir_123"), NULL); // 改成只有大小写不同的形式 // 第一个 hdir1 = CreateFile(TEXT("G:\\CaseDir_1"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf1, 0, sizeof renbuf1); ren1 = (FILE_RENAME_INFO*)renbuf1; ren1->ReplaceIfExists = FALSE; ren1->RootDirectory = NULL; wcscpy(ren1->FileName, L"G:\\CaseDir"); ren1->FileNameLength = wcslen(ren1->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir1, FileRenameInfo, &renbuf1, sizeof renbuf1); CloseHandle(hdir1); // 第二个 hdir2 = CreateFile(TEXT("G:\\casedir_123"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf2, 0, sizeof renbuf2); ren2 = (FILE_RENAME_INFO*)renbuf2; ren2->ReplaceIfExists = FALSE; ren2->RootDirectory = NULL; wcscpy(ren2->FileName, L"G:\\casedir"); ren2->FileNameLength = wcslen(ren2->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir2, FileRenameInfo, &renbuf2, sizeof renbuf2); CloseHandle(hdir2); // 按照区分大小写的方式查找文件/文件夹 hfind = FindFirstFileEx(TEXT("G:\\Case*"), FindExInfoStandard, &fd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE); if (hfind != INVALID_HANDLE_VALUE) { do { #ifdef UNICODE WideCharToMultiByte(CP_ACP, 0, fd.cFileName, -1, ansipath, MAX_PATH, NULL, NULL); #else strcpy(ansipath, fd.cFileName); #endif printf("%s\n", ansipath); } while (FindNextFile(hfind, &fd)); FindClose(hfind); } #if 0 // 改回来 // 第一个 hdir1 = CreateFile(TEXT("G:\\CaseDir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf1, 0, sizeof renbuf1); ren1 = (FILE_RENAME_INFO*)renbuf1; ren1->ReplaceIfExists = FALSE; ren1->RootDirectory = NULL; wcscpy(ren1->FileName, L"G:\\CaseDir_1"); ren1->FileNameLength = wcslen(ren1->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir1, FileRenameInfo, &renbuf1, sizeof renbuf1); CloseHandle(hdir1); // 第二个 hdir2 = CreateFile(TEXT("G:\\casedir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf2, 0, sizeof renbuf2); ren2 = (FILE_RENAME_INFO*)renbuf2; ren2->ReplaceIfExists = FALSE; ren2->RootDirectory = NULL; wcscpy(ren2->FileName, L"G:\\casedir_123"); ren2->FileNameLength = wcslen(ren2->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir2, FileRenameInfo, &renbuf2, sizeof renbuf2); CloseHandle(hdir2); #else // 删掉 // 第一个 hdir1 = CreateFile(TEXT("G:\\CaseDir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); del1.DeleteFile = TRUE; SetFileInformationByHandle(hdir1, FileDispositionInfo, &del1, sizeof del1); CloseHandle(hdir1); // 第二个 hdir2 = CreateFile(TEXT("G:\\casedir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); del2.DeleteFile = TRUE; SetFileInformationByHandle(hdir2, FileDispositionInfo, &del2, sizeof del2); CloseHandle(hdir2); #endif return 0; }

在Windows API中只开放了两个允许区分大小写的API: CreateFile,使用FILE_FLAG_POSIX_SEMANTICS开关; FindFirstFileEx,使用FIND_FIRST_EX_CASE_SENSITIVE开关。 不过我们可以通过SetFileInformationByHandle来实现文件/文件夹的改名和删除。 此外,在Native API中InitializeObjectAttributes时不指定OBJ_CASE_INSENSITIVE开关,也可以实现区分大小写的功能。 所有的这些开关只有注册表键值obcaseinsensitive设为DWORD:0并且【重新启动】之后才有效。obcaseinsensitive的具体位置是HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel。 这个键值只影响Windows API(kernel32.dll)和Native API(ntdll.dll)的行为,Windows Subsystem for Linux是通过未公开的内核调用来实现的,不受这个注册表键值影响。 (附件:278050) // ConsoleApplication1.cpp: 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <windows.h> #include <stdio.h> int main() { HKEY hsubkey; DWORD valtype, obcaseinsensitive, dwret; LSTATUS lstatus; ULONGLONG renbuf1[64], renbuf2[64]; FILE_RENAME_INFO *ren1, *ren2; FILE_DISPOSITION_INFO del1, del2; HANDLE hdir1, hdir2; HANDLE hfind; WIN32_FIND_DATA fd; char ansipath[MAX_PATH]; // 检测系统是否打开了Win32的大小写敏感接口 hsubkey = NULL; RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel"), &hsubkey); valtype = obcaseinsensitive = 0; dwret = sizeof obcaseinsensitive; lstatus = RegQueryValueEx(hsubkey, TEXT("obcaseinsensitive"), NULL, &valtype, (LPBYTE)&obcaseinsensitive, &dwret); RegCloseKey(hsubkey); if (lstatus != ERROR_SUCCESS || valtype != REG_DWORD || dwret != sizeof obcaseinsensitive || obcaseinsensitive != 0) { printf("HKEY_LOCAL_MACHINE\\CurrentControlSet\\Control\\Session Manager\\kernel\\obcaseinsensitive" " must be set to REG_DWORD:0, and you must restart your computer before trying again.\n"); return 1; } // 创建两个文件夹 CreateDirectory(TEXT("G:\\CaseDir_1"), NULL); CreateDirectory(TEXT("G:\\casedir_123"), NULL); // 改成只有大小写不同的形式 // 第一个 hdir1 = CreateFile(TEXT("G:\\CaseDir_1"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf1, 0, sizeof renbuf1); ren1 = (FILE_RENAME_INFO*)renbuf1; ren1->ReplaceIfExists = FALSE; ren1->RootDirectory = NULL; wcscpy(ren1->FileName, L"G:\\CaseDir"); ren1->FileNameLength = wcslen(ren1->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir1, FileRenameInfo, &renbuf1, sizeof renbuf1); CloseHandle(hdir1); // 第二个 hdir2 = CreateFile(TEXT("G:\\casedir_123"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf2, 0, sizeof renbuf2); ren2 = (FILE_RENAME_INFO*)renbuf2; ren2->ReplaceIfExists = FALSE; ren2->RootDirectory = NULL; wcscpy(ren2->FileName, L"G:\\casedir"); ren2->FileNameLength = wcslen(ren2->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir2, FileRenameInfo, &renbuf2, sizeof renbuf2); CloseHandle(hdir2); // 按照区分大小写的方式查找文件/文件夹 hfind = FindFirstFileEx(TEXT("G:\\Case*"), FindExInfoStandard, &fd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE); if (hfind != INVALID_HANDLE_VALUE) { do { #ifdef UNICODE WideCharToMultiByte(CP_ACP, 0, fd.cFileName, -1, ansipath, MAX_PATH, NULL, NULL); #else strcpy(ansipath, fd.cFileName); #endif printf("%s\n", ansipath); } while (FindNextFile(hfind, &fd)); FindClose(hfind); } #if 0 // 改回来 // 第一个 hdir1 = CreateFile(TEXT("G:\\CaseDir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf1, 0, sizeof renbuf1); ren1 = (FILE_RENAME_INFO*)renbuf1; ren1->ReplaceIfExists = FALSE; ren1->RootDirectory = NULL; wcscpy(ren1->FileName, L"G:\\CaseDir_1"); ren1->FileNameLength = wcslen(ren1->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir1, FileRenameInfo, &renbuf1, sizeof renbuf1); CloseHandle(hdir1); // 第二个 hdir2 = CreateFile(TEXT("G:\\casedir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); memset(renbuf2, 0, sizeof renbuf2); ren2 = (FILE_RENAME_INFO*)renbuf2; ren2->ReplaceIfExists = FALSE; ren2->RootDirectory = NULL; wcscpy(ren2->FileName, L"G:\\casedir_123"); ren2->FileNameLength = wcslen(ren2->FileName) * sizeof(wchar_t); SetFileInformationByHandle(hdir2, FileRenameInfo, &renbuf2, sizeof renbuf2); CloseHandle(hdir2); #else // 删掉 // 第一个 hdir1 = CreateFile(TEXT("G:\\CaseDir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); del1.DeleteFile = TRUE; SetFileInformationByHandle(hdir1, FileDispositionInfo, &del1, sizeof del1); CloseHandle(hdir1); // 第二个 hdir2 = CreateFile(TEXT("G:\\casedir"), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, NULL); del2.DeleteFile = TRUE; SetFileInformationByHandle(hdir2, FileDispositionInfo, &del2, sizeof del2); CloseHandle(hdir2); #endif return 0; }


本来打算用Shader Model 2.0,但是经过测试,Shader Model 2.0的功能太原始,连for循环都无法支持,最后只能改用Shader Model 3.0。由于使用了HLSL编译Shader Model 3.0,所以需要VS2002-2010和DirectX 9.0c SDK 2004-2010, 不能使用VC6和DirectX 9.0b SDK。 DX是DirectX的缩写,SM是Shader Model的缩写。 如果改写成DirectX 10/11版本的话,最低应该需要D3D_FEATURE_LEVEL_9_3和ps_4_0_level_9_3,同时还需要编写一个简易的vs_4_0_level_9_3,因为vertex shader和pixel shader在10/11中都不是可选的。 但是由于vs_4_0_level_9_3和ps_4_0_level_9_3不支持通用循环,需要把循环展开成多次条件判断,512的指令数很容易就用超了,对于较复杂的运算,最低建议使用D3D_FEATURE_LEVEL_10_0、vs_4_0和ps_4_0。 vs_1_1 - 128指令 vs_2_0 - 256指令 vs_4_0_level_9_1 - 256指令 vs_2_x - 256指令 vs_4_0_level_9_3 - 256指令 vs_3_0 - 最少512指令,上限到D3DCAPS9.MaxVertexShader30InstructionSlots,支持通用循环 vs_4_0 - 无限制,支持通用循环 ps_2_0 - 32纹理指令+64算术指令 ps_4_0_level_9_1 - 32纹理指令+64算术指令 ps_2_x - 最少96指令,上限到D3DCAPS9.D3DPSHADERCAPS2_0.NumInstructionSlots ps_4_0_level_9_3 - 512指令 vs_3_0 - 最少512指令,上限到D3DCAPS9.MaxPixelShader30InstructionSlots,支持通用循环 vs_4_0 - 无限制,支持通用循环 dxmandelbrot.zip 14.9M 7次 GPU部分: // complexsets.txt float4 ps_mandelbrot(float2 tex : TEXCOORD0) : COLOR0 { float2 zvar = float2(0, 0); float i; for (i = 0; i < 16 && zvar.x * zvar.x + zvar.y * zvar.y <= 4; i++) { float2 zvar2 = zvar; zvar.x = zvar2.x * zvar2.x - zvar2.y * zvar2.y + tex.x; zvar.y = zvar2.x * zvar2.y + zvar2.y * zvar2.x + tex.y; } return float4(i / 16.0, 0.0f, 0.0f, 1.0f); } float4 ps_julia(float2 tex : TEXCOORD0) : COLOR0 { float2 zvar = tex; float i; for (i = 0; i < 16 && zvar.x * zvar.x + zvar.y * zvar.y <= 4; i++) { float2 zvar2 = zvar; zvar.x = zvar2.x * zvar2.x - zvar2.y * zvar2.y + 0.4f; zvar.y = zvar2.x * zvar2.y + zvar2.y * zvar2.x + 0.3f; } return float4(i / 16.0, 0.0f, 0.0f, 1.0f); } CPU部分: // main.c #include <Windows.h> #include <tchar.h> // DirectX 9.0c SDK or higher #include <d3d9.h> #include <d3dx9.h> #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #pragma comment(linker, "/nodefaultlib:libcp") LPDIRECT3D9 g_pD3D = NULL; LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; LPDIRECT3DPIXELSHADER9 g_pPSmandelbrot = NULL; LPDIRECT3DPIXELSHADER9 g_pPSjulia = NULL; LPDIRECT3DPIXELSHADER9 g_pPS = NULL; typedef struct CUSTOMVERTEX { FLOAT x, y, z, rhw; FLOAT u, v; }CUSTOMVERTEX; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX1) HRESULT InitD3D(HWND hWnd) { D3DPRESENT_PARAMETERS d3dpp; if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) return E_FAIL; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; if (FAILED(IDirect3D9_CreateDevice(g_pD3D, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice))) { return E_FAIL; } return S_OK; } HRESULT CompileAndCreatePS(LPCTSTR filename, LPSTR funcname, LPDIRECT3DPIXELSHADER9 *pps) { LPD3DXBUFFER psbuf = NULL, errmsg = NULL; HRESULT hr = S_OK; hr = D3DXCompileShaderFromFile(filename, NULL, NULL, funcname, "ps_3_0", 0, &psbuf, &errmsg, NULL); if (errmsg) { MessageBoxA(NULL, (char*)errmsg->lpVtbl->GetBufferPointer(errmsg), "err", MB_ICONERROR); errmsg->lpVtbl->Release(errmsg); return hr; } else if (FAILED(hr)) { return hr; } hr = IDirect3DDevice9_CreatePixelShader(g_pd3dDevice, (DWORD*)psbuf->lpVtbl->GetBufferPointer(psbuf), pps); psbuf->lpVtbl->Release(psbuf); return S_OK; } HRESULT InitRes() { CUSTOMVERTEX vertices[] = { { 0.0f, 0.0f, 0.5f, 1.0f, -2.2f, -2.2f, }, { 480.0f, 0.0f, 0.5f, 1.0f, 2.2f, -2.2f, }, { 0.0f, 480.0f, 0.5f, 1.0f, -2.2f, 2.2f, }, { 480.0f, 480.0f, 0.5f, 1.0f, 2.2f, 2.2f, }, }; HRESULT hr = S_OK; VOID* pVertices; hr = IDirect3DDevice9_CreateVertexBuffer(g_pd3dDevice, 4*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL); if (FAILED(hr)) { return hr; } hr = IDirect3DVertexBuffer9_Lock(g_pVB, 0, sizeof(vertices), (void**)&pVertices, 0); if (FAILED(hr)) return hr; memcpy(pVertices, vertices, sizeof(vertices)); IDirect3DVertexBuffer9_Unlock(g_pVB); CompileAndCreatePS(_T("complexsets.txt"), "ps_mandelbrot", &g_pPSmandelbrot); CompileAndCreatePS(_T("complexsets.txt"), "ps_julia", &g_pPSjulia); g_pPS = g_pPSmandelbrot; return S_OK; } VOID Cleanup() { if (g_pPSjulia) IDirect3DPixelShader9_Release(g_pPSjulia); if (g_pPSmandelbrot) IDirect3DPixelShader9_Release(g_pPSmandelbrot); if (g_pVB != NULL) IDirect3DVertexBuffer9_Release(g_pVB); if (g_pd3dDevice != NULL) IDirect3DDevice9_Release(g_pd3dDevice); if (g_pD3D != NULL) IDirect3D9_Release(g_pD3D); } VOID Render() { IDirect3DDevice9_Clear(g_pd3dDevice, 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0); if (SUCCEEDED(IDirect3DDevice9_BeginScene(g_pd3dDevice))) { IDirect3DDevice9_SetStreamSource(g_pd3dDevice, 0, g_pVB, 0, sizeof(CUSTOMVERTEX)); IDirect3DDevice9_SetFVF(g_pd3dDevice, D3DFVF_CUSTOMVERTEX); IDirect3DDevice9_SetPixelShader(g_pd3dDevice, g_pPS); IDirect3DDevice9_DrawPrimitive(g_pd3dDevice, D3DPT_TRIANGLESTRIP, 0, 2); IDirect3DDevice9_EndScene(g_pd3dDevice); } IDirect3DDevice9_Present(g_pd3dDevice, NULL, NULL, NULL, NULL); } LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_LBUTTONDOWN: g_pPS = g_pPSmandelbrot; return 0; case WM_RBUTTONDOWN: g_pPS = g_pPSjulia; return 0; case WM_DESTROY: Cleanup(); PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR szCmdLine, int nShowCmd) { WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, hInst, LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW), NULL, NULL, _T("MainWndProc"), wc.hIcon }; RECT rc = { 0, 0, 480, 480 }; MSG msg; HWND hWnd; if (!RegisterClassEx(&wc)) return 0; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); hWnd = CreateWindow(wc.lpszClassName, _T("Mandelbrot and Julia"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, wc.hInstance, NULL); if (SUCCEEDED(InitD3D(hWnd))) { if (SUCCEEDED(InitRes())) { ShowWindow(hWnd, SW_SHOWDEFAULT); UpdateWindow(hWnd); ZeroMemory(&msg, sizeof(msg)); while (msg.message != WM_QUIT) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { Render(); } } } } return 0; } 更新:支持Visual C++ 6.0和DirectX 9.0b SDK 通过DirectX 9.0c SDK的fxc将上述HLSL代码编译为Shader Model 3.0 ASM代码,即可支持DirectX 9.0b SDK。 ps_mandelbrot.txt: // ps_mandelbrot.txt ps_3_0 def c0, 0, 0, 4, 1 def c1, 0.0625, 0, 0, 0 defi i0, 16, 0, 0, 0 dcl_texcoord v0.xy mov r0.y, c0.y mov r0.z, c0.y mov r0.w, c0.y rep i0 mul r0.x, r0.z, r0.z mad r0.x, r0.y, r0.y, r0.x break_lt c0.z, r0.x mul r0.x, r0.z, r0.z mad r0.x, r0.y, r0.y, -r0.x add r0.x, r0.x, v0.x dp2add r0.z, r0.y, r0.z, v0.y add r0.w, r0.w, c0.w mov r0.y, r0.x endrep mul oC0.x, r0.w, c1.x mov oC0.yzw, c0.xyyw ps_julia.txt // ps_julia.txt ps_3_0 def c0, 0, 0, 4, 0.400000006 def c1, 0.300000012, 1, 0.0625, 0 defi i0, 16, 0, 0, 0 dcl_texcoord v0.xy mov r0.y, v0.x mov r0.z, v0.y mov r0.w, c0.y rep i0 mul r0.x, r0.z, r0.z mad r0.x, r0.y, r0.y, r0.x break_lt c0.z, r0.x mul r0.x, r0.z, r0.z mad r0.x, r0.y, r0.y, -r0.x add r0.x, r0.x, c0.w dp2add r0.z, r0.y, r0.z, c1.x add r0.w, r0.w, c1.y mov r0.y, r0.x endrep mul oC0.x, r0.w, c1.z mov oC0.yzw, c1.xwwy main.c // main.c #include <Windows.h> #include <tchar.h> #include <d3d9.h> #include <d3dx9.h> #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") LPDIRECT3D9 g_pD3D = NULL; LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; LPDIRECT3DPIXELSHADER9 g_pPSmandelbrot = NULL; LPDIRECT3DPIXELSHADER9 g_pPSjulia = NULL; LPDIRECT3DPIXELSHADER9 g_pPS = NULL; typedef struct CUSTOMVERTEX { FLOAT x, y, z, rhw; FLOAT u, v; }CUSTOMVERTEX; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX1) HRESULT InitD3D(HWND hWnd) { D3DPRESENT_PARAMETERS d3dpp; if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) return E_FAIL; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; if (FAILED(IDirect3D9_CreateDevice(g_pD3D, D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice))) { return E_FAIL; } return S_OK; } HRESULT AssembleAndCreatePS(LPCTSTR filename, LPDIRECT3DPIXELSHADER9 *pps) { LPD3DXBUFFER psbuf = NULL, errmsg = NULL; HRESULT hr = S_OK; hr = D3DXAssembleShaderFromFile(filename, NULL, NULL, 0, &psbuf, &errmsg); if (errmsg) { MessageBoxA(NULL, (char*)errmsg->lpVtbl->GetBufferPointer(errmsg), "err", MB_ICONERROR); errmsg->lpVtbl->Release(errmsg); return hr; } else if (FAILED(hr)) { return hr; } hr = IDirect3DDevice9_CreatePixelShader(g_pd3dDevice, (DWORD*)psbuf->lpVtbl->GetBufferPointer(psbuf), pps); psbuf->lpVtbl->Release(psbuf); return S_OK; } HRESULT InitRes() { CUSTOMVERTEX vertices[] = { { 0.0f, 0.0f, 0.5f, 1.0f, -2.2f, -2.2f, }, { 480.0f, 0.0f, 0.5f, 1.0f, 2.2f, -2.2f, }, { 0.0f, 480.0f, 0.5f, 1.0f, -2.2f, 2.2f, }, { 480.0f, 480.0f, 0.5f, 1.0f, 2.2f, 2.2f, }, }; HRESULT hr = S_OK; VOID* pVertices; hr = IDirect3DDevice9_CreateVertexBuffer(g_pd3dDevice, 4*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL); if (FAILED(hr)) { return hr; } hr = IDirect3DVertexBuffer9_Lock(g_pVB, 0, sizeof(vertices), (void**)&pVertices, 0); if (FAILED(hr)) return hr; memcpy(pVertices, vertices, sizeof(vertices)); IDirect3DVertexBuffer9_Unlock(g_pVB); AssembleAndCreatePS(_T("ps_mandelbrot.txt"), &g_pPSmandelbrot); AssembleAndCreatePS(_T("ps_julia.txt"), &g_pPSjulia); g_pPS = g_pPSmandelbrot; return S_OK; } VOID Cleanup() { if (g_pPSjulia) IDirect3DPixelShader9_Release(g_pPSjulia); if (g_pPSmandelbrot) IDirect3DPixelShader9_Release(g_pPSmandelbrot); if (g_pVB != NULL) IDirect3DVertexBuffer9_Release(g_pVB); if (g_pd3dDevice != NULL) IDirect3DDevice9_Release(g_pd3dDevice); if (g_pD3D != NULL) IDirect3D9_Release(g_pD3D); } VOID Render() { IDirect3DDevice9_Clear(g_pd3dDevice, 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0); if (SUCCEEDED(IDirect3DDevice9_BeginScene(g_pd3dDevice))) { IDirect3DDevice9_SetStreamSource(g_pd3dDevice, 0, g_pVB, 0, sizeof(CUSTOMVERTEX)); IDirect3DDevice9_SetFVF(g_pd3dDevice, D3DFVF_CUSTOMVERTEX); IDirect3DDevice9_SetPixelShader(g_pd3dDevice, g_pPS); IDirect3DDevice9_DrawPrimitive(g_pd3dDevice, D3DPT_TRIANGLESTRIP, 0, 2); IDirect3DDevice9_EndScene(g_pd3dDevice); } IDirect3DDevice9_Present(g_pd3dDevice, NULL, NULL, NULL, NULL); } LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_LBUTTONDOWN: g_pPS = g_pPSmandelbrot; return 0; case WM_RBUTTONDOWN: g_pPS = g_pPSjulia; return 0; case WM_DESTROY: Cleanup(); PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR szCmdLine, int nShowCmd) { WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, hInst, LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW), NULL, NULL, _T("MainWndProc"), wc.hIcon }; RECT rc = { 0, 0, 480, 480 }; MSG msg; HWND hWnd; if (!RegisterClassEx(&wc)) return 0; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); hWnd = CreateWindow(wc.lpszClassName, _T("Mandelbrot and Julia"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, wc.hInstance, NULL); if (SUCCEEDED(InitD3D(hWnd))) { if (SUCCEEDED(InitRes())) { ShowWindow(hWnd, SW_SHOWDEFAULT); UpdateWindow(hWnd); ZeroMemory(&msg, sizeof(msg)); while (msg.message != WM_QUIT) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { Render(); } } } } return 0; }

写了个cuda版的 #include <stdio.h> #include <stdlib.h> #include <cuda_runtime.h> #define grid 10 #define block 1024 bool InitCUDA() { int count; cudaGetDeviceCount(&count); if (count == 0) { fprintf(stderr, "There is no device.\n"); return false; } int i; for (i = 0; i < count; i++) { cudaDeviceProp prop; cudaGetDeviceProperties(&prop, i); if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) { if (prop.major >= 1) { break; } } } if (i == count) { fprintf(stderr, "There is no device supporting CUDA 1.x.\n"); return false; } cudaSetDevice(i); return true; } struct complex{ float r; float i; }; __device__ float mod(struct complex a){ return sqrt(a.r*a.r+a.i*a.i); } __device__ struct complex add(struct complex a,struct complex b){ struct complex c; c.r=a.r+b.r; c.i=a.i+b.i; return c; } __device__ struct complex square(struct complex a){ struct complex b; b.r=a.r*a.r-a.i*a.i; b.i=2*a.r*a.i; return b; } #define maxx 1000 __global__ void man(struct complex *p,int *o,int tol,int pixels) { int id=blockIdx.x*blockDim.x+threadIdx.x; int i,j,k; struct complex z,c; for(i=0;i*tol+id<pixels;i++){ z.r=0; z.i=0; c=p[i*tol+id]; k=maxx; for(j=1;j<maxx;j++){ z=add(square(z),c); if((k==maxx)&&(mod(z)>2.0f)){ k=j; } } o[i*tol+id]=k; } } #define height 2000 #define width 3000 #define xstart -2.0f #define xend 1.0f #define ystart -1.0f #define yend 1.0f int main() { if (!InitCUDA()) { return 0; } struct complex* pic=(struct complex*)malloc(sizeof(struct complex)*width*height); int i,j; for(i=0;i<width;i++){ for(j=0;j<height;j++){ pic[i*height+j].r=xstart+(xend-xstart)*i/width; pic[i*height+j].i=ystart+(yend-ystart)*j/height; } } struct complex* picd; int *o=(int*)malloc(sizeof(int)*width*height); int *od; cudaMalloc(&picd,sizeof(struct complex)*width*height); cudaMemcpy(picd,pic,sizeof(struct complex)*width*height,cudaMemcpyHostToDevice); cudaMalloc(&od,sizeof(int)*width*height); man<<<grid,block>>>(picd,od,grid*block,height*width); cudaMemcpy(o,od,sizeof(int)*width*height,cudaMemcpyDeviceToHost); FILE *fp=fopen("./out.ppm","w"); fprintf(fp,"P3\n%d %d\n255\n",width,height); int r,g,b,col; for(i=0;i<height;i++){ for(j=0;j<width;j++){ col=o[j*height+i]; if(col==maxx){ r=0; g=0; b=0; }else{ b=(int)max(min((0+(int)(512.0f/sqrt(o[j*height+i]))),255),0); g=0; r=0; } fprintf(fp,"%d %d %d ",r,g,b); } fprintf(fp,"\n"); } fclose(fp); free(pic); free(o); cudaFree(picd); cudaFree(od); cudaDeviceReset(); return 0; } (附件:278048)


《2017/09/13更新》 当前现存Windows 10版本的生命周期政策 现存的桌面SAC版本: Windows 10 (Version 1507) Build 10240 支持到 2017/05/09 Windows 10 Version 1511 Build 10586 支持到 2017/10/10 Windows 10 Version 1607 Build 14393 支持到 暂定2018/03 (Clover Trail 支持到 2023/01) Windows 10 Version 1703 Build 15063 支持到 暂定2018/09 现存的桌面LTSC版本: Windows 10 Enterprise 2015 LTSB 主流支持到 2020/10/13 延伸支持到 2025/10/14 Windows 10 Enterprise 2016 LTSB 主流支持到 2021/10/12 延伸支持到 2026/10/13 现存的服务器LTSC版本: Windows Server 2016 主流支持到 2022/01/11 延伸支持到 2027/01/11 参考资料: https://support.microsoft.com/zh-cn/help/13853/windows-lifecycle-fact-sheet https://support.microsoft.com/zh-cn/lifecycle https://docs.microsoft.com/en-us/windows/deployment/update/waas-overview https://blogs.technet.microsoft.com/windowsserver/2017/08/24/sneak-peek-1-windows-server-version-1709/ https://answers.microsoft.com/en-us/windows/forum/windows_10-windows_install/intel-clover-trail-processors-are-not-supported-on/ed1823d3-c82c-4d7f-ba9d-43ecbcf526e9


时间在流逝,世界在变化。对于编程工具的使用,激进派当然好选择了,尽量用最新的(比如vs2017),这样即使稍有过时也不会立刻落伍。 但是如果你是保守派,正在坚守一个你认为刚刚好,不会过新也不会过旧的工具,学会了可以立刻致大用,就要当心你所坚守的工具,在坚守了几年之后,可能落伍了。 就说一下C/C++的事情。Win98时代,最流行的是turboc2.0。WinXP时代,再用turboc2.0已经令人抓狂了,自然就要上vc6.0(也可以上devcpp或cfree,但是要写支持中文的gui当然是vc6.0用起来舒服)。2013年左右Win7/Win8/Win8.1登上取代WinXP的征程的时候,vc6.0体验很差,当然要用vs2005/2008/2010(同样的,也有人用codeblocks)。 现在2017年,WinXP已经渐渐淡出历史,Win7成了新时代的WinXP,Win10正在半年一次飞速更新,用什么版本的vs最好呢? 如果不想折腾的话,VS2012(Update5)加上最新版的.NET DevPack能够满足绝大多数爱好者的要求。 为什么不用VS2013/2015呢?VS2013/2015很多功能需要装IE10/11,装了IE10/11就得装上平台更新KB2670838,但是我见过的盗版Win7大部分是IE8/9,自然也是没有KB2670838的。简而言之,Win7SP1装VS2012没有先决条件,是使用VS2012的最大理由。 为什么不用VS2005/2008/2010呢?这是因为VS2012支持比较新的技术,比以前的版本好用很多。 对于C#/VB爱好者,它支持最新的.NET4.x(只要安装最新的.NET DevPack),虽然语法只有C#/VB 5.0,但也够用了。对于数据库,它原生支持超轻量级的SQLLocalDB,不用再担心SQLExpress在新系统的兼容性问题。目前来看比较好用。 对于C++爱好者,它自带DirectX编译器d3dcompiler_46.dll,同时,它也开始支持了C++AMP这种简化调用GPU运算的技术,它也对C++/CLI恢复了智能提示支持。目前来看比较好用。 要注意的是,这篇文章只是说「在2017年,vs2012是比较好用的版本」,并不表明以后仍然好用,甚至不保证2018年是比较好用的版本。究竟是放弃使用保守版本去追新,还是继续寻找新的保守版本,就要看你自己的判断力了。 VS2012下载地址: VS2012Ultimate简体中文(已集成序列号): ed2k://|file|cn_visual_studio_ultimate_2012_x86_dvd_920954.iso|1643802624|A3058CE4F2AD7035DB6E67586D07AAA5|/ VS2012Ultimate英文(已集成序列号): ed2k://|file|en_visual_studio_ultimate_2012_x86_dvd_920947.iso|1594998784|1BBECB1C7A892DA7D97EC4C840953915|/ Update5升级包: ed2k://|file|mu_visual_studio_2012_update_5_x86_dvd_6967467.iso|2538768384|405A484D70CA922DC6FC1F204818D412|/ .NET DevPack下载地址: 最新版下载页面: https://www.microsoft.com/net/targeting .NET4.6.2 DevPack: ed2k://|file|en_.net_fx_4_6_2_dp_lps_win_7sp1_8dot1_10_win_server_2008sp2_2008r2sp1_2012_2012r2_x86_x64_9058145.exe|86788848|064175480A208A61C4E68CB678A399B3|/ .NET4.6.2 DevPack 简体中文语言包: ed2k://|file|cn_.net_fx_4_6_2_dp_lps_win_7sp1_8dot1_10_win_server_2008sp2_2008r2sp1_2012_2012r2_x86_x64_9058141.exe|13237608|D4E1F9DA83CAD505E89B43FF194FBFDB|/ Win8WDK暂时还没有离线的解决方案,但是也不是每一个人都要去学习编写驱动的。 其它资源: DirectXTK for DirectX 11 [2014-09]: DirectXTK-sept2014.zip 583k 对于Office编程,Visual Studio 2012只支持Office 2010,不支持较早的Office 2007和较晚的Office 2013,这是比较坑的一点。各版本支持的如下: Visual Studio 2008支持Office 2003/2007 Visual Studio 2010支持Office 2007/2010 Visual Studio 2012支持Office 2010 Visual Studio 2013支持Office 2010/2013 Visual Studio 2015/2017支持Office 2010/2013/2016


不违背语言逻辑的做法都是可行的。。。 像是这种情况,就不能用goto。 #include <Windows.h> #include <tchar.h> #include <string> #include <iostream> using namespace std; int _tmain(int argc, TCHAR **argv) { if (argc >= 1) goto b; int i = 1; // warning & assert fail //string s = "aaa"; // compile error b: cout << i << endl; return 0; } 微软那个可以用goto是因为变量声明和初始化都写在函数开头附近,不存在被跳过的问题。 一般来说,如果你习惯于C89风格的那种变量声明全部写在开头的做法,完全可以随意使用goto。但是如果你习惯于ISO C++风格的那种变量声明写在程序中间的做法,就应该考虑一下其它写法。比如你如果觉得do-while(0)-break太拽,完全可以换一种写法。 需要清理的资源变量声明; { 不需要清理的变量声明; 可能失败的操作; if (失败) goto 清理; // ... } 清理: if (变量1需要清理) 清理变量1; // ...


使用C语言让Windows睡眠/休眠可以使用SetSystemPowerState函数,但是这个函数需要进程具有SE_SHUTDOWN_NAME(或者_T("SeShutdownPrivilege"))权限,这个权限默认是关闭的,需要手动打开。 #include <Windows.h> #include <tchar.h> int _tmain(int argc, TCHAR **argv) { HANDLE token = NULL; TOKEN_PRIVILEGES tp = { 0 }; tp.PrivilegeCount = 1; LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tp.Privileges[0].Luid); tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token); AdjustTokenPrivileges(token, FALSE, &tp, sizeof tp, NULL, NULL); CloseHandle(token); SetSystemPowerState(TRUE, FALSE); // 第一个参数TRUE睡眠,FALSE休眠 return 0; } 另一个方法是使用SetSuspendState,它不用调整权限,但是需要导入powrprof.h和powrprof.lib。 #include <Windows.h> #include <tchar.h> #include <PowrProf.h> #pragma comment(lib, "powrprof.lib") int _tmain(int argc, TCHAR **argv) { SetSuspendState(FALSE, FALSE, FALSE); // 第一个参数FALSE睡眠,TRUE休眠 return 0; }

使用C语言让Windows睡眠/休眠可以使用SetSystemPowerState函数,但是这个函数需要进程具有SE_SHUTDOWN_NAME(或者_T("SeShutdownPrivilege"))权限,这个权限默认是关闭的,需要手动打开。 #include <Windows.h> #include <tchar.h> int _tmain(int argc, TCHAR **argv) { HANDLE token = NULL; TOKEN_PRIVILEGES tp = { 0 }; tp.PrivilegeCount = 1; LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tp.Privileges[0].Luid); tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token); AdjustTokenPrivileges(token, FALSE, &tp, sizeof tp, NULL, NULL); CloseHandle(token); SetSystemPowerState(TRUE, FALSE); // 第一个参数TRUE睡眠,FALSE休眠 return 0; } 另一个方法是使用SetSuspendState,它不用调整权限,但是需要导入powrprof.h和powrprof.lib。 #include <Windows.h> #include <tchar.h> #include <PowrProf.h> #pragma comment(lib, "powrprof.lib") int _tmain(int argc, TCHAR **argv) { SetSuspendState(FALSE, FALSE, FALSE); // 第一个参数FALSE睡眠,TRUE休眠 return 0; }


在所有现代Windows操作系统中,系统的环境变量保存在这两个注册表键中的注册表值: 系统级:HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\Environment 用户级:HKEY_CURRENT_USER\\Environment 添加、修改注册表值,最方便的方法是使用RegSetKeyValue。删除注册表值可以用RegDeleteKeyValue。如果需要兼容Windows XP等老系统,需要使用RegOpenKeyEx-RegSetValueEx/RegDeleteValue-RegCloseKey的方式。 修改了这两个地方,不会立即生效,还需要通知explorer.exe环境变量已经改变,需要重新读取注册表。方法是向所有窗口(HWND_BROADCAST)发送WM_SETTINGCHANGE消息,wParam为0,lParam为(LPARAM)TEXT("Environment")。向窗口发送消息可以用SendMessage函数,但为了避免受到挂起的窗口影响,这里一般是使用SendMessageTimeout或SendNotifyMessage函数代替,但不能使用PostMessage,这是因为这个消息需要直接调用WndProc,不能被放到消息队列中。 不过,程序自身的环境块是相对独立,并且继承自父进程的。如果需要根据注册表生成环境块以供CreateProcess使用,需要使用CreateEnvironmentBlock,它还需要一个用户token,可以用OpenProcessToken获取。注意这样获取的环境块只能是Unicode(wchar_t)的,如果需要ANSI(char)的,需要自己转换,这也意味着,CreateProcess时需要指定CREATE_UNICODE_ENVIRONMENT开关。用完以后,使用DestroyEnvironmentBlock释放。 #include <Windows.h> #include <tchar.h> #include <stdio.h> #include <UserEnv.h> #pragma comment(lib, "userenv.lib") int _tmain(int argc, TCHAR **argv) { // 设置(删除)用户级(系统级)环境变量,并通知explorer.exe读取注册表环境变量 TCHAR envstr[] = _T("My Environment String"); RegSetKeyValue(HKEY_CURRENT_USER, _T("Environment"), _T("myenv"), REG_SZ, envstr, lstrlen(envstr) * sizeof (TCHAR)); //RegDeleteKeyValue(HKEY_CURRENT_USER, _T("Environment"), _T("myenv")); RegSetKeyValue(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Control\\Session Manager\\Environment"), _T("mysysenv"), REG_SZ, envstr, lstrlen(envstr) * sizeof (TCHAR)); SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)_T("Environment")); // 根据NT系统的userenv.dll生成Unicode环境块 HANDLE htoken = NULL; OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htoken); LPVOID envblock = NULL; CreateEnvironmentBlock(&envblock, htoken, FALSE); CloseHandle(htoken);      // 以这个环境块运行cmd /k set以查看结果 TCHAR cmdline[1024] = _T("cmd /k set"); STARTUPINFO si = { sizeof si }; PROCESS_INFORMATION pi = { 0 }; CreateProcess(NULL, cmdline, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, envblock, NULL, &si, &pi); CloseHandle(pi.hThread);      // 等待程序结束 WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess);      // 销毁环境块 DestroyEnvironmentBlock(envblock);      return 0; }

在所有现代Windows操作系统中,系统的环境变量保存在这两个注册表键中的注册表值: 系统级:HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\Environment 用户级:HKEY_CURRENT_USER\\Environment 添加、修改注册表值,最方便的方法是使用RegSetKeyValue。删除注册表值可以用RegDeleteKeyValue。如果需要兼容Windows XP等老系统,需要使用RegOpenKeyEx-RegSetValueEx/RegDeleteValue-RegCloseKey的方式。 修改了这两个地方,不会立即生效,还需要通知explorer.exe环境变量已经改变,需要重新读取注册表。方法是向所有窗口(HWND_BROADCAST)发送WM_SETTINGCHANGE消息,wParam为0,lParam为(LPARAM)TEXT("Environment")。向窗口发送消息可以用SendMessage函数,但为了避免受到挂起的窗口影响,这里一般是使用SendMessageTimeout或SendNotifyMessage函数代替,但不能使用PostMessage,这是因为这个消息需要直接调用WndProc,不能被放到消息队列中。 不过,程序自身的环境块是相对独立,并且继承自父进程的。如果需要根据注册表生成环境块以供CreateProcess使用,需要使用CreateEnvironmentBlock,它还需要一个用户token,可以用OpenProcessToken获取。注意这样获取的环境块只能是Unicode(wchar_t)的,如果需要ANSI(char)的,需要自己转换,这也意味着,CreateProcess时需要指定CREATE_UNICODE_ENVIRONMENT开关。用完以后,使用DestroyEnvironmentBlock释放。 #include <Windows.h> #include <tchar.h> #include <stdio.h> #include <UserEnv.h> #pragma comment(lib, "userenv.lib") int _tmain(int argc, TCHAR **argv) { // 设置(删除)用户级(系统级)环境变量,并通知explorer.exe读取注册表环境变量 TCHAR envstr[] = _T("My Environment String"); RegSetKeyValue(HKEY_CURRENT_USER, _T("Environment"), _T("myenv"), REG_SZ, envstr, lstrlen(envstr) * sizeof (TCHAR)); //RegDeleteKeyValue(HKEY_CURRENT_USER, _T("Environment"), _T("myenv")); RegSetKeyValue(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Control\\Session Manager\\Environment"), _T("mysysenv"), REG_SZ, envstr, lstrlen(envstr) * sizeof (TCHAR)); SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)_T("Environment")); // 根据NT系统的userenv.dll生成Unicode环境块 HANDLE htoken = NULL; OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htoken); LPVOID envblock = NULL; CreateEnvironmentBlock(&envblock, htoken, FALSE); CloseHandle(htoken);      // 以这个环境块运行cmd /k set以查看结果 TCHAR cmdline[1024] = _T("cmd /k set"); STARTUPINFO si = { sizeof si }; PROCESS_INFORMATION pi = { 0 }; CreateProcess(NULL, cmdline, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, envblock, NULL, &si, &pi); CloseHandle(pi.hThread);      // 等待程序结束 WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess);      // 销毁环境块 DestroyEnvironmentBlock(envblock);      return 0; }


许多书上都写了,应该始终使用_beginthreadex和_endthreadex,永远不要使用CreateThread和ExitThread,因为后者不会正常分配和释放C运行库的tiddata。但是,这是不准确的。 实际上,微软考虑到一些开发者喜欢用CreateThread和ExitThread,而不愿意用_beginthreadex和_endthreadex,并且喜欢把设计的禁区说成是系统的bug。因此做了一些设计,使得tiddata的处理变得自动化,尽可能地兼容这种不规范的用法。 WinXP(NT5.1)及以前的系统: 1、当用到tiddata时按需分配tiddata 2、当线程删除时,在msvcrXX.dll中监视DLL_THREAD_DETACH消息,并自动删除tiddata(无法照顾到静态链接的情况) WinServer2003(NT5.2)及更高版本的系统: 1、当用到tiddata时按需分配tiddata 2、用到了FlsAlloc函数而不是TlsAlloc函数,这个函数允许指定一个线程退出时自动执行的回调函数,C运行库会注册一个_freefls函数,当线程删除时自动删除tiddata 不过这个功能需要运行库支持,检查方法是找到VC\crt\src\tidtable.c里面是否存在FlsAlloc等文本。 有的地方提到CreateThread不支持signal,但是实测CreateThread即使是在非常老的VC6.0,在非常老的WinXP系统上,也是支持signal的。 CreateThread使用指南: 1、动态链接:没有内存泄露 3、静态链接、支持FlsAlloc的运行库+WinServer2003及更高版本的系统:没有内存泄露 2、静态链接、不支持FlsAlloc的运行库(或者)WinXP及以前的系统:需手动调用_endthreadex以避免内存泄露,不可直接return或ExitThread

许多书上都写了,应该始终使用_beginthreadex和_endthreadex,永远不要使用CreateThread和ExitThread,因为后者不会正常分配和释放C运行库的tiddata。但是,这是不准确的。 实际上,微软考虑到一些开发者喜欢用CreateThread和ExitThread,而不愿意用_beginthreadex和_endthreadex,并且喜欢把设计的禁区说成是系统的bug。因此做了一些设计,使得tiddata的处理变得自动化,尽可能地兼容这种不规范的用法。 WinXP(NT5.1)及以前的系统: 1、当用到tiddata时按需分配tiddata 2、当线程删除时,在msvcrXX.dll中监视DLL_THREAD_DETACH消息,并自动删除tiddata(无法照顾到静态链接的情况) WinServer2003(NT5.2)及更高版本的系统: 1、当用到tiddata时按需分配tiddata 2、用到了FlsAlloc函数而不是TlsAlloc函数,这个函数允许指定一个线程退出时自动执行的回调函数,C运行库会注册一个_freefls函数,当线程删除时自动删除tiddata 不过这个功能需要运行库支持,检查方法是找到VC\crt\src\tidtable.c里面是否存在FlsAlloc等文本。 有的地方提到CreateThread不支持signal,但是实测CreateThread即使是在非常老的VC6.0,在非常老的WinXP系统上,也是支持signal的。 CreateThread使用指南: 1、动态链接:没有内存泄露 3、静态链接、支持FlsAlloc的运行库+WinServer2003及更高版本的系统:没有内存泄露 2、静态链接、不支持FlsAlloc的运行库(或者)WinXP及以前的系统:需手动调用_endthreadex以避免内存泄露,不可直接return或ExitThread


.NET Framework 4.7.1 预览版 Build 2538已经发布,正式版可能将与Windows 10秋季创意者更新一同发布。 blog文章: https://blogs.msdn.microsoft.com/dotnet/2017/08/07/welcome-to-the-net-framework-4-7-1-early-access/ github地址: https://github.com/Microsoft/dotnet-framework-early-access/blob/master/README.md 不建议在日常环境和编程中安装使用这个版本,仅作为测试之用,建议等待正式版发布再升级。 支持以下系统: Windows 7 SP1 x86 / x64 & Windows Server 2008 R2 SP1 x64 Windows Server 2012 Windows 8.1 with Update x86 / x64 & Windows Server 2012 R2 with Update Windows 10 Version 1607 x86 / x64 & Windows Server 2016 Windows 10 Version 1703 x86 / x64 Windows 10 Insider Preview x86 / x64 & Windows Server Insider Preview (自带) 比较重要的改进 支持.NET Standard 2.0。 WPF应用程序视觉树的增强。 辅助功能的增强。 SHA256算法支持。 性能和可靠性改进。 已解决之前版本的重要问题 已经解决.NET Framework 4.7在Windows 7、Windows Server 2008 R2和Windows Server 2012安装时需要KB4019990的问题。 关于支持的系统版本 此次.NET Framework更新支持的系统版本和4.7、4.6.2相同。 根据官方列表,近期不受支持的版本如下,但不排除可以安装更高版本。 以下操作系统最高支持.NET Framework 4.6.1: Windows 8 x86 / x64 Windows 10 x86 / x64 Windows 10 Version 1511 x86 / x64 (自带) 以下操作系统最高支持.NET Framework 4.6: Windows Vista SP2 x86 / x64 & Windows Server 2008 SP2 x86 / x64

.NET Framework 4.7.1 预览版 Build 2538已经发布,正式版可能将与Windows 10秋季创意者更新一同发布。 blog文章: https://blogs.msdn.microsoft.com/dotnet/2017/08/07/welcome-to-the-net-framework-4-7-1-early-access/ github地址: https://github.com/Microsoft/dotnet-framework-early-access/blob/master/README.md 不建议在日常环境和编程中安装使用这个版本,仅作为测试之用,建议等待正式版发布再升级。 支持以下系统: Windows 7 SP1 x86 / x64 & Windows Server 2008 R2 SP1 x64 Windows Server 2012 Windows 8.1 with Update x86 / x64 & Windows Server 2012 R2 with Update Windows 10 Version 1607 x86 / x64 & Windows Server 2016 Windows 10 Version 1703 x86 / x64 Windows 10 Insider Preview x86 / x64 & Windows Server Insider Preview (自带) 比较重要的改进 支持.NET Standard 2.0。 WPF应用程序视觉树的增强。 辅助功能的增强。 SHA256算法支持。 性能和可靠性改进。 已解决之前版本的重要问题 已经解决.NET Framework 4.7在Windows 7、Windows Server 2008 R2和Windows Server 2012安装时需要KB4019990的问题。 关于支持的系统版本 此次.NET Framework更新支持的系统版本和4.7、4.6.2相同。 根据官方列表,近期不受支持的版本如下,但不排除可以安装更高版本。 以下操作系统最高支持.NET Framework 4.6.1: Windows 8 x86 / x64 Windows 10 x86 / x64 Windows 10 Version 1511 x86 / x64 (自带) 以下操作系统最高支持.NET Framework 4.6: Windows Vista SP2 x86 / x64 & Windows Server 2008 SP2 x86 / x64


64位Windows中不但SysWOW64文件夹中存在32位的msvcrt.dll、msvcp60.dll、msvcirt.dll,而且System32文件夹里边还有64位版本,而众所周知,VC6是没有64位版本的,这个64位的运行库是怎么来的呢? 这是因为从Windows 2000开始,Windows使用VC6运行库编写,VC6运行库变成了Windows内部DLL。后来Windows被移植到64位,于是VC6运行库就有了64位版本。 经过研究发现,Windows SDK默认链接到msvcrXXX.dll,但是Windows Driver Kit则不同,链接到的是msvcrt.dll。Windows Driver Kit是编写驱动程序的环境,需要与Windows本身使用同一个运行库版本。 链接到64位的VC6运行库 链接到C运行库msvcrt.dll的方法很简单——安装 Windows Driver Kit 7.1.0 ,然后把 C:\WinDDK\7600.16385.1\lib\Crt\amd64 里边的msvcrt.lib复制到项目根目录即可。 链接到C++运行库msvcp60.dll和msvcirt.dll的方法比较复杂,因为已经好几代了,C++有些地方已经不兼容了,事实上Windows 7也基本上没有链接到这个运行库。 注意 不过这里千万要注意,每一代Windows系统的VC6运行库都不同, 不要使用超出VC6范围的导出函数 ,因为这些函数仅供Windows操作系统内部使用,不保证在任何版本的Windows中一定存在。


Mandelbrot集合的定义是:设Z0=0+0i,C=复平面上的点,根据Zn=Z(n-1)^2+C递推出后面的Zn,若|Zn|<2,则属于该集合。 Julia集合的定义是:设Z0=复平面上的点,C=a+bi,根据Zn=Z(n-1)^2+C递推出后面的Zn,若|Zn|<2,则属于该集合。 #include <windows.h> #include <tchar.h> #include <math.h> HINSTANCE hInst; HWND hMainWnd; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void PaintMandelbrot(HWND, HDC, double, double, double, double, int, int); void PaintJulia(HWND, HDC, double, double, double, double, int, int, double, double); void CExpoInt(double *, double *, int); typedef COLORREF(WINAPI*Pshlwapi_ColorHLSToRGB)(WORD, WORD, WORD); HMODULE hshlwapi; Pshlwapi_ColorHLSToRGB shlwapi_ColorHLSToRGB; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASSEX wcex; RECT rc; MSG msg; hshlwapi = LoadLibrary(_T("shlwapi.dll")); if (!hshlwapi) return 0; shlwapi_ColorHLSToRGB = (Pshlwapi_ColorHLSToRGB)GetProcAddress(hshlwapi, "ColorHLSToRGB"); if (!shlwapi_ColorHLSToRGB) return 0; hInst = hInstance; wcex.cbSize = sizeof wcex; wcex.style = CS_VREDRAW|CS_HREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wcex.lpszMenuName = NULL; wcex.lpszClassName = _T("MainWndClass"); wcex.hIconSm = wcex.hIcon; if (!RegisterClassEx(&wcex)) return 0; rc.left = rc.top = 0; rc.right = rc.bottom = 480; AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); hMainWnd = CreateWindowEx( 0, wcex.lpszClassName, _T("Mandelbrot and Julia"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL); if (!hMainWnd) return 0; ShowWindow(hMainWnd, nShowCmd); UpdateWindow(hMainWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_LBUTTONDOWN) { HDC hdc = GetDC(hWnd); PaintMandelbrot(hWnd, hdc, -2.2, -2.2, 2.2, 2.2, 16, 2); ReleaseDC(hWnd, hdc); return 0; } if (msg == WM_RBUTTONDOWN) { HDC hdc = GetDC(hWnd); PaintJulia(hWnd, hdc, -2.2, -2.2, 2.2, 2.2, 16, 2, 0.4, 0.3); ReleaseDC(hWnd, hdc); return 0; } if (msg == WM_DESTROY) { PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } void CExpoInt(double *preal, double *pimag, int expo) { double real, imag, treal, timag; int i; real = *preal; imag = *pimag; for (i = 1; i < expo; i++) { treal = *preal; timag = *pimag; *preal = treal * real - timag * imag; *pimag = timag * real + treal * imag; } } void PaintMandelbrot(HWND hWnd, HDC hdc, double fromx, double fromy, double tox, double toy, int iter, int expo) { RECT rc; int width, height, i, j, k, value; double Creal, Cimag; double Zreal, Zimag; GetClientRect(hWnd, &rc); width = rc.right - rc.left; height = rc.bottom - rc.top; for (i = 0; i < height; i++) { Creal = 0; Cimag = fromy + (toy - fromy) * i / (double)height; for (j = 0; j < width; j++) { Creal = fromx + (tox - fromx) * j / (double)width; Zreal = 0; Zimag = 0; for (k = 0; k < iter; k++) { if ((Zreal * Zreal + Zimag * Zimag) > 4.0) break; CExpoInt(&Zreal, &Zimag, expo); Zreal += Creal; Zimag += Cimag; } value = k * 160 / iter; SetPixel(hdc, j, i, shlwapi_ColorHLSToRGB((WORD)value, 120, 240)); } } return; } void PaintJulia(HWND hWnd, HDC hdc, double fromx, double fromy, double tox, double toy, int iter, int expo, double real, double imag) { RECT rc; int width, height, i, j, k, value; double Creal, Cimag; double Zreal, Zimag; GetClientRect(hWnd, &rc); width = rc.right - rc.left; height = rc.bottom - rc.top; for (i = 0; i < height; i++) { Creal = 0; Cimag = fromy + (toy - fromy) * i / (double)height; for (j = 0; j < width; j++) { Creal = fromx + (tox - fromx) * j / (double)width; Zreal = Creal; Zimag = Cimag; for (k = 0; k < iter; k++) { if ((Zreal * Zreal + Zimag * Zimag) > 4.0) break; CExpoInt(&Zreal, &Zimag, expo); Zreal += real; Zimag += imag; } value = k * 160 / iter; SetPixel(hdc, j, i, shlwapi_ColorHLSToRGB((WORD)value, 120, 240)); } } return; }

Mandelbrot集合的定义是:设Z0=0+0i,C=复平面上的点,根据Zn=Z(n-1)^2+C递推出后面的Zn,若|Zn|<2,则属于该集合。 Julia集合的定义是:设Z0=复平面上的点,C=a+bi,根据Zn=Z(n-1)^2+C递推出后面的Zn,若|Zn|<2,则属于该集合。 (附件:276038) (附件:276039) #include <windows.h> #include <tchar.h> #include <math.h> HINSTANCE hInst; HWND hMainWnd; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void PaintMandelbrot(HWND, HDC, double, double, double, double, int, int); void PaintJulia(HWND, HDC, double, double, double, double, int, int, double, double); void CExpoInt(double *, double *, int); typedef COLORREF(WINAPI*Pshlwapi_ColorHLSToRGB)(WORD, WORD, WORD); HMODULE hshlwapi; Pshlwapi_ColorHLSToRGB shlwapi_ColorHLSToRGB; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASSEX wcex; RECT rc; MSG msg; hshlwapi = LoadLibrary(_T("shlwapi.dll")); if (!hshlwapi) return 0; shlwapi_ColorHLSToRGB = (Pshlwapi_ColorHLSToRGB)GetProcAddress(hshlwapi, "ColorHLSToRGB"); if (!shlwapi_ColorHLSToRGB) return 0; hInst = hInstance; wcex.cbSize = sizeof wcex; wcex.style = CS_VREDRAW|CS_HREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wcex.lpszMenuName = NULL; wcex.lpszClassName = _T("MainWndClass"); wcex.hIconSm = wcex.hIcon; if (!RegisterClassEx(&wcex)) return 0; rc.left = rc.top = 0; rc.right = rc.bottom = 480; AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); hMainWnd = CreateWindowEx( 0, wcex.lpszClassName, _T("Mandelbrot and Julia"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL); if (!hMainWnd) return 0; ShowWindow(hMainWnd, nShowCmd); UpdateWindow(hMainWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_LBUTTONDOWN) { HDC hdc = GetDC(hWnd); PaintMandelbrot(hWnd, hdc, -2.2, -2.2, 2.2, 2.2, 16, 2); ReleaseDC(hWnd, hdc); return 0; } if (msg == WM_RBUTTONDOWN) { HDC hdc = GetDC(hWnd); PaintJulia(hWnd, hdc, -2.2, -2.2, 2.2, 2.2, 16, 2, 0.4, 0.3); ReleaseDC(hWnd, hdc); return 0; } if (msg == WM_DESTROY) { PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } void CExpoInt(double *preal, double *pimag, int expo) { double real, imag, treal, timag; int i; real = *preal; imag = *pimag; for (i = 1; i < expo; i++) { treal = *preal; timag = *pimag; *preal = treal * real - timag * imag; *pimag = timag * real + treal * imag; } } void PaintMandelbrot(HWND hWnd, HDC hdc, double fromx, double fromy, double tox, double toy, int iter, int expo) { RECT rc; int width, height, i, j, k, value; double Creal, Cimag; double Zreal, Zimag; GetClientRect(hWnd, &rc); width = rc.right - rc.left; height = rc.bottom - rc.top; for (i = 0; i < height; i++) { Creal = 0; Cimag = fromy + (toy - fromy) * i / (double)height; for (j = 0; j < width; j++) { Creal = fromx + (tox - fromx) * j / (double)width; Zreal = 0; Zimag = 0; for (k = 0; k < iter; k++) { if ((Zreal * Zreal + Zimag * Zimag) > 4.0) break; CExpoInt(&Zreal, &Zimag, expo); Zreal += Creal; Zimag += Cimag; } value = k * 160 / iter; SetPixel(hdc, j, i, shlwapi_ColorHLSToRGB((WORD)value, 120, 240)); } } return; } void PaintJulia(HWND hWnd, HDC hdc, double fromx, double fromy, double tox, double toy, int iter, int expo, double real, double imag) { RECT rc; int width, height, i, j, k, value; double Creal, Cimag; double Zreal, Zimag; GetClientRect(hWnd, &rc); width = rc.right - rc.left; height = rc.bottom - rc.top; for (i = 0; i < height; i++) { Creal = 0; Cimag = fromy + (toy - fromy) * i / (double)height; for (j = 0; j < width; j++) { Creal = fromx + (tox - fromx) * j / (double)width; Zreal = Creal; Zimag = Cimag; for (k = 0; k < iter; k++) { if ((Zreal * Zreal + Zimag * Zimag) > 4.0) break; CExpoInt(&Zreal, &Zimag, expo); Zreal += real; Zimag += imag; } value = k * 160 / iter; SetPixel(hdc, j, i, shlwapi_ColorHLSToRGB((WORD)value, 120, 240)); } } return; }


对于一般的程序,按照下面的方法存储可变数据就行了: Unicode程序(类似Chrome): 对于新系统,放到%[LOCAL]APPDATA%\OrgName\AppName 对于老系统,放到%APPDATA%\OrgName\AppName 非Unicode程序(类似Python 2.7): 如果要求整个树Authenticated Users允许修改(最简单实用): 放到%SYSTEMDRIVE%\AppName 最新的系统,系统根目录只能新建子文件夹,子文件夹才可以允许修改 如果要求整个树CREATOR OWNER完全控制,所有子文件夹Users可创建项目和修改属性(不推荐): 对于新系统,放到%PROGRAMDATA%\OrgName\AppName\user#hash# 对于老系统,放到%ALLUSERSPROFILE%\Application Data\OrgName\AppName\user#hash# 简单一句话就是: Unicode程序最好放到AppData子文件夹,非Unicode程序最好放到系统根目录子文件夹。 如果需要让用户能够备份自己的数据,可以把数据放到用户资料文件夹,SHGetSpecialFolderPath等API可以获取Windows中特殊文件夹的位置,用户资料文件夹如下: 老系统:CSIDL_PERSONAL、CSIDL_MYPICTURES、CSIDL_MYMUSIC、CSIDL_MYVIDEO、CSIDL_DESKTOPDIRECTORY 新系统: 传统:FOLDERID_Documents、FOLDERID_Pictures、FOLDERID_Music、FOLDERID_Videos、FOLDERID_Desktop 新增:FOLDERID_SavedGames、FOLDERID_Contacts、FOLDERID_Links、FOLDERID_Favorites、FOLDERID_SavedSearches、FOLDERID_Downloads

引用 amo: 软件解压即可运行,就没那么多烦恼了…… 商业软件把安装过程、卸载过程搞得那么炫目,并且在系统盘几十个文件夹里都放置它的各种文件夹、文件,是不是一种过度包装行为,目的是为了让用户感受其专业性而买单? 要考虑到很多系统只有C盘一个盘,你解压到哪里呢?这就是我说的这个问题了。 第二个问题, Windows爱好者确实最喜欢绿色软件,这主要是因为Windows是个丰富的标准环境,什么都不装就可以具有强大的功能。Windows发展过程中,会将一些常用的redist dll集成为known dll,也会增加一些non-redist的known dll,使得系统功能越来越强大,而这些dll出于兼容性的考虑一般都不会被主动移除,这就是Windows现在绿色软件多的原因。Linux绿色软件反而比较少,主要是Linux已安装环境可以差别很大,标准环境并不强大,经常需要解决依赖问题。 使用安装程序而不是绿色软件,主要有以下好处: 1、安装程序可以自动安装新版系统组件(如.NET4.0、Access2010、SQLCompact或SQLLocalDB)和第三方组件(如Java),而不用麻烦用户去搜索这些组件,也不用固守系统自带的组件(如.NET3.5或Jet4.0)。 2、普通用户往往会去桌面、任务栏、开始菜单找软件,或者直接双击文档来打开关联的软件,这时候就要创建快捷方式或注册关联。要求用户必须去找exe的话,确实不怎么友好。





CPU的设计理念是顺序执行,对并行执行并不擅长,而GPU正是为高并行而设计的。因此,使用GPU进行运算,配合合适的并行算法,可以大大提高程序的运行效率。 本文使用C/C++调用DirectX 11 Compute Shader(DirectCompute)实现简单GPU计算。 环境要求: Windows 7以上版本(最好装上IE11) 支持DirectX 10和Compute Shader 4.0,或者支持DirectX 11的显卡(Intel比较新的核显就可以了) Visual Studio 2012+ 如果觉得Compute Shader 4.0不够用,还可以改成Compute Shader 5.0,不过硬件要支持DirectX 11才行。 环境初始化基本流程: 创建设备对象→编译HLSL→创建Shader→创建常量缓冲区(可选)→创建GPU缓冲区→为每一个缓冲区创建SRV绑定(可选)→为每一个缓冲区创建UAV绑定→创建GPU到CPU传输缓冲区 进行GPU计算的方法: 上传数据用UpdateSubresource 设定当前对象用CSSetConstantBuffers,CSSetShaderResources,CSSetUnorderedAccessViews,CSSetShader 发出线程组用Dispatch 下载数据用CopyResource→Map→memcpy→Unmap GPU线程的各参数满足下图所示含义: 除本帖以外,还可参考微软官方示例: DirectCompute Basic Win32 Samples.zip 50.4k 3次 GPU程序: // dcompute.hlsl - 要运行的GPU程序 // 常量内存(必须为16的倍数) cbuffer CB : register(b0) { unsigned int a; unsigned int b; unsigned int c; unsigned int d; }; // u0对应UnorderedAccessView RWStructuredBuffer<unsigned int> Data : register(u0); // 主程序(注意cs_4_0只支持M,N,1,只有cs_5_0才支持M,N,P) [numthreads(4, 1, 1)] void main(uint3 Gid : SV_GroupID, // 组别ID(Dispatch函数三个参数) uint3 DTid : SV_DispatchThreadID, // 总ID uint3 GTid : SV_GroupThreadID, // 组内线程ID(numthreads属性三个参数) uint GI : SV_GroupIndex) // 组内序号 { Data[DTid.x] = a + b + c + d; } 主程序(C语言版本,需VS2013+): myd3dres.h 7.07k 2次 dcompute.c 2.00k 2次 主程序(C++版本): // dcomupte.cpp - 运行GPU程序的程序 #include <stdio.h> #include <windows.h> #include <d3d11.h> #include <d3dcompiler.h> #include <atlbase.h> // CComPtr<T> #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3dcompiler.lib") // CComPtr<T> g_obj的使用 // 初始化:&g_obj // 已经初始化取地址:&g_obj.p // 调用成员函数:g_obj-> // 释放:g_obj = NULL; // 工具类型,HRESULT返回值转换为此类型,可自动抛出异常 struct comexcept { explicit comexcept(HRESULT ret) : hr(ret) { if (FAILED(hr)) throw *this; } HRESULT hr; }; // 基础对象 CComPtr<ID3D11Device> g_dev; // 设备对象 CComPtr<ID3D11DeviceContext> g_immctx; // 设备上下文对象 D3D_FEATURE_LEVEL g_level; // Direct3D支持级别 CComPtr<ID3DBlob> g_cs_sort_code; // GPU程序编译后的字节码 CComPtr<ID3D11ComputeShader> g_cs_sort; // GPU程序对象 // 资源对象 CComPtr<ID3D11Buffer> g_constbuf; // 常量内存 CComPtr<ID3D11Buffer> g_gpubuf; // GPU内存 CComPtr<ID3D11ShaderResourceView> g_gpubuf_srv; // GPU内存Shader资源视图绑定(多步Shader计算会用到) CComPtr<ID3D11UnorderedAccessView> g_gpubuf_uav; // GPU内存乱序访问视图绑定 CComPtr<ID3D11Buffer> g_cpubuf; // CPU内存(用来读取GPU内存数据) // 常量内存的结构 // 注意:大小必须是16的倍数,否则会失败 typedef struct ConstBuffer { UINT a; UINT b; UINT c; UINT d; }ConstBuffer; #define NUM_ELEMENTS 16 void DoCompute(); // 入口点(初始化资源) int main(int argc, char *argv[]) { // 支持的设备级别 D3D_FEATURE_LEVEL dlevel[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, }; // 创建设备 // D3D_DRIVER_TYPE_HARDWARE = 使用GPU // D3D_DRIVER_TYPE_WARP = 使用CPU (comexcept)D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, dlevel, sizeof dlevel / sizeof dlevel[0], D3D11_SDK_VERSION, &g_dev, &g_level, &g_immctx); // 检查是否支持Compute Shader 4.0 D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS hwopts; (comexcept)g_dev->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &hwopts, sizeof(hwopts)); if (!hwopts.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x) (comexcept)E_FAIL; // 编译HLSL CComPtr<ID3DBlob> cs_errors; HRESULT hrcompile = D3DCompileFromFile(L"dcompute.hlsl", NULL, NULL, "main", "cs_4_0", 0, 0, &g_cs_sort_code, &cs_errors); if (cs_errors) { printf("%s", cs_errors->GetBufferPointer()); return 0; // 直接结束程序而不是抛出异常,确保能显示编译错误 } (comexcept)hrcompile; // 创建Compute Shader (comexcept)g_dev->CreateComputeShader(g_cs_sort_code->GetBufferPointer(), g_cs_sort_code->GetBufferSize(), NULL, &g_cs_sort); // 创建常量内存(必须是16的倍数,对应b0寄存器) D3D11_BUFFER_DESC constant_buffer_desc = { sizeof (ConstBuffer), D3D11_USAGE_DEFAULT, D3D11_BIND_CONSTANT_BUFFER, 0, 0, 0 }; (comexcept)g_dev->CreateBuffer(&constant_buffer_desc, NULL, &g_constbuf); // 创建GPU内存 D3D11_BUFFER_DESC buffer_desc = { NUM_ELEMENTS * sizeof(UINT), D3D11_USAGE_DEFAULT, D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE, 0, D3D11_RESOURCE_MISC_BUFFER_STRUCTURED, sizeof (UINT) }; (comexcept)g_dev->CreateBuffer(&buffer_desc, NULL, &g_gpubuf); // 创建GPU内存的Shader资源视图绑定(对应t0寄存器) D3D11_SHADER_RESOURCE_VIEW_DESC srvbuffer_desc = { DXGI_FORMAT_UNKNOWN, D3D11_SRV_DIMENSION_BUFFER }; srvbuffer_desc.Buffer.NumElements = NUM_ELEMENTS; (comexcept)g_dev->CreateShaderResourceView(g_gpubuf, &srvbuffer_desc, &g_gpubuf_srv); // 创建GPU内存的乱序访问视图绑定(对应u0寄存器) D3D11_UNORDERED_ACCESS_VIEW_DESC uavbuffer_desc = { DXGI_FORMAT_UNKNOWN, D3D11_UAV_DIMENSION_BUFFER }; uavbuffer_desc.Buffer.NumElements = NUM_ELEMENTS; (comexcept)g_dev->CreateUnorderedAccessView(g_gpubuf, &uavbuffer_desc, &g_gpubuf_uav); // 创建CPU传输内存 D3D11_BUFFER_DESC readback_buffer_desc = { NUM_ELEMENTS * sizeof (UINT), D3D11_USAGE_STAGING, 0, D3D11_CPU_ACCESS_READ, 0, sizeof (UINT) }; (comexcept)g_dev->CreateBuffer(&readback_buffer_desc, NULL, &g_cpubuf); DoCompute(); return 0; } // 计算主程序 void DoCompute(void) { // 设置常量内存为1,2,3,4 // 并将常量内存绑定到b0寄存器 ConstBuffer cb = { 1, 2, 3, 4 }; g_immctx->UpdateSubresource(g_constbuf, 0, NULL, &cb, 0, 0); g_immctx->CSSetConstantBuffers(0, 1, &g_constbuf.p); // 引用已创建的对象要用&xxx.p而不是&xxx // 设置GPU内存为0 // 并将GPU内存绑定到u0寄存器 UINT buf[NUM_ELEMENTS] = { 0 }; g_immctx->UpdateSubresource(g_gpubuf, 0, NULL, &buf[0], 0, 0); g_immctx->CSSetUnorderedAccessViews(0, 1, &g_gpubuf_uav.p, NULL); // 进行运算(4,4,1线程组,注意cs_4_0只支持M,N,1,只有cs_5_0才支持M,N,P) g_immctx->CSSetShader(g_cs_sort, NULL, 0); g_immctx->Dispatch(4, 1, 1); // 将GPU内存数据复制到CPU D3D11_MAPPED_SUBRESOURCE mapped = { 0 }; g_immctx->CopyResource(g_cpubuf, g_gpubuf); (comexcept)g_immctx->Map(g_cpubuf, 0, D3D11_MAP_READ, 0, &mapped); memcpy(&buf[0], mapped.pData, NUM_ELEMENTS * sizeof(UINT)); g_immctx->Unmap(g_cpubuf, 0); // 显示数据 for (int i = 0; i < NUM_ELEMENTS; i++) { printf("%d ", buf[i]); } printf("\n"); }


.NET Framework 4.7离线安装包可以在 https://www.microsoft.com/zh-CN/download/details.aspx?id=55167 下载。 开发包可以在 https://www.microsoft.com/net/targeting 下载,如果使用的是 Visual Studio 2017 或 Visual Studio 2017 Preview ,直接打开安装程序进行更新即可。 支持以下系统: Windows 7 SP1 x86 / x64 & Windows Server 2008 R2 SP1 x64 Windows Server 2012 Windows 8.1 with Update x86 / x64 & Windows Server 2012 R2 with Update Windows 10 Version 1607 x86 / x64 & Windows Server 2016 Windows 10 Version 1703 x86 / x64 (自带) Windows 7、Windows Server 2008 R2、Windows Server 2012,需要先安装 最新的月度汇总或月度汇总预览 ,以安装d3dcompiler_47.dll这个文件。 Windows 7、Windows Server 2008 R2: https://support.microsoft.com/zh-cn/help/4009469 Windows Server 2012: https://support.microsoft.com/zh-cn/help/4009471 专用的轻量级补丁: https://support.microsoft.com/en-us/help/4020302 Windows 8.1、Windows Server 2012 R2可能需要安装KB2919355,即Windows 8.1 Update。现在大多数Windows 8.1已经安装,但是Windows Server 2012 R2有可能还没有安装。如果开始屏幕有搜索按钮,表明已经安装了KB2919355,否则,请参照以下文章安装。 Windows 8.1 Update: https://support.microsoft.com/zh-cn/help/2919355 月度汇总和月度汇总预览: https://support.microsoft.com/zh-cn/help/4009470 (非必需) d3dcompiler_47.dll问题的解决方案 微软也提供了一个专用的轻量级补丁包,可以在程序里写一个【NET4.7安装攻略.htm】,给出相应的下载地址,让用户自己下载安装。 Win7和Win8.0装.NET4.7之前可能需要装这个小补丁—— 下载页面: https://support.microsoft.com/en-us/help/4020302 http://www.catalog.update.microsoft.com/Search.aspx?q=kb4019990 Win7-32位-直链: http://download.windowsupdate.com/c/msdownload/update/software/updt/2017/05/windows6.1-kb4019990-x86_1365fb557d5e5917cbf59b507eac066ad89ea3f7.msu Win7-64位-直链: http://download.windowsupdate.com/c/msdownload/update/software/updt/2017/05/windows6.1-kb4019990-x64_35cc310e81ef23439ba0ec1f11d7b71dd34adfe5.msu Win8.0-32位-直链: http://download.windowsupdate.com/c/msdownload/update/software/updt/2017/05/windows8-rt-kb4019990-x86_9ded2c63f713636270e7e911a73d8ca40076bd95.msu Win8.0-64位-直链: http://download.windowsupdate.com/c/msdownload/update/software/updt/2017/05/windows8-rt-kb4019990-x64_a77f4e3e1f2d47205824763e7121bb11979c2716.msu 虽然d3dcompiler_47.dll可以随程序携带,但是系统补丁好像不行。如果觉得对用户不友好,可以尝试这个dll安装程序。 dotnetfx47_d3dcompiler.7z 2.74M 1次 关于支持的系统版本 此次.NET Framework更新支持的系统版本和4.6.2相同。 根据官方列表,近期不受支持的版本如下,但不排除可以安装更高版本。 以下操作系统最高支持.NET Framework 4.6.1: Windows 8 x86 / x64 Windows 10 x86 / x64 Windows 10 Version 1511 x86 / x64 (自带) 以下操作系统最高支持.NET Framework 4.6: Windows Vista SP2 x86 / x64 & Windows Server 2008 SP2 x86 / x64


很简单的东西 用于批量生产代码 Buttons LeftThumbX LeftThumbY LeftTrigger RightThumbX RightThumbY RightTrigger <TextBlock Text="{0} = " /> <TextBlock Text="{{Binding {0}}}"/> <StackPanel Grid.Column="0"> <TextBlock Text="Buttons = " /> <TextBlock Text="LeftThumbX = " /> <TextBlock Text="LeftThumbY = " /> <TextBlock Text="LeftTrigger = " /> <TextBlock Text="RightThumbX = " /> <TextBlock Text="RightThumbY = " /> <TextBlock Text="RightTrigger = " /> </StackPanel> <StackPanel Grid.Column="1"> <TextBlock Text="{Binding Buttons}"/> <TextBlock Text="{Binding LeftThumbX}"/> <TextBlock Text="{Binding LeftThumbY}"/> <TextBlock Text="{Binding LeftTrigger}"/> <TextBlock Text="{Binding RightThumbX}"/> <TextBlock Text="{Binding RightThumbY}"/> <TextBlock Text="{Binding RightTrigger}"/> </StackPanel> namespace CodeTemplate { class Program { static void Main(string[] args) { var Input = string.Empty; while (true) { Input = Console.ReadLine(); switch (Input.ToLower()) { case "?": Help.Instance.Write(); break; case "c": case "cls": Console.Clear(); break; case "t": case "template": Console.Clear(); Template.Instance.ReadTemplate(); Console.Clear(); break; default: ClearCurrentConsoleLine(); BuildCode.Instance.Read(Input); break; } } } public static void ClearCurrentConsoleLine() { int currentLineCursor = Console.CursorTop - 1; Console.SetCursorPosition(0, currentLineCursor); Console.Write(new string(' ', Console.WindowWidth)); Console.SetCursorPosition(0, currentLineCursor); } } } class Template { static Template _Instance = new Template(); public static Template Instance { get { return _Instance; } } private Template() { } public void ReadTemplate() { Console.WriteLine("请输入 Template"); var input = Console.ReadLine(); var Template = new StringBuilder(); while (input != "end" && input != "e") { Template.Append(input); input = Console.ReadLine(); } CodeTemplate = Template.ToString(); } public string CodeTemplate { get; private set; } } class BuildCode { static BuildCode _Instance = new BuildCode(); public static BuildCode Instance { get { return _Instance; } } private BuildCode() { } public void Read(string Input) { try { Console.WriteLine(Template.Instance.CodeTemplate, Input.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }


自从Windows 8.1废弃了GetVersionEx,必须写manifest才能正确获取系统版本之后,如何始终正确获取系统版本就是一个让人头疼的话题。大概有以下几种尝试。 尝试使用VerifyVersionInfo——实际上是没读懂微软的文档,这个函数也会被manifest影响 使用RtlGetVersion——早在XP时代就被很多人用于防止兼容模式了,所以Vista以后兼容模式也hook掉了,虽然不需要manifest,但是不保证以后还能用 使用RtlGetNtVersionNumbers——至今未文档化的函数,不推荐 实际上,有一种方法微软可能懒得造假,那就是VerQueryValue读取kernel32.dll等系统文件的产品版本号dwProductVersionMS/LS。有人可能会说,那以后微软不在这里写系统版本了怎么办?实际上,微软已经公开了这个做法,将来不可能大改,否则将有兼容性问题。 https://msdn.microsoft.com/en-us/library/windows/desktop/ms724429(v=vs.85).aspx 一般来说,系统文件版本号的格式是major.minor.build.rev。不过,已淘汰的Windows 95/98/Me并不遵守这个约定,它们的系统文件版本号是major.minor.0.build。不过,可以通过dwFileOS是否设置有VOS_DOS位来判断Windows 95/98/Me,而且它们的版本号都是4.x,现在的操作系统版本号早已超过5.0。 不建议读取dwFileVersionMS/LS,它是kernel32.dll文件本身的版本号,而不是系统的版本号。 为了保证可靠,这里也不推荐使用GetFileVersionInfo,因为Windows也可以在这个函数上做手脚。建议直接读取已加载的kernel32.dll模块的资源段,方法如下: GetModuleHandle获取已加载的kernel32.dll模块地址 LockResource(LoadResource(h, FindResource(h, n, t)))获取kernel32.dll资源段编号为1的RT_VERSION资源首地址 VerQueryValue查询路径为"\\"的版本信息,即VS_FIXEDFILEINFO结构体 将dwProductVersionMS和dwProductVersionLS组合成ULONGLONG,即为正确的系统版本 获得的ULONGLONG版本信息可以作为无符号数直接判断大小 #include <windows.h> #include <tchar.h> #pragma comment(lib, "version.lib") BOOL CheckModuleProductVersion(LPCTSTR modname, WORD major, WORD minor, WORD build, WORD rev) { HMODULE hmod = GetModuleHandle(modname); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; ULONGLONG ullProductVersion = ((ULONGLONG)pvsffi->dwProductVersionMS << 32) + pvsffi->dwProductVersionLS; ULONGLONG ullReqVersion = ((ULONGLONG)major << 48) + ((ULONGLONG)minor << 32) + ((ULONGLONG)build << 16) + rev; if (ullProductVersion < ullReqVersion) return FALSE; return TRUE; } BOOL GetOSVersion(PULONGLONG pullProductVersion, PBOOL piswin9x) { #pragma comment(lib, "version.lib") HMODULE hmod = GetModuleHandle(_T("kernel32.dll")); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; if (pullProductVersion) *pullProductVersion = ((ULONGLONG)pvsffi->dwProductVersionMS << 32) + pvsffi->dwProductVersionLS; if (piswin9x) *piswin9x = !!(pvsffi->dwFileOS & VOS_DOS); return TRUE; } BOOL Is_Win7_OrGreater() { ULONGLONG osver = 0; if (!GetOSVersion(&osver, NULL)) return FALSE; return osver >= (6UI64 << 48) + (1UI64 << 32); }

自从Windows 8.1废弃了GetVersionEx,必须写manifest才能正确获取系统版本之后,如何始终正确获取系统版本就是一个让人头疼的话题。大概有以下几种尝试。 尝试使用VerifyVersionInfo——实际上是没读懂微软的文档,这个函数也会被manifest影响 使用RtlGetVersion——早在XP时代就被很多人用于防止兼容模式了,所以Vista以后兼容模式也hook掉了,虽然不需要manifest,但是不保证以后还能用 使用RtlGetNtVersionNumbers——至今未文档化的函数,不推荐 实际上,有一种方法微软可能懒得造假,那就是VerQueryValue读取kernel32.dll等系统文件的产品版本号dwProductVersionMS/LS。有人可能会说,那以后微软不在这里写系统版本了怎么办?实际上,微软已经公开了这个做法,将来不可能大改,否则将有兼容性问题。 https://msdn.microsoft.com/en-us/library/windows/desktop/ms724429(v=vs.85).aspx 一般来说,系统文件版本号的格式是major.minor.build.rev。不过,已淘汰的Windows 95/98/Me并不遵守这个约定,它们的系统文件版本号是major.minor.0.build。不过,可以通过dwFileOS是否设置有VOS_DOS位来判断Windows 95/98/Me,而且它们的版本号都是4.x,现在的操作系统版本号早已超过5.0。 不建议读取dwFileVersionMS/LS,它是kernel32.dll文件本身的版本号,而不是系统的版本号。 为了保证可靠,这里也不推荐使用GetFileVersionInfo,因为Windows也可以在这个函数上做手脚。建议直接读取已加载的kernel32.dll模块的资源段,方法如下: GetModuleHandle获取已加载的kernel32.dll模块地址 LockResource(LoadResource(h, FindResource(h, n, t)))获取kernel32.dll资源段编号为1的RT_VERSION资源首地址 VerQueryValue查询路径为"\\"的版本信息,即VS_FIXEDFILEINFO结构体 将dwProductVersionMS和dwProductVersionLS组合成ULONGLONG,即为正确的系统版本 获得的ULONGLONG版本信息可以作为无符号数直接判断大小 #include <windows.h> #include <tchar.h> #pragma comment(lib, "version.lib") BOOL CheckModuleProductVersion(LPCTSTR modname, WORD major, WORD minor, WORD build, WORD rev) { HMODULE hmod = GetModuleHandle(modname); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; ULONGLONG ullProductVersion = ((ULONGLONG)pvsffi->dwProductVersionMS << 32) + pvsffi->dwProductVersionLS; ULONGLONG ullReqVersion = ((ULONGLONG)major << 48) + ((ULONGLONG)minor << 32) + ((ULONGLONG)build << 16) + rev; if (ullProductVersion < ullReqVersion) return FALSE; return TRUE; } BOOL GetOSVersion(PULONGLONG pullProductVersion, PBOOL piswin9x) { #pragma comment(lib, "version.lib") HMODULE hmod = GetModuleHandle(_T("kernel32.dll")); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; if (pullProductVersion) *pullProductVersion = ((ULONGLONG)pvsffi->dwProductVersionMS << 32) + pvsffi->dwProductVersionLS; if (piswin9x) *piswin9x = !!(pvsffi->dwFileOS & VOS_DOS); return TRUE; } BOOL Is_Win7_OrGreater() { ULONGLONG osver = 0; if (!GetOSVersion(&osver, NULL)) return FALSE; return osver >= (6UI64 << 48) + (1UI64 << 32); }



尽管很多框架非常方便,有时候我们还是希望尽量减少程序体积和复杂度,甚至连一个*.exe.config都不想要,这时候用C/C++调用DialogBox创建Win32对话框是最好的选项。 而为了更好地使用Win32对话框,需要学习Win32标准控件的使用。学习使用Win32标准控件的最佳途径是边练习边参考Windows SDK Documentation。 新版Windows SDK已经不带帮助文档,而Visual Studio自带的MSDN帮助文档的往往故意砍掉了最重要的User Interface\Windows User Interface一节,所以还是需要下载一份Windows SDK Documentation的。 WinSDK文档的下载可以在我的另一篇帖子里找到。 日常加载代码如下: #include <windows.h> #include <tchar.h> #include <windowsx.h> #include <commctrl.h> #include <commdlg.h> #pragma comment(lib, "comctl32.lib") INT_PTR CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _TCHAR *szCmdLine, int nShowCmd) { // comctl32.dll v4.70-5.81 compatible HMODULE hcomctl32 = LoadLibrary(TEXT("comctl32.dll")); INITCOMMONCONTROLSEX icc = { sizeof icc }; typedef BOOL(WINAPI*TInitCommonControlsEx)(LPINITCOMMONCONTROLSEX); TInitCommonControlsEx pInitCommonControlsEx = NULL; if (hcomctl32) pInitCommonControlsEx = (TInitCommonControlsEx)GetProcAddress(hcomctl32, "InitCommonControlsEx"); if (pInitCommonControlsEx) for (int i = 0; i < 16; i++) icc.dwICC = 1 << i, pInitCommonControlsEx(&icc); // riched20.dll LoadLibrary(TEXT("riched20.dll")); DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc, NULL); return 0; } 控件之外 User Interface、Graphics and Multimedia两章与用户界面有关,应该通读这些章节。 System Services、Networking等其它章节包含了大多数非用户界面的编程,也应该尽量多地了解。但是尽量还是在读完上面两章之后进行。 MFC与Win32的差别 经典MFC(MFC9.1之前的版本)除了这些控件之外,还有一些私有功能,比如静态窗口分割、动态窗口分割、浮动工具栏、ActiveX控件等。 实际上,这些东西不一定非要和MFC实现得完全一致。 工具软件用得最多的是静态分割。动态分割、浮动工具栏很少使用。 实际上,静态分割用鼠标事件代码很好实现: 鼠标左键按下:捕获鼠标、显示阴影 鼠标移动且按下了鼠标左键:消除旧阴影、显示新阴影 鼠标左键松开:消除阴影、重新布置控件、释放鼠标捕获 至于拖动阴影,虽然有的软件也没有实现,但是它可以在拖动的时候防止闪烁,提高用户体验,还是实现一下比较好。拖动阴影的实现方法:建立一个单色位图画刷,然后用PatBlt函数对矩形区域进行PATINVERT。 void InvertFillFocusRect(HDC hdc, int x, int y, int cx, int cy) { WORD patbits[] = { 0x80, 0x40 }; HBITMAP hpatbmp = CreateBitmap(2, 2, 1, 1, patbits); HBRUSH hpatbr = CreatePatternBrush(hpatbmp); HGDIOBJ holdbr = SelectObject(hdc, hpatbr); PatBlt(hdc, x, y, cx, cy, PATINVERT); SelectObject(hdc, holdbr); DeleteObject(hpatbr); DeleteObject(hpatbmp); } ActiveX功能主要用于嵌入WebBrowser控件,但是随着IE的退出竞争,这种方法越来越不好用了。现在一般使用CEF。实际上,如果使用WebBrowser,就要同时面对IE8/9/10/11四个版本,其中IE8经常导致排版混乱,甚至莫名其妙弹出一些对话框,大大降低用户体验。 但是CEF太臃肿了。对于不准备植入广告的工具软件来说,还是尽量用基于XmlHttpRequest或WebSockets的开放应用接口。实在没有可用的开放应用接口的话,再考虑使用正则表达式等工具分析HTTP文本。

DLL版本的一些细节 下面这个文章介绍了一些没有被微软写进文档里的细节,但是最好选择性地参考,不要依赖一些微软没有写进文档的做法,可能会降低程序的兼容性。 http://www.geoffchappell.com/studies/windows/shell/comctl32/ https://blogs.msdn.microsoft.com/murrays/2006/10/13/richedit-versions/ comctl32.dll版本的区别 传统版本: v4.0是Win95附带的,支持SysListView32,SysHeader32,SysTreeView32,SysTabControl32,ToolbarWindow32,msctls_statusbar32,msctls_trackbar32,msctls_updown32,msctls_progress32,msctls_hotkey32,SysAnimate32,tooltips_class32共12个控件(用于WinNT的v4.0版本非常少见,主要是IE3.00 for NT附带的版本,并且功能上介于v4.0和v4.70之间,可以忽略) v4.70是Win95OSR2和WinNT4附带的,但可以在Win95随IE3.01-3.02装上,增加了ComboBoxEx32,ReBarWindow32,SysMonthCal32,SysDateTimePick32共4个控件,现有控件进行了升级 v4.71可以在之前的系统随IE4.0装上,增加了SysIPAddress32,SysPager,NativeFontCtl共3个控件,现有控件进行了升级 v4.72是Win98附带的,但可以在之前的系统随IE4.01装上,现有控件进行了升级 v5.80是Win98SE附带的,但可以在之前的系统随IE5.0装上,现有控件进行了升级 v5.81是Win2000,WinMe附带的,但可以在之前的系统随IE5.01,5.5,6.0装上,现有控件进行了升级 v5.82是WinXP附带的,不能装在以前的系统上,不再需要InitCommonControlsEx,现有控件进行了升级 需要manifest的版本: v6.0是WinXP附带的,不能装在以前的系统上,需要manifest,改为使用uxtheme.dll而不是纯GDI渲染,不再需要InitCommonControlsEx,增加了SysLink控件,现有控件增加了一些功能,增强了预定义控件的功能 v6.10是WinVista附带的,不能装在以前的系统上,需要manifest,在v6.0的基础上继续增加了一些功能 关于SetWindowSubclass等函数 comctl32.dll中包含有SetWindowSubclass,GetWindowSubclass,RemoveWindowSubclass,DefSubclassProc四个函数,可以实现可拆卸的窗口过程替换。 v6.0按名称导出了全部的四个函数。 v5.82sp1按名称导出了除GetWindowSubclass以外的三个函数,GetWindowSubclass按照序号411导出。 v4.72-v5.82rtm均只按序号导出,没有按名称导出,序号依次是410,411,412,413。 不过个人并不推荐按序号使用这四个函数,因为微软并没有把这四个函数的序号写进文档里。推荐的做法: 如果需要使用全部四个函数,必须确保函数是WinXP+,并且使用manifest启用comctl32.dll v6.0。 如果需要使用除GetWindowSubclass以外的三个函数,但只使用comctl32.dll v5.82,必须确保系统是WinXPSP1+。 不满足以上条件,应使用GetWindowLongPtr/SetWindowLongPtr,GWLP_WNDPROC和CallWindowProc实现窗口过程替换,这种窗口过程替换是不可拆卸的,因为拆卸可能会导致调用链失效。 riched20.dll v2.0和v3.0的区别 Win98/98SE/NT4自带的riched20.dll版本号是v2.0(文件版本v5.0),功能不全,不过我们可以用instmsia.exe和instmsiw.exe将其更新成v3.0(文件版本v5.30)。Win2000/Me/XP/2003的riched20.dll是v3.0,WinVista以上的riched20.dll是v3.1(文件版本v5.31)。 v2.0和v3.0下载: (附件:275001) (附件:277088) 一般来说,应该总是认为riched20.dll是v3.0版本,并使用"RichEdit20A"和"RichEdit20W"。除此之外,也可以使用WinXPSP1附带的msftedit.dll(v4.1,"RichEdit50W")。虽然Office 2003/2007/2010里边的riched20.dll版本更高,但是没有官方文档,用起来问题比较多。 判断DLL版本的方法 comctl32.dll导出了一个叫做DllGetVersion的函数,可以获取DLL版本。不过它没有被放到comctl32.lib中,因为还有shell32.dll和shlwapi.dll也导出了这个函数。因此需要使用GetProcAddress调用它。它的原型在shlwapi.h头文件中定义为DLLGETVERSIONPROC。 不过由于像riched20.dll之类的大多数DLL都没有导出DllGetVersion函数,因此个人建议通过读取RT_VERSION资源的方式判断DLL文件版本号,这种方法才是最通用的方法。 这里要注意的是,riched20.dll版本v2.0的实际文件版本号是v5.0,而riched20.dll版本v3.0的实际文件版本号是v5.30。 #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <shlwapi.h> #include <tchar.h> //#include <strsafe.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "version.lib") BOOL CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL CheckModuleFileVersion(LPCTSTR modname, WORD major, WORD minor, WORD build, WORD rev) { HMODULE hmod = GetModuleHandle(modname); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; ULONGLONG ullFileVersion = ((ULONGLONG)pvsffi->dwFileVersionMS << 32) + pvsffi->dwFileVersionLS; ULONGLONG ullReqVersion = ((ULONGLONG)major << 48) + ((ULONGLONG)minor << 32) + ((ULONGLONG)build << 16) + rev; if (ullFileVersion < ullReqVersion) return FALSE; return TRUE; } BOOL CheckModuleProductVersion(LPCTSTR modname, WORD major, WORD minor, WORD build, WORD rev) { HMODULE hmod = GetModuleHandle(modname); if (!hmod) return FALSE; LPVOID verblock = LockResource(LoadResource(hmod, FindResource(hmod, MAKEINTRESOURCE(1), MAKEINTRESOURCE(RT_VERSION)))); if (!verblock) return FALSE; VS_FIXEDFILEINFO *pvsffi = NULL; UINT szvsffi = 0; if (!VerQueryValue(verblock, _T("\\"), (LPVOID*)&pvsffi, &szvsffi)) return FALSE; ULONGLONG ullProductVersion = ((ULONGLONG)pvsffi->dwProductVersionMS << 32) + pvsffi->dwProductVersionLS; ULONGLONG ullReqVersion = ((ULONGLONG)major << 48) + ((ULONGLONG)minor << 32) + ((ULONGLONG)build << 16) + rev; if (ullProductVersion < ullReqVersion) return FALSE; return TRUE; } void NoSupportedDllExit() { MessageBox(NULL, _T("此操作系统需要安装Internet Explorer 5.01和Windows Installer 1.0或以上版本的相应组件以运行此程序。"), _T("错误"), MB_ICONERROR); exit(1); } int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _TCHAR *szCmdLine, int nShowCmd) { // 检查系统支持 #ifdef UNICODE if (!CheckModuleProductVersion(_T("kernel32.dll"), 5, 0, 0, 0)) #else if (!CheckModuleProductVersion(_T("kernel32.dll"), 4, 10, 0, 0)) #endif { MessageBox(NULL, _T("此操作系统不受支持。"), _T("错误"), MB_ICONERROR); exit(1); } // comctl32.dll v4.70-5.81 compatible HMODULE hcomctl32 = LoadLibrary(TEXT("comctl32.dll")); INITCOMMONCONTROLSEX icc = { sizeof icc }; typedef BOOL(WINAPI*TInitCommonControlsEx)(LPINITCOMMONCONTROLSEX); TInitCommonControlsEx pInitCommonControlsEx = NULL; if (hcomctl32) pInitCommonControlsEx = (TInitCommonControlsEx)GetProcAddress(hcomctl32, "InitCommonControlsEx"); if (pInitCommonControlsEx) for (int i = 0; i < 16; i++) icc.dwICC = 1 << i, pInitCommonControlsEx(&icc); // riched20.dll LoadLibrary(TEXT("riched20.dll")); // 要求comctl32.dll版本v5.81和riched20.dll版本v3.0(实际文件版本v5.30) if (!CheckModuleFileVersion(_T("comctl32.dll"), 5, 81, 0, 0) || !CheckModuleFileVersion(_T("riched20.dll"), 5, 30, 0, 0)) NoSupportedDllExit(); DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc, NULL); return 0; }



Win10RS1/2/3的DPI支持改进简介 Win10RS1(1607/周年更新):新增一套API,使得每个窗口可以具有不同的DPI缩放兼容性,并大大简化了多显示器DPI编程。WPF可选支持多显示器DPI编程。 Win10RS2(1703/创意者更新):新增PerMonitorV2模式,简化Win32和WinForms的多显示器DPI编程。新增gdiScaling选项,改善DPI虚拟化画质。程序兼容型选项卡新增DPI兼容性设置,允许用户强行打开DPI虚拟化。 Win10RS3(秋季创意者更新,待发布):更改系统级DPI不再需要注销并重新登录,只需重新打开未响应DPI变化的程序即可。 WPF编程 .NET 4.6.2已经支持Win10 RS1的多显示器DPI支持模式,不过,为了使用这个功能,你还需要作以下操作: 1、声明程序支持Win10 RS1的多显示器DPI支持模式,但不支持Win8.1的多显示器DPI支持模式 编写App.manifest,在assembly标签下添加如下声明: <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <!-- The combination of below two tags have the following effect : 1) Per-Monitor for >= Windows 10 Anniversary Update 2) System < Windows 10 Anniversary Update --> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application> 2、设置目标框架为4.6.2以上,或者在App.config的configuration标签下添加如下声明: <runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime> Windows Forms编程 Windows Forms已经在.NET Framework 4.7支持Win10 RS2的多显示器DPI第二版。不过,由于Windows Forms的架构限制,实际上有一些bug,比如在频繁调整下,ToolStrip控件会越来越大。 **编写支持多显示器DPI的应用程序,建议使用WPF。**因为WPF基于浮点坐标和矢量绘图,能够更好地支持DPI的动态变化(Windows Forms基于整数坐标和像素绘图)。 如果你想尝试一下这个新功能,可以按照下面的方法声明。 https://blogs.msdn.microsoft.com/dotnet/2017/04/05/announcing-the-net-framework-4-7/ https://channel9.msdn.com/Blogs/dotnet/Introducing-Windows-Forms-HDPI-Improvements-in-NET-Framework-47 https://msdn.microsoft.com/library/mt492940.aspx 首先在App.manifest中允许.NET检测Win10系统版本 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <application> <!-- Windows Vista --> <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />--> <!-- Windows 7 --> <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />--> <!-- Windows 8 --> <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />--> <!-- Windows 8.1 --> <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />--> <!-- Windows 10 --> <!-- Allow detecting Windows 10 --> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> </application> </compatibility> 然后在App.config中指定目标版本为4.7,并开启Windows Forms的多显示器DPI支持模式第二版 <?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/> </startup> <System.Windows.Forms.ApplicationConfigurationSection> <add key="DpiAwareness" value="PerMonitorV2"/> </System.Windows.Forms.ApplicationConfigurationSection> </configuration> GDI缩放技术 Win10 RS2支持GDI缩放技术,可以在大多数情况下取代原有的像素缩放技术,优化一些内容的显示。 App.manifest声明方法: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0"> <assemblyIdentity type="win32" name="MyApp" version="1.0.0.0" /> <asmv3:application> <asmv3:windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">false</dpiAware> <gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling> </asmv3:windowsSettings> </asmv3:application> </assembly> Win32编程 Win10RS1添加了一些动态DPI函数,这些函数比Win8.1的那些使用起来更容易。 Win10RS2添加了多显示器DPI支持模式第二版,增加了对话框、菜单栏、非客户区的自动缩放支持,以及子控件的DPI消息通知。 经过实测,其它通用控件可以近乎完美缩放,但是工具栏图标不能自动缩放,还是需要处理一下WM_DPICHANGED,然后重新给工具栏指定一组更大的位图。 不一定要支持Windows 8.1/10TH1/10TH2的多显示器DPI支持模式,甚至连10RS1的都不一定要支持,因为多显示器动态DPI的API是逐渐完善的,而且需要使用多显示器DPI支持的用户一般倾向于升级到最新版Windows 10。 另外要注意,SetProcessDpiAwarenessContext函数,虽然MSDN上写的Win10RS2,实际上在Win10RS1已经导出了,所以如果以-4调用,一定要检查是否返回FALSE,返回FALSE的话需要再以-2调用一次。 App.mainfest多显示器DPI支持模式第二版声明方法: <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <!-- The combination of below two tags have the following effect : 1) Per-Monitor for >= Windows 10 Creators Update 2) System < Windows 10 Creators Update --> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,System</dpiAwareness> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application> 这里的声明取自Windows SDK 10.0.15063.0头文件,而非MSDN Library开发者文档。(2017/4/6更新) #pragma once #include <windows.h> #include <uxtheme.h> #define DefineDLLEntry(name) T_##name *name #define LoadDLLEntry(hmod, name) name = (T_##name *)GetProcAddress(hmod, #name) #define _Inout_ #define _In_ #define _Out_ #define _In_opt_ // Windows 7加入的窗口消息 #define WM_DPICHANGED 0x02E0 // Windows 10 RS2加入的窗口消息 #define WM_DPICHANGED_BEFOREPARENT 0x02E2 #define WM_DPICHANGED_AFTERPARENT 0x02E3 #define WM_GETDPISCALEDSIZE 0x02E4 #if NTDDI_VERSION < 0x0A000002 DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); typedef enum DPI_AWARENESS { DPI_AWARENESS_INVALID = -1, DPI_AWARENESS_UNAWARE = 0, DPI_AWARENESS_SYSTEM_AWARE = 1, DPI_AWARENESS_PER_MONITOR_AWARE = 2 } DPI_AWARENESS; #endif #if NTDDI_VERSION < 0x0A000003 typedef enum DIALOG_DPI_CHANGE_BEHAVIORS { DDC_DEFAULT = 0x0000, DDC_DISABLE_ALL = 0x0001, DDC_DISABLE_RESIZE = 0x0002, DDC_DISABLE_CONTROL_RELAYOUT = 0x0004, } DIALOG_DPI_CHANGE_BEHAVIORS; typedef enum DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS { DCDC_DEFAULT = 0x0000, DCDC_DISABLE_FONT_UPDATE = 0x0001, DCDC_DISABLE_RELAYOUT = 0x0002, } DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS; #endif // 不支持DPI #define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) // 系统级DPI(传统DPI模式) #define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) // 逐显示器DPI第一版(Win8.1/Win10RS1模式) #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) // 逐显示器DPI第二版(Win10RS2模式) // 支持对话框、菜单、非客户区自动缩放,支持子控件DPI通知 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) // Windows 10 RS1、Windows 10 RS2加入的高DPI支持API struct Win10DPI { typedef BOOL WINAPI T_AdjustWindowRectExForDpi( _Inout_ LPRECT lpRect, _In_ DWORD dwStyle, _In_ BOOL bMenu, _In_ DWORD dwExStyle, _In_ UINT dpi ); typedef BOOL WINAPI T_AreDpiAwarenessContextsEqual( _In_ DPI_AWARENESS_CONTEXT dpiContextA, _In_ DPI_AWARENESS_CONTEXT dpiContextB ); typedef BOOL WINAPI T_EnableNonClientDpiScaling( _In_ HWND hwnd ); typedef DPI_AWARENESS WINAPI T_GetAwarenessFromDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT value ); typedef UINT WINAPI T_GetDpiForSystem(void); typedef UINT WINAPI T_GetDpiForWindow( _In_ HWND hwnd ); typedef int WINAPI T_GetSystemMetricsForDpi( _In_ int nIndex, _In_ UINT dpi ); typedef DPI_AWARENESS_CONTEXT WINAPI T_GetThreadDpiAwarenessContext(void); typedef DPI_AWARENESS_CONTEXT WINAPI T_GetWindowDpiAwarenessContext( _In_ HWND hwnd ); typedef BOOL WINAPI T_IsValidDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT value ); // Windows 8.1加入,但仍然适用 typedef BOOL WINAPI T_LogicalToPhysicalPointForPerMonitorDPI( _In_ HWND hwnd, _Inout_ LPPOINT lpPoint ); // Windows 8.1加入,但仍然适用 typedef BOOL WINAPI T_PhysicalToLogicalPointForPerMonitorDPI( _In_ HWND hwnd, _Inout_ LPPOINT lpPoint ); typedef DPI_AWARENESS_CONTEXT WINAPI T_SetThreadDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT dpiContext ); typedef BOOL WINAPI T_SystemParametersInfoForDpi( _In_ UINT uiAction, _In_ UINT uiParam, _Inout_ PVOID pvParam, _In_ UINT fWinIni, _In_ UINT dpi ); // Windows 10 RS2加入 // 这个其实Windows 10 RS1已经导出了 typedef BOOL WINAPI T_SetProcessDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT value ); typedef BOOL WINAPI T_SetDialogDpiChangeBehavior( _In_ HWND hDlg, _In_ DIALOG_DPI_CHANGE_BEHAVIORS mask, _In_ DIALOG_DPI_CHANGE_BEHAVIORS values ); typedef DIALOG_DPI_CHANGE_BEHAVIORS WINAPI T_GetDialogDpiChangeBehavior( _In_ HWND hDlg ); typedef BOOL WINAPI T_SetDialogControlDpiChangeBehavior( _In_ HWND hWnd, _In_ DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS mask, _In_ DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS values ); typedef DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS WINAPI T_GetDialogControlDpiChangeBehavior( _In_ HWND hWnd ); typedef HTHEME WINAPI T_OpenThemeDataForDpi( _In_opt_ HWND hwnd, _In_ LPCWSTR pszClassList, _In_ UINT dpi ); DefineDLLEntry(AdjustWindowRectExForDpi); DefineDLLEntry(AreDpiAwarenessContextsEqual); DefineDLLEntry(EnableNonClientDpiScaling); DefineDLLEntry(GetAwarenessFromDpiAwarenessContext); DefineDLLEntry(GetDpiForSystem); DefineDLLEntry(GetDpiForWindow); DefineDLLEntry(GetSystemMetricsForDpi); DefineDLLEntry(GetThreadDpiAwarenessContext); DefineDLLEntry(GetWindowDpiAwarenessContext); DefineDLLEntry(IsValidDpiAwarenessContext); DefineDLLEntry(LogicalToPhysicalPointForPerMonitorDPI); DefineDLLEntry(PhysicalToLogicalPointForPerMonitorDPI); DefineDLLEntry(SetThreadDpiAwarenessContext); DefineDLLEntry(SystemParametersInfoForDpi); DefineDLLEntry(SetProcessDpiAwarenessContext); DefineDLLEntry(SetDialogDpiChangeBehavior); DefineDLLEntry(GetDialogDpiChangeBehavior); DefineDLLEntry(SetDialogControlDpiChangeBehavior); DefineDLLEntry(GetDialogControlDpiChangeBehavior); Win10DPI() { HMODULE huser32 = GetModuleHandleA("user32"); LoadDLLEntry(huser32, AdjustWindowRectExForDpi); LoadDLLEntry(huser32, AreDpiAwarenessContextsEqual); LoadDLLEntry(huser32, EnableNonClientDpiScaling); LoadDLLEntry(huser32, GetAwarenessFromDpiAwarenessContext); LoadDLLEntry(huser32, GetDpiForSystem); LoadDLLEntry(huser32, GetDpiForWindow); LoadDLLEntry(huser32, GetSystemMetricsForDpi); LoadDLLEntry(huser32, GetThreadDpiAwarenessContext); LoadDLLEntry(huser32, GetWindowDpiAwarenessContext); LoadDLLEntry(huser32, IsValidDpiAwarenessContext); LoadDLLEntry(huser32, LogicalToPhysicalPointForPerMonitorDPI); LoadDLLEntry(huser32, PhysicalToLogicalPointForPerMonitorDPI); LoadDLLEntry(huser32, SetThreadDpiAwarenessContext); LoadDLLEntry(huser32, SystemParametersInfoForDpi); LoadDLLEntry(huser32, SetProcessDpiAwarenessContext); LoadDLLEntry(huser32, SetDialogDpiChangeBehavior); LoadDLLEntry(huser32, GetDialogDpiChangeBehavior); LoadDLLEntry(huser32, SetDialogControlDpiChangeBehavior); LoadDLLEntry(huser32, GetDialogControlDpiChangeBehavior); } }; struct WinVistaDPI { typedef BOOL WINAPI T_SetProcessDPIAware(VOID); typedef BOOL WINAPI T_IsProcessDPIAware(VOID); typedef BOOL WINAPI T_LogicalToPhysicalPoint( _In_ HWND hWnd, _Inout_ LPPOINT lpPoint ); typedef BOOL WINAPI T_PhysicalToLogicalPoint( _In_ HWND hWnd, _Inout_ LPPOINT lpPoint ); DefineDLLEntry(SetProcessDPIAware); DefineDLLEntry(IsProcessDPIAware); DefineDLLEntry(LogicalToPhysicalPoint); DefineDLLEntry(PhysicalToLogicalPoint); WinVistaDPI() { HMODULE huser32 = GetModuleHandleA("user32"); LoadDLLEntry(huser32, SetProcessDPIAware); LoadDLLEntry(huser32, IsProcessDPIAware); LoadDLLEntry(huser32, LogicalToPhysicalPoint); LoadDLLEntry(huser32, PhysicalToLogicalPoint); } }; // Win8.1加入,但已不适用的API #ifdef WIN81DPI_DEPRECATED typedef enum _MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } Monitor_DPI_Type; typedef enum _PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; typedef enum _SHELL_UI_COMPONENT { SHELL_UI_COMPONENT_TASKBARS = 0, SHELL_UI_COMPONENT_NOTIFICATIONAREA = 1, SHELL_UI_COMPONENT_DESKBAND = 2 } SHELL_UI_COMPONENT; typedef HRESULT WINAPI T_GetDpiForMonitor( _In_ HMONITOR hmonitor, _In_ MONITOR_DPI_TYPE dpiType, _Out_ UINT *dpiX, _Out_ UINT *dpiY ); typedef UINT WINAPI T_GetDpiForShellUiComponent( _In_ SHELL_UI_COMPONENT component ); typedef HRESULT WINAPI T_GetProcessDpiAwareness( _In_ HANDLE hprocess, _Out_ PROCESS_DPI_AWARENESS *value ); typedef HRESULT WINAPI T_SetProcessDpiAwareness( _In_ PROCESS_DPI_AWARENESS value ); #endif (未完待续)

Win10RS1/2/3的DPI支持改进简介 Win10RS1(1607/周年更新):新增一套API,使得每个窗口可以具有不同的DPI缩放兼容性,并大大简化了多显示器DPI编程。WPF可选支持多显示器DPI编程。 Win10RS2(1703/创意者更新):新增PerMonitorV2模式,简化Win32和WinForms的多显示器DPI编程。新增gdiScaling选项,改善DPI虚拟化画质。程序兼容型选项卡新增DPI兼容性设置,允许用户强行打开DPI虚拟化。 Win10RS3(秋季创意者更新,待发布):更改系统级DPI不再需要注销并重新登录,只需重新打开未响应DPI变化的程序即可。 WPF编程 .NET 4.6.2已经支持Win10 RS1的多显示器DPI支持模式,不过,为了使用这个功能,你还需要作以下操作: 1、声明程序支持Win10 RS1的多显示器DPI支持模式,但不支持Win8.1的多显示器DPI支持模式 编写App.manifest,在assembly标签下添加如下声明: <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <!-- The combination of below two tags have the following effect : 1) Per-Monitor for >= Windows 10 Anniversary Update 2) System < Windows 10 Anniversary Update --> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application> 2、设置目标框架为4.6.2以上,或者在App.config的configuration标签下添加如下声明: <runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime> Windows Forms编程 Windows Forms已经在.NET Framework 4.7支持Win10 RS2的多显示器DPI第二版。不过,由于Windows Forms的架构限制,实际上有一些bug,比如在频繁调整下,ToolStrip控件会越来越大。 **编写支持多显示器DPI的应用程序,建议使用WPF。**因为WPF基于浮点坐标和矢量绘图,能够更好地支持DPI的动态变化(Windows Forms基于整数坐标和像素绘图)。 如果你想尝试一下这个新功能,可以按照下面的方法声明。 https://blogs.msdn.microsoft.com/dotnet/2017/04/05/announcing-the-net-framework-4-7/ https://channel9.msdn.com/Blogs/dotnet/Introducing-Windows-Forms-HDPI-Improvements-in-NET-Framework-47 https://msdn.microsoft.com/library/mt492940.aspx 首先在App.manifest中允许.NET检测Win10系统版本 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <application> <!-- Windows Vista --> <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />--> <!-- Windows 7 --> <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />--> <!-- Windows 8 --> <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />--> <!-- Windows 8.1 --> <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />--> <!-- Windows 10 --> <!-- Allow detecting Windows 10 --> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> </application> </compatibility> 然后在App.config中指定目标版本为4.7,并开启Windows Forms的多显示器DPI支持模式第二版 <?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/> </startup> <System.Windows.Forms.ApplicationConfigurationSection> <add key="DpiAwareness" value="PerMonitorV2"/> </System.Windows.Forms.ApplicationConfigurationSection> </configuration> GDI缩放技术 Win10 RS2支持GDI缩放技术,可以在大多数情况下取代原有的像素缩放技术,优化一些内容的显示。 App.manifest声明方法: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0"> <assemblyIdentity type="win32" name="MyApp" version="1.0.0.0" /> <asmv3:application> <asmv3:windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">false</dpiAware> <gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling> </asmv3:windowsSettings> </asmv3:application> </assembly> Win32编程 Win10RS1添加了一些动态DPI函数,这些函数比Win8.1的那些使用起来更容易。 Win10RS2添加了多显示器DPI支持模式第二版,增加了对话框、菜单栏、非客户区的自动缩放支持,以及子控件的DPI消息通知。 经过实测,其它通用控件可以近乎完美缩放,但是工具栏图标不能自动缩放,还是需要处理一下WM_DPICHANGED,然后重新给工具栏指定一组更大的位图。 不一定要支持Windows 8.1/10TH1/10TH2的多显示器DPI支持模式,甚至连10RS1的都不一定要支持,因为多显示器动态DPI的API是逐渐完善的,而且需要使用多显示器DPI支持的用户一般倾向于升级到最新版Windows 10。 另外要注意,SetProcessDpiAwarenessContext函数,虽然MSDN上写的Win10RS2,实际上在Win10RS1已经导出了,所以如果以-4调用,一定要检查是否返回FALSE,返回FALSE的话需要再以-2调用一次。 App.mainfest多显示器DPI支持模式第二版声明方法: <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <!-- The combination of below two tags have the following effect : 1) Per-Monitor for >= Windows 10 Creators Update 2) System < Windows 10 Creators Update --> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,System</dpiAwareness> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application> 这里的声明取自Windows SDK 10.0.15063.0头文件,而非MSDN Library开发者文档。(2017/4/6更新) #pragma once #include <windows.h> #include <uxtheme.h> #define DefineDLLEntry(name) T_##name *name #define LoadDLLEntry(hmod, name) name = (T_##name *)GetProcAddress(hmod, #name) #define _Inout_ #define _In_ #define _Out_ #define _In_opt_ // Windows 7加入的窗口消息 #define WM_DPICHANGED 0x02E0 // Windows 10 RS2加入的窗口消息 #define WM_DPICHANGED_BEFOREPARENT 0x02E2 #define WM_DPICHANGED_AFTERPARENT 0x02E3 #define WM_GETDPISCALEDSIZE 0x02E4 #if NTDDI_VERSION < 0x0A000002 DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); typedef enum DPI_AWARENESS { DPI_AWARENESS_INVALID = -1, DPI_AWARENESS_UNAWARE = 0, DPI_AWARENESS_SYSTEM_AWARE = 1, DPI_AWARENESS_PER_MONITOR_AWARE = 2 } DPI_AWARENESS; #endif #if NTDDI_VERSION < 0x0A000003 typedef enum DIALOG_DPI_CHANGE_BEHAVIORS { DDC_DEFAULT = 0x0000, DDC_DISABLE_ALL = 0x0001, DDC_DISABLE_RESIZE = 0x0002, DDC_DISABLE_CONTROL_RELAYOUT = 0x0004, } DIALOG_DPI_CHANGE_BEHAVIORS; typedef enum DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS { DCDC_DEFAULT = 0x0000, DCDC_DISABLE_FONT_UPDATE = 0x0001, DCDC_DISABLE_RELAYOUT = 0x0002, } DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS; #endif // 不支持DPI #define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) // 系统级DPI(传统DPI模式) #define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) // 逐显示器DPI第一版(Win8.1/Win10RS1模式) #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) // 逐显示器DPI第二版(Win10RS2模式) // 支持对话框、菜单、非客户区自动缩放,支持子控件DPI通知 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) // Windows 10 RS1、Windows 10 RS2加入的高DPI支持API struct Win10DPI { typedef BOOL WINAPI T_AdjustWindowRectExForDpi( _Inout_ LPRECT lpRect, _In_ DWORD dwStyle, _In_ BOOL bMenu, _In_ DWORD dwExStyle, _In_ UINT dpi ); typedef BOOL WINAPI T_AreDpiAwarenessContextsEqual( _In_ DPI_AWARENESS_CONTEXT dpiContextA, _In_ DPI_AWARENESS_CONTEXT dpiContextB ); typedef BOOL WINAPI T_EnableNonClientDpiScaling( _In_ HWND hwnd ); typedef DPI_AWARENESS WINAPI T_GetAwarenessFromDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT value ); typedef UINT WINAPI T_GetDpiForSystem(void); typedef UINT WINAPI T_GetDpiForWindow( _In_ HWND hwnd ); typedef int WINAPI T_GetSystemMetricsForDpi( _In_ int nIndex, _In_ UINT dpi ); typedef DPI_AWARENESS_CONTEXT WINAPI T_GetThreadDpiAwarenessContext(void); typedef DPI_AWARENESS_CONTEXT WINAPI T_GetWindowDpiAwarenessContext( _In_ HWND hwnd ); typedef BOOL WINAPI T_IsValidDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT value ); // Windows 8.1加入,但仍然适用 typedef BOOL WINAPI T_LogicalToPhysicalPointForPerMonitorDPI( _In_ HWND hwnd, _Inout_ LPPOINT lpPoint ); // Windows 8.1加入,但仍然适用 typedef BOOL WINAPI T_PhysicalToLogicalPointForPerMonitorDPI( _In_ HWND hwnd, _Inout_ LPPOINT lpPoint ); typedef DPI_AWARENESS_CONTEXT WINAPI T_SetThreadDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT dpiContext ); typedef BOOL WINAPI T_SystemParametersInfoForDpi( _In_ UINT uiAction, _In_ UINT uiParam, _Inout_ PVOID pvParam, _In_ UINT fWinIni, _In_ UINT dpi ); // Windows 10 RS2加入 // 这个其实Windows 10 RS1已经导出了 typedef BOOL WINAPI T_SetProcessDpiAwarenessContext( _In_ DPI_AWARENESS_CONTEXT value ); typedef BOOL WINAPI T_SetDialogDpiChangeBehavior( _In_ HWND hDlg, _In_ DIALOG_DPI_CHANGE_BEHAVIORS mask, _In_ DIALOG_DPI_CHANGE_BEHAVIORS values ); typedef DIALOG_DPI_CHANGE_BEHAVIORS WINAPI T_GetDialogDpiChangeBehavior( _In_ HWND hDlg ); typedef BOOL WINAPI T_SetDialogControlDpiChangeBehavior( _In_ HWND hWnd, _In_ DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS mask, _In_ DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS values ); typedef DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS WINAPI T_GetDialogControlDpiChangeBehavior( _In_ HWND hWnd ); typedef HTHEME WINAPI T_OpenThemeDataForDpi( _In_opt_ HWND hwnd, _In_ LPCWSTR pszClassList, _In_ UINT dpi ); DefineDLLEntry(AdjustWindowRectExForDpi); DefineDLLEntry(AreDpiAwarenessContextsEqual); DefineDLLEntry(EnableNonClientDpiScaling); DefineDLLEntry(GetAwarenessFromDpiAwarenessContext); DefineDLLEntry(GetDpiForSystem); DefineDLLEntry(GetDpiForWindow); DefineDLLEntry(GetSystemMetricsForDpi); DefineDLLEntry(GetThreadDpiAwarenessContext); DefineDLLEntry(GetWindowDpiAwarenessContext); DefineDLLEntry(IsValidDpiAwarenessContext); DefineDLLEntry(LogicalToPhysicalPointForPerMonitorDPI); DefineDLLEntry(PhysicalToLogicalPointForPerMonitorDPI); DefineDLLEntry(SetThreadDpiAwarenessContext); DefineDLLEntry(SystemParametersInfoForDpi); DefineDLLEntry(SetProcessDpiAwarenessContext); DefineDLLEntry(SetDialogDpiChangeBehavior); DefineDLLEntry(GetDialogDpiChangeBehavior); DefineDLLEntry(SetDialogControlDpiChangeBehavior); DefineDLLEntry(GetDialogControlDpiChangeBehavior); Win10DPI() { HMODULE huser32 = GetModuleHandleA("user32"); LoadDLLEntry(huser32, AdjustWindowRectExForDpi); LoadDLLEntry(huser32, AreDpiAwarenessContextsEqual); LoadDLLEntry(huser32, EnableNonClientDpiScaling); LoadDLLEntry(huser32, GetAwarenessFromDpiAwarenessContext); LoadDLLEntry(huser32, GetDpiForSystem); LoadDLLEntry(huser32, GetDpiForWindow); LoadDLLEntry(huser32, GetSystemMetricsForDpi); LoadDLLEntry(huser32, GetThreadDpiAwarenessContext); LoadDLLEntry(huser32, GetWindowDpiAwarenessContext); LoadDLLEntry(huser32, IsValidDpiAwarenessContext); LoadDLLEntry(huser32, LogicalToPhysicalPointForPerMonitorDPI); LoadDLLEntry(huser32, PhysicalToLogicalPointForPerMonitorDPI); LoadDLLEntry(huser32, SetThreadDpiAwarenessContext); LoadDLLEntry(huser32, SystemParametersInfoForDpi); LoadDLLEntry(huser32, SetProcessDpiAwarenessContext); LoadDLLEntry(huser32, SetDialogDpiChangeBehavior); LoadDLLEntry(huser32, GetDialogDpiChangeBehavior); LoadDLLEntry(huser32, SetDialogControlDpiChangeBehavior); LoadDLLEntry(huser32, GetDialogControlDpiChangeBehavior); } }; struct WinVistaDPI { typedef BOOL WINAPI T_SetProcessDPIAware(VOID); typedef BOOL WINAPI T_IsProcessDPIAware(VOID); typedef BOOL WINAPI T_LogicalToPhysicalPoint( _In_ HWND hWnd, _Inout_ LPPOINT lpPoint ); typedef BOOL WINAPI T_PhysicalToLogicalPoint( _In_ HWND hWnd, _Inout_ LPPOINT lpPoint ); DefineDLLEntry(SetProcessDPIAware); DefineDLLEntry(IsProcessDPIAware); DefineDLLEntry(LogicalToPhysicalPoint); DefineDLLEntry(PhysicalToLogicalPoint); WinVistaDPI() { HMODULE huser32 = GetModuleHandleA("user32"); LoadDLLEntry(huser32, SetProcessDPIAware); LoadDLLEntry(huser32, IsProcessDPIAware); LoadDLLEntry(huser32, LogicalToPhysicalPoint); LoadDLLEntry(huser32, PhysicalToLogicalPoint); } }; // Win8.1加入,但已不适用的API #ifdef WIN81DPI_DEPRECATED typedef enum _MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } Monitor_DPI_Type; typedef enum _PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; typedef enum _SHELL_UI_COMPONENT { SHELL_UI_COMPONENT_TASKBARS = 0, SHELL_UI_COMPONENT_NOTIFICATIONAREA = 1, SHELL_UI_COMPONENT_DESKBAND = 2 } SHELL_UI_COMPONENT; typedef HRESULT WINAPI T_GetDpiForMonitor( _In_ HMONITOR hmonitor, _In_ MONITOR_DPI_TYPE dpiType, _Out_ UINT *dpiX, _Out_ UINT *dpiY ); typedef UINT WINAPI T_GetDpiForShellUiComponent( _In_ SHELL_UI_COMPONENT component ); typedef HRESULT WINAPI T_GetProcessDpiAwareness( _In_ HANDLE hprocess, _Out_ PROCESS_DPI_AWARENESS *value ); typedef HRESULT WINAPI T_SetProcessDpiAwareness( _In_ PROCESS_DPI_AWARENESS value ); #endif (未完待续)


使用GdiGradientFill和GdiAlphaBlend函数,可以绘制带有Alpha通道的透明图形,但是还没有实现除锯齿,因为GdiAlphaBlend函数不支持HALFTONE缩放模式。 实际上,我们可以使用StretchBlt先将背景2倍放大拷出来,然后GdiAlphaBlend混合,最后再StretchBlt将混合后的图形2倍缩小拷回去来实现除锯齿。 void GdiAlphaBlendFac(HDC hdc1, int x, int y, int width, int height, HDC hdc2, int x2, int y2, int fac, unsigned constalpha) { // 先将背景放大 HDC hdc3 = CreateCompatibleDC(hdc1); HBITMAP hbmp3 = CreateCompatibleBitmap(hdc1, width * fac, height * fac); SelectObject(hdc3, hbmp3); SetStretchBltMode(hdc3, HALFTONE); SetBrushOrgEx(hdc3, 0, 0, NULL); StretchBlt(hdc3, 0, 0, width * fac, height * fac, hdc1, x, y, width, height, SRCCOPY); // 然后将图形图层混合进去 BLENDFUNCTION bf = { AC_SRC_OVER, 0, (BYTE)constalpha, AC_SRC_ALPHA }; GdiAlphaBlend(hdc3, 0, 0, width * fac, height * fac, hdc2, x2, y2, width * fac, height * fac, bf); // 最后将背景连同图形图层缩小 SetStretchBltMode(hdc1, HALFTONE); SetBrushOrgEx(hdc1, 0, 0, NULL); StretchBlt(hdc1, x, y, width, height, hdc3, 0, 0, width * fac, height * fac, SRCCOPY); DeleteDC(hdc3); DeleteObject(hbmp3); } 绘图代码: case WM_ERASEBKGND: // 去掉系统托管的背景擦除阶段,防止闪烁 return 0; case WM_PAINT: // 窗口客户区需要更新 { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: 在此处添加使用 hdc 的任何绘图代码... // 内存位图,用来加速绘制,防止闪烁 RECT rc; GetClientRect(hWnd, &rc); HDC hdc1 = CreateCompatibleDC(hdc); HBITMAP hbmp1 = CreateCompatibleBitmap(hdc, rc.right - rc.left, rc.bottom - rc.top); SelectObject(hdc1, hbmp1); FillRect(hdc1, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH)); // 首先进行一些普通GDI绘图函数,作为衬底图形 SelectObject(hdc1, GetStockObject(NULL_BRUSH)); MoveToEx(hdc1, 0, 0, NULL); LineTo(hdc1, 150, 100); Ellipse(hdc1, 10, 40, 120, 80); // 创建带有Alpha通道的32位位图,作为图形图层 HDC hdc2 = CreateCompatibleDC(hdc1); HBITMAP hbmp2 = CreateBitmap(200, 200, 1, 32, NULL); SelectObject(hdc2, hbmp2); // 注意: // 只能使用GdiGradientFill函数进行绘图! // RGB值必须预乘Alpha值! TRIVERTEX tvrc[] = { { 0, 0, 0x0000, 0x0000, 0x0000, 0x0000 }, { 200, 200, 0x0000, 0x0000, 0x0000, 0x0000 }, }; GRADIENT_TRIANGLE grc[] = { { 0, 1 } }; GdiGradientFill(hdc2, tvrc, 2, grc, 1, GRADIENT_FILL_RECT_V); TRIVERTEX tvtri[] = { { 100, 0, 0xff00, 0x0000, 0x0000, 0xff00 }, { 200, 200, 0x0000, 0xff00, 0x0000, 0xff00 }, { 0, 200, 0x0000, 0x0000, 0xff00, 0xff00 }, }; GRADIENT_TRIANGLE gtri[] = { { 0, 1, 2 } }; GdiGradientFill(hdc2, tvtri, 3, gtri, 1, GRADIENT_FILL_TRIANGLE); // 进行带有HALFTONE除锯齿的AlphaBlend // 注意,必须是整数倍,否则背景会失真 GdiAlphaBlendFac(hdc1, 10, 10, 100, 100, hdc2, 0, 0, 2, 0xff); // 删除图形图层 DeleteDC(hdc2); DeleteObject(hbmp2); // 内存位图上屏 BitBlt(hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hdc1, 0, 0, SRCCOPY); DeleteDC(hdc1); DeleteObject(hbmp1); EndPaint(hWnd, &ps); return 0; } 最终效果: 放大:



nkc Development Server  https://github.com/kccd/nkc

科创研究院 (c)2005-2016

蜀ICP备11004945号-2 川公网安备51010802000058号