用了n多年的OutputDebugString作为调试输出信息的方法,很顺手,在VS2008中用起来也依然顺手,VS2010亦然.
马上2012了,我想如果真的有末日,就在末日来临之前,让我尝试下与以往不同的方法,不要让生命留下什么遗憾.所以我就想能不能使用一个控制台窗口来作为辅助的调试信息输出窗口,甚至可以用它来接收一些控制命令,从而更灵活的控制程序的调试呢?于是又一个大胆的尝试历程开始了,和平常一样,这个过程耗费了我近20多个小时生命,谋杀了近3个左右的脑细胞,结果终于出来了,下面就分享给大家,为了让大家节约近20多个小时生命和3个左右脑细胞的开支.
控制台应用在Windows NT内核平台上,已经不再是传统意义上的Dos On Windows了,而是纯粹的另一类应用程序——Console应用,也就是控制台应用.它虽然看上去和那个Dos命令窗口很像,但是内核已经完全变了,它是纯32位(或64位)的了,而且有很多更加强大的命令行工具采用这种形式来运行.
在NT内核中,其实命令行应用和普通的窗口应用之间,并没有太大的本质上的区别,只是API层面的区别而已,两者其实都需要建立和显示窗口……还有更多的异同我就不再去总结了,一般的VC++开发人员应该早就烂熟于心了.
当今的时代是一个融会贯通的年代了:OpenGL和DirectX可以互操作了,现代化妆术让美与丑的界限消失了,据说科学家亦由此发现了恐龙灭绝的秘密,现代医学则模糊了性别的界限,而平板也越来越像手机了,手机则越来越像电脑了,不一而足,这里我也来讲讲如何跨越控制台应用和窗口应用间的所谓鸿沟,以顺应时代的需求,所谓与时俱进嘛!
虽然从理论上讲,控制台应用和窗口应用并无本质区别,但是二者的彻底融合还是需要些技巧的.当然控制台程序中显示窗口,则是小菜一碟,也已经有例子了,本文就不在罗嗦了,这里我们重点来看看窗口应用中如何嵌入控制台窗口.并可以用传统的C库函数进行输入输出操作.这也是为了让广大网友学习的那些标准输入输出知识能在窗口应用横飞的年代中保值并增值.
因为是Windows平台,所以遇到问题的第一解决方案就是搜索API,只要找到一个API,那么在MSDN中就可以找到一组API,运气好的话还可以找到完整的例子.于是我打开MSDN,按照记忆输入了Console Functions结果一无所获,只好又从记忆深处搜索一个名为SetStdHandle的API,找到了,忒好咧!找到了一组Console Functions,哈哈,发财啦!挨个搜索了一下,居然没有CreateConsole,郁闷!于是返回SetStdHandle看看它提示这个句柄怎么来的,它说要CreateFile,哦,对了自己昏头了,Console嘛,就是要CreateFile创建嘛,字符设备嘛,ok,看CreateFile,MSDN上说,要创建控制台,那么就用文件名CONIN$和CONOUT$分别来创建输入和输出控制台,呵呵,啥也不说了,VS2008伺候!用向导建个最简单的Windows应用先,然后在_tWinMain中调用CreateFile,运行,结果……啥也没有!~!@#$%^&*
不怕,咱还有互联网嘛,可是搜啥呢?算了还是搜CONOUT$和CONIN$试试,呵呵,运气不错搜到了,有个哥们说BCB窗口应用中调用AllocConsole然后调用freopen重打开下CONOUT$和CONIN$之后调用printf就行了.试了试,嗨,效果还真好.继续深入,找到这组方法的UNICODE版wprintf实验,结果啥也没输出,printf就行.纳了闷了.最后发现,原来没有设置标准库的代码页,晕,调用setlocale(LC_ALL,”chs”);一切ok了.
紧接着又实验了scanf,wscanf,多线程,一切正常,输入输出在窗口中和往常一样方便.不多说了,直接上代码吧:
// Window And Console.cpp : 定义应用程序的入口点。
// 以下宏定义要求的最低平台。要求的最低平台
// 是具有运行应用程序所需功能的Windows、Internet Explorer 等产品的
// 最早版本。通过在指定版本及更低版本的平台上启用所有可用的功能,宏可以
// 正常工作。
// 如果必须要针对低于以下指定版本的平台,请修改下列定义。
// 有关不同平台对应值的最新信息,请参考MSDN。
#ifndef WINVER // 指定要求的最低平台是Windows Vista。
#define WINVER 0x0600 // 将此值更改为相应的值,以适用于Windows 的其他版本。
#endif
#ifndef _WIN32_WINNT // 指定要求的最低平台是Windows Vista。
#define _WIN32_WINNT 0x0600 // 将此值更改为相应的值,以适用于Windows 的其他版本。
#endif
#ifndef _WIN32_WINDOWS // 指定要求的最低平台是Windows 98。
#define _WIN32_WINDOWS 0x0410 // 将此值更改为适当的值,以适用于Windows Me 或更高版本。
#endif
#ifndef _WIN32_IE // 指定要求的最低平台是Internet Explorer 7.0。
#define _WIN32_IE 0x0700 // 将此值更改为相应的值,以适用于IE 的其他版本。
#endif
#define WIN32_LEAN_AND_MEAN // 从Windows 头中排除极少使用的资料
// Windows 头文件:
#include
#include
#include
#include "resource.h"
#include
#include
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
//
HANDLE g_hInput = NULL;
HANDLE g_hOutput = NULL;
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
unsigned __stdcall InPutOutPutThread( void * );
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
AllocConsole();
_tfreopen(_T("CONOUT$"), _T("w+t"),stdout);
_tfreopen(_T("CONIN$"), _T("r+t"),stdin);
_tsetlocale(LC_ALL,_T("chs") );
// TODO: 在此放置代码。
MSG msg;
HACCEL hAccelTable;
//HINSTANCE hInstance = GetModuleHandle(NULL);
//int nCmdShow = SW_SHOW;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WINDOWANDCONSOLE, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWANDCONSOLE));
HANDLE hConsoleIOThread = (HANDLE)_beginthreadex(NULL,0,InPutOutPutThread,NULL,CREATE_SUSPENDED,NULL);
//std::wcout<<"开始消息循环!"< _tprintf(_T("开始消息循环!\n")); wprintf(L"UNICODE 开始消息循环!\n"); printf("MBCS 开始消息循环!\n"); //OutputDebugString(_T("开始消息循环!\n")); ResumeThread(hConsoleIOThread); // 主消息循环: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } _tprintf(_T("消息循环结束!\n")); _tsystem(_T("PAUSE")); FreeConsole(); return (int) msg.wParam; } // // 函数: MyRegisterClass() // // 目的: 注册窗口类。 // // 注释: // // 仅当希望 // 此代码与添加到Windows 95 中的“RegisterClassEx” // 函数之前的Win32 系统兼容时,才需要此函数及其用法。调用此函数十分重要, // 这样应用程序就可以获得关联的 // “格式正确的”小图标。 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWANDCONSOLE)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WINDOWANDCONSOLE); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex); } // // 函数: InitInstance(HINSTANCE, int) // // 目的: 保存实例句柄并创建主窗口 // // 注释: // // 在此函数中,我们在全局变量中保存实例句柄并 // 创建和显示主程序窗口。 // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; // 将实例句柄存储在全局变量中 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } // // 函数: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: 处理主窗口的消息。 // // WM_COMMAND - 处理应用程序菜单 // WM_PAINT - 绘制主窗口 // WM_DESTROY - 发送退出消息并返回 // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_CREATE: { int iInput = 0; _tprintf(_T("请输入一个数字以便打开主窗口:")); _tscanf(_T("%d"),&iInput); _tprintf(_T("您输入的数字是:%d\n"),iInput); } break; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // 分析菜单选择: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: 在此添加任意绘图代码... EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // “关于”框的消息处理程序。 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; } unsigned __stdcall InPutOutPutThread( void * ) { _tprintf(_T("控制台输入输出线程启动!\n")); int iInput = 0; while(TRUE) { _tprintf(_T("请输入一个Int值(输入自动停止接受输入):")); _tscanf(_T("%d"),&iInput); _tprintf(_T("您输入的int值是:%d\n"),iInput); if( 0 == iInput ) { break; } } return 0; } 最后需要说明下,C++的cin/cout等标准输入输出对象也可以直接使用,效果和printf等c函数是一样的,并且如果你确定项目中只使用C++方式的输入输出,那么freopen都可以不调用了,只调用AllocConsole即可.最后需要释放下Console,调用FreeConsole即可. 另一个问题就是,如果你关闭了控制台窗口,那么整个程序也就退出了,偶还木有找到解决的方法,如果哪位知道解决方法,还望不吝赐教! http://gamebabyrocksun.blog.163.com/blog/static/5715346320111027333683