【问题标题】:Why can't I redirect output from WriteConsole?为什么我不能从 WriteConsole 重定向输出?
【发布时间】:2018-01-30 00:58:31
【问题描述】:

在以下程序中,我使用两个不同的功能打印到控制台

#include <windows.h>

int main() {
    HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD byteswritten;
    WriteConsole(h, "WriteConsole", 12, &byteswritten, NULL);
    WriteFile(h, "WriteFile", 9, &byteswritten, NULL);
}

如果当我执行这个程序并使用a &gt; out.txta 1&gt; out.txt 重定向它的输出时,控制台不会打印任何内容(如预期的那样),但out.txt 的内容只是

WriteFile

两者之间有什么不同,允许将调用 WriteFile 重定向到文件并调用 WriteConsole 转到...无处

在 Windows 10 上使用 gcc 和 msvc 测试

【问题讨论】:

  • WriteConsole 与重定向到文件的标准句柄一起使用时会失败
  • @RbMm 来自哪里?
  • 添加一些错误检查,看看您的 API 调用是否成功。否则你无能为力。几乎这里提出的每个 winapi 问题都会出错。不要忽视错误检查。阅读文档也没有什么坏处。忽略这是另一个常见的愚蠢行为。
  • @RbMm 那我想这就是我的答案,继续发帖让我接受

标签: c winapi console windows-console


【解决方案1】:

WriteConsole 仅适用于控制台屏幕句柄,不适用于文件或管道。

如果您只编写 ASCII 内容,您可以使用 WriteFile 来处理所有内容。

如果您需要编写 Unicode 字符,您可以使用 GetConsoleMode 来检测句柄类型,对于不是控制台句柄的所有内容,它都会失败。

在进行这样的原始输出时,如果句柄被重定向到文件,您还必须处理 BOM

This blog post 是在 Windows 控制台中处理 Unicode 的良好起点...

【讨论】:

  • 具体来说,在 Windows 8+ WriteConsole 中请求仅受 ConDrv 控制台设备支持的 I/O 控制 (IOCTL)。其他一些设备,例如文件系统,会将此 IOCTL 作为无效参数失败,然后控制台 API 会将其报告为无效句柄。在 Windows 7 和更早版本中,控制台 API 使用 LPC 而不是 ConDrv 设备,因此控制台缓冲区句柄被标记为路由到 LPC 连接,因此可以在用户模式下立即检测到(由 API,not 客户端代码)通过检查低两位是否已设置(例如 3、7、11 等)。
  • 那篇旧博文提供了一些有用的信息,但是当它将控制台与 CMD shell 混淆时,它就无济于事了。
【解决方案2】:

如果对方使用WriteConsole,下面的代码可以用来重定向控制台输出。该代码通过隐藏的控制台屏幕缓冲区读取输出。我编写了这段代码来拦截一些directshow驱动程序写入控制台的调试输出。 Directshow 驱动程序有做驱动程序不应该做的事情的习惯,例如写入不需要的日志文件、写入控制台和崩溃。

// info to redirected console output
typedef struct tagRedConInfo
{
  // hidden console
  HANDLE     hCon;

  // old console handles
  HANDLE     hOldConOut;
  HANDLE     hOldConErr;

  // buffer to read screen content
  CHAR_INFO *BufData;
  INT        BufSize;

  //
} TRedConInfo;




//------------------------------------------------------------------------------
// GLOBALS
//------------------------------------------------------------------------------

// initial handles
HANDLE gv_hOldConOut;
HANDLE gv_hOldConErr;



//------------------------------------------------------------------------------
// PROTOTYPES
//------------------------------------------------------------------------------

/* init redirecting the console output */
BOOL Shell_InitRedirectConsole(BOOL,TRedConInfo*);

/* done redirecting the console output */
BOOL Shell_DoneRedirectConsole(TRedConInfo*);

/* read string from hidden console, then clear */
BOOL Shell_ReadRedirectConsole(TRedConInfo*,TCHAR*,INT);

/* clear buffer of hidden console */
BOOL Shell_ClearRedirectConsole(TRedConInfo*);





//------------------------------------------------------------------------------
// IMPLEMENTATIONS
//------------------------------------------------------------------------------


/***************************************/
/* init redirecting the console output */
/***************************************/

BOOL Shell_InitRedirectConsole(BOOL in_SetStdHandles, TRedConInfo *out_RcInfo)
{
    /* locals */
    HANDLE              lv_hCon;
    SECURITY_ATTRIBUTES lv_SecAttr;


  // preclear structure
  memset(out_RcInfo, 0, sizeof(TRedConInfo));

  // prepare inheritable handle just in case an api spans an external process
  memset(&lv_SecAttr, 0, sizeof(SECURITY_ATTRIBUTES));
  lv_SecAttr.nLength        = sizeof(SECURITY_ATTRIBUTES);
  lv_SecAttr.bInheritHandle = TRUE;

  // create hidden console buffer
  lv_hCon = CreateConsoleScreenBuffer(
     GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
    &lv_SecAttr, CONSOLE_TEXTMODE_BUFFER, 0);

  // failed to create console buffer?
  if (lv_hCon == INVALID_HANDLE_VALUE)
    return FALSE;

  // store
  out_RcInfo->hCon = lv_hCon;

  // set as standard handles for own process?
  if (in_SetStdHandles)
  {
    // mutex the globals
    WaitForGlobalVarMutex();

    // remember the old handles
    out_RcInfo->hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
    out_RcInfo->hOldConErr = GetStdHandle(STD_ERROR_HANDLE);

    // set hidden console as std output
    SetStdHandle(STD_OUTPUT_HANDLE, lv_hCon);
    SetStdHandle(STD_ERROR_HANDLE,  lv_hCon);

    // is this the first instance?
    if (!gv_hOldConOut)
    {
      // inform our own console output code about the old handles so our own
      // console will be writing to the real console, only console output from
      // other parties will write to the hidden console
      gv_hOldConOut = out_RcInfo->hOldConOut;
      gv_hOldConErr = out_RcInfo->hOldConErr;
    }

    // release mutex
    ReleaseGlobalVarMutex();
  }

  // done
  return TRUE;
}




/***************************************/
/* done redirecting the console output */
/***************************************/

BOOL Shell_DoneRedirectConsole(TRedConInfo *in_RcInfo)
{
  // validate
  if (!in_RcInfo->hCon)
    return FALSE;

  // restore original handles?
  if (in_RcInfo->hOldConOut)
  {
    // mutex the globals
    WaitForGlobalVarMutex();

    // restore original handles
    SetStdHandle(STD_OUTPUT_HANDLE, in_RcInfo->hOldConOut);
    SetStdHandle(STD_ERROR_HANDLE,  in_RcInfo->hOldConErr);

    // was this the first instance?
    if (in_RcInfo->hOldConOut == gv_hOldConOut)
    {
      // clear
      gv_hOldConOut = NULL;
      gv_hOldConErr = NULL;
    }

    // release mutex
    ReleaseGlobalVarMutex();
  }

  // close the console handle
  CloseHandle(in_RcInfo->hCon);

  // free read buffer
  if (in_RcInfo->BufData)
    MemFree(in_RcInfo->BufData);

  // clear structure
  memset(in_RcInfo, 0, sizeof(TRedConInfo));

  // done
  return TRUE;
}




/***********************************************/
/* read string from hidden console, then clear */
/***********************************************/

BOOL Shell_ReadRedirectConsole(TRedConInfo *in_RcInfo, TCHAR *out_Str, INT in_MaxLen)
{
    /* locals */
    TCHAR                      lv_C;
    INT                        lv_X;
    INT                        lv_Y;
    INT                        lv_W;
    INT                        lv_H;
    INT                        lv_N;
    INT                        lv_Len;
    INT                        lv_Size;
    INT                        lv_PrvLen;
    COORD                      lv_DstSze;
    COORD                      lv_DstOfs;
    DWORD                      lv_Written;
    SMALL_RECT                 lv_SrcRect;
    CHAR_INFO                 *lv_BufData;
    CONSOLE_SCREEN_BUFFER_INFO lv_Info;


  // preclear output
  out_Str[0] = 0;

  // validate
  if (!in_RcInfo->hCon)
    return FALSE;

  // reserve character for eos
  --in_MaxLen;

  // get current buffer info
  if (!GetConsoleScreenBufferInfo(in_RcInfo->hCon, &lv_Info))
    return FALSE;

  // check whether there is something at all
  if (!lv_Info.dwSize.X || !lv_Info.dwSize.Y)
    return FALSE;

  // limit the buffer passed onto read call otherwise it
  // will fail with out-of-resources error
  lv_DstSze.X = (INT16)(lv_Info.dwSize.X);
  lv_DstSze.Y = (INT16)(lv_Info.dwSize.Y < 8 ? lv_Info.dwSize.Y : 8);

  // size of buffer needed
  lv_Size = lv_DstSze.X * lv_DstSze.Y * sizeof(CHAR_INFO);

  // is previous buffer too small?
  if (!in_RcInfo->BufData || in_RcInfo->BufSize < lv_Size)
  {
    // free old buffer
    if (in_RcInfo->BufData)
      MemFree(in_RcInfo->BufData);

    // allocate read buffer
    if ((in_RcInfo->BufData = (CHAR_INFO*)MemAlloc(lv_Size)) == NULL)
      return FALSE;

    // store new size
    in_RcInfo->BufSize = lv_Size;
  }

  // always write to (0,0) in buffer
  lv_DstOfs.X = 0;
  lv_DstOfs.Y = 0;

  // init src rectangle
  lv_SrcRect.Left   = 0;
  lv_SrcRect.Top    = 0;
  lv_SrcRect.Right  = lv_DstSze.X;
  lv_SrcRect.Bottom = lv_DstSze.Y;

  // buffer to local
  lv_BufData = in_RcInfo->BufData;

  // start at first string position in output
  lv_Len = 0;

  // loop until no more rows to read
  do
  {
    // read buffer load
    if (!ReadConsoleOutput(in_RcInfo->hCon, lv_BufData, lv_DstSze, lv_DstOfs, &lv_SrcRect))
      return FALSE;

    // w/h of actually read content
    lv_W = lv_SrcRect.Right  - lv_SrcRect.Left + 1;
    lv_H = lv_SrcRect.Bottom - lv_SrcRect.Top  + 1;

    // remember previous position
    lv_PrvLen = lv_Len;

    // loop through rows of buffer
    for (lv_Y = 0; lv_Y < lv_H; ++lv_Y)
    {
      // reset output position of current row
      lv_N = 0;

      // loop through columns
      for (lv_X = 0; lv_X < lv_W; ++lv_X)
      {
        // is output full?
        if (lv_Len + lv_N > in_MaxLen)
          break;

        // get character from screen buffer, ignore attributes
        lv_C = lv_BufData[lv_Y * lv_DstSze.X + lv_X].Char.UnicodeChar;

        // append character
        out_Str[lv_Len + lv_N++] = lv_C;
      }

      // remove spaces at the end of the line
      while (lv_N > 0 && out_Str[lv_Len+lv_N-1] == ' ')
        --lv_N;

      // if row was not blank
      if (lv_N > 0)
      {
        // update output position
        lv_Len += lv_N;

        // is output not full?
        if (lv_Len + 2 < in_MaxLen)
        {
          // append cr/lf
          out_Str[lv_Len++] = '\r';
          out_Str[lv_Len++] = '\n';
        }
      }
    }

    // update screen position
    lv_SrcRect.Top    = (INT16)(lv_SrcRect.Top    + lv_H);
    lv_SrcRect.Bottom = (INT16)(lv_SrcRect.Bottom + lv_H);

    // until nothing is added or no more screen rows
  } while (lv_PrvLen != lv_Len && lv_SrcRect.Bottom < lv_Info.dwSize.Y);

  // remove last cr/lf
  if (lv_Len > 2)
    lv_Len -= 2;

  // append eos
  out_Str[lv_Len] = 0;

  // total screen buffer size in characters
  lv_Size = lv_Info.dwSize.X * lv_Info.dwSize.Y;

  // clear the buffer with spaces
  FillConsoleOutputCharacter(in_RcInfo->hCon, ' ', lv_Size, lv_DstOfs, &lv_Written);

  // reset cursor position to (0,0)
  SetConsoleCursorPosition(in_RcInfo->hCon, lv_DstOfs);

  // done
  return TRUE;
}




/**********************************/
/* clear buffer of hidden console */
/**********************************/

BOOL Shell_ClearRedirectConsole(TRedConInfo *in_RcInfo)
{
    /* locals */
    INT                        lv_Size;
    COORD                      lv_ClrOfs;
    DWORD                      lv_Written;
    CONSOLE_SCREEN_BUFFER_INFO lv_Info;


  // validate
  if (!in_RcInfo->hCon)
    return FALSE;

  // get current buffer info
  if (!GetConsoleScreenBufferInfo(in_RcInfo->hCon, &lv_Info))
    return FALSE;

  // clear from (0,0) onward
  lv_ClrOfs.X = 0;
  lv_ClrOfs.Y = 0;

  // total screen buffer size in characters
  lv_Size = lv_Info.dwSize.X * lv_Info.dwSize.Y;

  // clear the buffer with spaces
  FillConsoleOutputCharacter(in_RcInfo->hCon, ' ', lv_Size, lv_ClrOfs, &lv_Written);

  // reset cursor position to (0,0)
  SetConsoleCursorPosition(in_RcInfo->hCon, lv_ClrOfs);

  // done
  return TRUE;
}

【讨论】:

    【解决方案3】:

    2021 年编辑:

    Windows 10 现在有了ConPTY API(又名伪控制台),它基本上允许任何程序充当另一个程序的控制台,从而能够捕获直接写入控制台的输出。

    这使得我原来的答案对于支持 ConPTY 的 Windows 版本已过时。


    原答案:

    来自reference

    WriteConsole 如果与标准句柄一起使用,则会失败 重定向到一个文件。如果应用程序处理多语言输出 可以重定向的,判断输出句柄是否为 控制台句柄(一种方法是调用GetConsoleMode 函数和 检查是否成功)。如果句柄是控制台句柄,请调用 WriteConsole。如果句柄不是控制台句柄,则输出为 重定向,您应该调用 WriteFile 来执行 I/O。

    这仅适用于您控制要重定向的应用程序的源代码。我最近不得不重定向来自闭源应用程序的输出,该应用程序无条件调用WriteConsole(),因此无法正常重定向。

    读取控制台屏幕缓冲区(如this answer 所建议的那样)被证明是不可靠的,因此我使用Microsoft Detours library 在目标进程中挂钩WriteConsole() API 并在必要时调用WriteFile()。否则调用原来的WriteConsole()函数。

    我根据Using Detours的例子创建了一个hook DLL:

    #include <windows.h>
    #include <detours.h>
    
    // Target pointer for the uninstrumented WriteConsoleW API.
    //
    auto WriteConsoleW_orig = &WriteConsoleW;
    
    // Detour function that replaces the WriteConsoleW API.
    //
    BOOL WINAPI WriteConsoleW_hooked(
      _In_             HANDLE  hConsoleOutput,
      _In_       const VOID    *lpBuffer,
      _In_             DWORD   nNumberOfCharsToWrite,
      _Out_            LPDWORD lpNumberOfCharsWritten,
      _Reserved_       LPVOID  lpReserved 
    )
    {
        // Check if this actually is a console screen buffer handle.
        DWORD mode;
        if( GetConsoleMode( hConsoleOutput, &mode ) )
        {
            // Forward to the original WriteConsoleW() function.
            return WriteConsoleW_orig( hConsoleOutput, lpBuffer, nNumberOfCharsToWrite, lpNumberOfCharsWritten, lpReserved );
        }
        else
        {
            // This is a redirected handle (e. g. a file or a pipe). We multiply with sizeof(WCHAR), because WriteFile()
            // expects the number of bytes, but WriteConsoleW() gets passed the number of characters.
            BOOL result = WriteFile( hConsoleOutput, lpBuffer, nNumberOfCharsToWrite * sizeof(WCHAR), lpNumberOfCharsWritten, nullptr );
    
            // WriteFile() returns number of bytes written, but WriteConsoleW() has to return the number of characters written.
            if( lpNumberOfCharsWritten )
                *lpNumberOfCharsWritten /= sizeof(WCHAR);
            
            return result;
        }
    }
    
    // DllMain function attaches and detaches the WriteConsoleW_hooked detour to the
    // WriteConsoleW target function.  The WriteConsoleW target function is referred to
    // through the WriteConsoleW_orig target pointer.
    //
    BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
    {
        if (DetourIsHelperProcess()) {
            return TRUE;
        }
    
        if (dwReason == DLL_PROCESS_ATTACH) {
            DetourRestoreAfterWith();
    
            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)WriteConsoleW_orig, WriteConsoleW_hooked);
            DetourTransactionCommit();
        }
        else if (dwReason == DLL_PROCESS_DETACH) {
            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourDetach(&(PVOID&)WriteConsoleW_orig, WriteConsoleW_hooked);
            DetourTransactionCommit();
        }
        return TRUE;
    }
    

    注意:WriteFile() 分支中,我不写 BOM(字节顺序标记),因为它并不总是需要(例如,当重定向到管道而不是文件时,或者当附加到现有文件)。使用 DLL 将进程输出重定向到文件的应用程序只需在启动重定向进程之前自行编写 UTF-16 LE BOM。

    目标进程是使用DetourCreateProcessWithDllExW() 创建的,将我们的钩子DLL 的名称指定为lpDllName 参数的参数。其他参数与通过CreateProcessW() API 创建重定向进程的方式相同。我不会详细介绍,因为这些都有据可查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-13
      • 1970-01-01
      • 2021-07-01
      • 1970-01-01
      • 2010-12-09
      • 1970-01-01
      相关资源
      最近更新 更多