Windows进程内标准输出重定向及其在程序调试上的应用

本文介绍了一种在Windows环境下将标准输出(如cout/printf)重定向至DebugView的方法,利用匿名管道和线程实现,使得调试信息既能在控制台显示也能被DebugView捕获。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 一、如何实现  
  2.   
  3. 打印调试信息的方法有很多,最常用的是使用标准输出设备(如printf、cout等),也可以用OutPutDebugString输出、用 DebugView工具查看,还可以写入日志文件。如果程序运行需要记录日志(log),往往需要打开个文件,或许是写入系统事件、用系统的事件查看器查看。  
  4.   
  5. 应用程序打印调试信息、日志的方法往往是确定的,但如果是要编写一个模块或者说组件,那样的输出信息应该写入哪里呢?或者说程序本身对此也没有明确需求的话,那该怎么办呢?  
  6.   
  7. 可喜的是一个进程的标准输出是可以重定向的,所以我建议把调试信息直接打到标准输出上,这样代码中可以统一使用cout或者printf,然后根据需要将标准输出重定向。  
  8.   
  9. Linux 中重定向标准输出就容易了,因为有强大的dup2函数。而对于Windows的重定向,貌似往往是用于子进程的,在用CreateProcess创建子进程时设置子进程的标准输出句柄。而我们想要的是重定向自己这个进程的标准输出,那个用不上。我在MSDN中也没找到类似dup2的Win32 API函数,只有一个DuplicateHandle函数,这相当于linux中的dup,也用不上。  
  10.   
  11. 这里顺便提下,SetStdHandle是不能实现重定向的。这个函数的功能是将某句柄指向标准设备,并不能将标准设备句柄重定向到另外的句柄。  
  12.   
  13. 于是我就想到,Windows不是支持一部分POSIX标准的吗。于是我找到了一个CRT的函数,叫_dup2,看起来是不是特眼熟,对了,这就是Windows中dup2的兼容版本。  
  14.   
  15. 值得注意的是,_dup2以及与此相关的一系列CRT中的IO函数(如_read,_write)均以下划线开头,其余与linux大致相同,其参数中所谓的文件描述符与Win32中的句柄不一样,文件描述符实际上是句柄数组的索引,也就是说文件描述符不能与句柄混用。比如0,1,2分别是标准输入、标准输出、标准错误的文件描述符,但句柄值不是这样确定的。文件描述符不是Win32的概念,是POSIX中的概念。  
  16.   
  17. _dup2用法与dup2大致相同,不多解释,不了解的可以查阅dup2相关资料。下面讲点应用。  
  18.   
  19. 二、如何应用于调试  
  20.   
  21. 写一个模块时,我们可以直接用cout/printf来作调试。但是如果这个模块用于图形界面或许是系统服务呢?这时标准输出看不到了,我们可以用 OutPutDebugString函数和DebugView这样的调试工具。这样就带来一种选择,而选择往往是增加软件复杂度的因素。所以我的想法是代码中只用cout/printf,如果需要将其重定向到调试工具中去。  
  22.   
  23. 如何实现呢,用匿名管道和线程。  
  24.   
  25. 用一个pipe,标准输出重定向到其write端,然后创建一个线程,线程要做的就是从pipe的read端读出数据后用OutPutDebugString输出。  
  26.   
  27. 下面是实现代码。  
  28.   
  29. 头文件是这样子的,构造时重定向,析构时解除:  
  30.   
  31.   
  32.  namespace common {  
  33.     // 将标准输出重定向到DebugView,保持对象存在即有效  
  34.   
  35.     class StdoutToDebugString {  
  36.     public:  
  37.         StdoutToDebugString();  
  38.           
  39.         ~StdoutToDebugString();  
  40.   
  41.     private:  
  42.         int fds_[2];  
  43.         int orign_stdout_;  
  44.         uintptr_t thread_handle_;  
  45.     };  
  46. }  
  47.   
  48. 实现文件:  
  49.   
  50.   
  51. #include <io.h>  
  52. #include <fcntl.h>  
  53. #include <stdio.h>  
  54. #include <process.h>  
  55. #include <Windows.h>  
  56. #include "StdoutRedirect.h"  
  57.   
  58. using namespace common;  
  59.   
  60. const int kBufferSize = 4096;  
  61.   
  62. unsigned __stdcall RedirectThreadProc(void* param) {  
  63.     int pipe_read = (int)param;  
  64.     char buf[kBufferSize];  
  65.     int bytes_read;  
  66.     do {  
  67.         bytes_read = ::_read(pipe_read, buf, kBufferSize);  
  68.         buf[bytes_read] = 0;  
  69.         ::OutputDebugString(buf);  
  70.     } while (bytes_read);  
  71.     return 0;  
  72. };  
  73.   
  74. StdoutToDebugString::StdoutToDebugString() {  
  75.     ::_pipe(fds_, kBufferSize, _O_TEXT);  
  76.     orign_stdout_ = ::_dup(_fileno(stdout));  
  77.     ::_dup2(fds_[1], _fileno(stdout));  
  78.     thread_handle_ = ::_beginthreadex(NULL, 0, RedirectThreadProc, (void*)fds_[0], 0, NULL);  
  79.     ::CloseHandle((HANDLE)thread_handle_);  
  80. }  
  81.   
  82. StdoutToDebugString::~StdoutToDebugString() {  
  83.     ::_dup2(orign_stdout_, _fileno(stdout));  
  84. }  
  85.   
  86. 测试代码:  
  87.   
  88.   
  89. #include <iostream>  
  90. #include <Windows.h>  
  91. #include "../Common/StdoutRedirect.h"  
  92. using namespace std;  
  93. using namespace common;  
  94.   
  95. void main() {  
  96.     StdoutToDebugStringredirect;  
  97.     cout<< "hello" << endl;  
  98.     ::system("pause");  
  99. }  
  100.    

另外一篇很不错的dup2介绍

http://www.cnblogs.com/sdphome/archive/2011/04/30/2033381.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值