2014-01-27 72 views
5

标题说明了一切。当我运行下面的代码:SetStdHandle对cout/printf没有影响

HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); 
HANDLE hFile = CreateFile(TEXT("Foo.txt"), GENERIC_WRITE, FILE_READ_ACCESS | FILE_WRITE_ACCESS, 
    NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 

SetStdHandle(STD_OUTPUT_HANDLE, hFile); 
std::cout << "Hello, "; 
printf("world!\n"); 
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello, world!\n", 13, NULL, NULL); 

SetStdHandle(STD_OUTPUT_HANDLE, hOut); 
CloseHandle(hFile); 

结果是Hello, world!被写入到控制台的调用coutprintf的结果,并Hello, world!也被写入文件Foo.txt为调用的结果到WriteFile。我的假设是,当一切都在开始初始化时,由GetStdHandle返回的HANDLE被缓存并重新用于coutprintf。这是完全合理的,正是我想要的,因为我假设GetStdHandle需要调用操作系统(可能会很长!)。麻烦的是我想覆盖这种行为,如果可能的话,将cout和printf与应用程序的标准句柄“同步”。

在提出任何替代方案之前,让我准确描述它是我正在尝试做的事情(是的,我知道有可能为此使用freopen)。我需要做的是在修改之前将当前标准输出句柄“保存”在类似堆栈的数据结构上,以便我能够恢复先前的输出句柄。任何缺少这种情况的情况都是不可接受的(即我无法恢复到CONOUT$等)。这需要有递归的能力。即以下应该工作,你会期待它:

std::cout << "A1" << std::endl; 

StartStdOutRedirection(TEXT("Foo.txt")); 
std::cout << "B1" << std::endl; 

StartStdOutRedirection(TEXT("Bar.txt")); 
std::cout << "C1" << std::endl; 
EndStdOutRedirection(); 

std::cout << "B2" << std::endl; 
EndStdOutRedirection(); 

std::cout << "A2" << std::endl; 

这将是过于容易,如果有办法“重新同步” stdout如下面的代码应该做的伎俩:

std::vector<HANDLE> vStdOutHandles; 
void StartStdOutRedirection(_In_ LPCTSTR lpFile) 
{ 
    vStdOutHandles.push_back(GetStdHandle(STD_OUTPUT_HANDLE)); 
    SetStdHandle(STD_OUTPUT_HANDLE, CreateFile(lpFile, GENERIC_WRITE, 
     FILE_WRITE_ACCESS | FILE_READ_ACCESS, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); 
} 

void EndStdOutRedirection(void) 
{ 
    CloseHandle(GetStdHandle(STD_INPUT_HANDLE)); 
    SetStdHandle(STD_OUTPUT_HANDLE, vStdOutHandles.back()); 
    vStdOutHandles.pop_back(); 
} 

以上代码的正确性可以通过WriteFile代替cout调用GetStdHandle(STD_OUTPUT_HANDLE)来验证。我理想需要的是相当于freopen,适用于HANDLE s。通过这种方式,我可以在GetStdHandle返回的HANDLE上使用DuplicateHandle,然后使用MyReopenHandle函数将该HANDLE的底层文件设置为我喜欢的文件。我相信这会工作,因为我认为printfcout有一个HANDLE保存在某处深处。我试图通过复制标准输出句柄来“伪造”,关闭该句柄,然后致电CreateFile,希望它能给予我相同的HANDLE价值,但这种情况在零星情况下最好。这里是我的代码,如果你有兴趣:

std::vector<HANDLE> vStdOutHandles; 
bool StartStdOutRedirection(_In_ LPCTSTR lpFile) 
{ 
    bool fResult = false; 
    HANDLE hProc = GetCurrentProcess(); 
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); 

    if (hOut != INVALID_HANDLE_VALUE) 
    { 
     HANDLE hDup; 
     if (DuplicateHandle(hProc, hOut, hProc, &hDup, 0, FALSE, DUPLICATE_SAME_ACCESS)) 
     { 
      // Need to close the current handle before we open the new one 
      CloseHandle(hOut); 
      HANDLE hFile = CreateFile(lpFile, GENERIC_WRITE, FILE_WRITE_ACCESS | FILE_READ_ACCESS, 
       NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 

      if (hFile != INVALID_HANDLE_VALUE) 
      { 
       // Should be same HANDLE; else we're screwed... 
       assert(hFile == hOut); 
       SetStdHandle(STD_OUTPUT_HANDLE, hFile); 

       vStdOutHandles.push_back(hDup); 
       fResult = true; 
      } 
      else 
      { 
       // Otherwise, reopen the previous output HANDLE on failure 
       DuplicateHandle(hProc, hDup, hProc, &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS); 

       assert(hFile == hOut); 
       CloseHandle(hDup); 
      } 
     } 
    } 

    return fResult; 
} 

bool EndStdOutRedirection(void) 
{ 
    bool fResult = false; 
    HANDLE hProc = GetCurrentProcess(); 
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); 

    if (hOut != INVALID_HANDLE_VALUE && vStdOutHandles.size() != 0) 
    { 
     HANDLE hDup; 
     HANDLE hNext = vStdOutHandles.back(); 

     // Close current handle and re-open previous one 
     CloseHandle(hOut); 
     if (DuplicateHandle(hProc, hNext, hProc, &hDup, 0, FALSE, DUPLICATE_SAME_ACCESS)) 
     { 
      // Again, we're screwed if these are not the same 
      assert(hOut == hDup); 
      SetStdHandle(STD_OUTPUT_HANDLE, hDup); 

      vStdOutHandles.pop_back(); 
      fResult = true; 
     } 
    } 

    return fResult; 
} 

上述断言失败半个月左右的时间(我是不是真的希望或者在计算工作......我只是感兴趣)。就我所知,这个问题就是这样。如果有人有任何建议,请让我知道:)

回答

2

哇,经过一段时间的搜索手动设置的HANDLE的方式,我终于发现有一个相当直接的方法来使用C运行时库:

std::vector<int> vfdStdOut; 
void StartStdOutRedirection(_In_ LPCSTR lpFile) 
{ 
    // Duplicate stdout and give it a new file descriptor 
    int fdDup = _dup(_fileno(stdout)); 
    vfdStdOut.push_back(fdDup); 

    // Re-open stdout to the new file 
    freopen(lpFile, "w", stdout); 
} 

bool EndStdOutRedirection(void) 
{ 
    if (vfdStdOut.size() != 0) 
    { 
     // Get last saved file descriptor and restore it 
     int fdNext = vfdStdOut.back(); 
     _dup2(fdNext, _fileno(stdout)); 

     // Need to close the file associated with the saved file descriptor 
     _close(fdNext); 

     vfdStdOut.pop_back(); 
     return true; 
    } 

    return false; 
} 

这也将照顾为您打电话SetStdHandle

0

这只适用于MS-CRT。

FILE *只是线程本地存储中的FILE结构数组中的条目。

所以我的想法是:

  1. 公开赛的fopen新的文件。我们现在有一个新的FILE *给内部结构数组。
  2. 将此新指针保存到您的堆栈中。
  3. 现在只需将两个结构stdout与新的FILE结构交换。

代码应该是:

FILE swap = *stdout; 
*stdout = *pFile; 
*pFile = swap; 

此操作后,标准输出句柄现在是新的文件。旧的标准输出句柄在FILE *中缓慢保存在堆栈中。

为刚刚回归:

  1. 获取文件+从堆栈。
  2. 用此FILE *再次交换stdout。 (交换完整的FILE结构)
  3. 关闭您收到的FILE *。

如果你想用文件句柄来做到这一点,你需要将OS文件句柄与FILE *关联起来。这是通过_open_osfhandle()和_fdopen()完成的。

因为文件操作使用缓冲区,所以需要在交换之前刷新缓冲区。确保旧输出中没有“剩菜”。

形成我的观点,这个黑客应该工作。

+0

这具有负面影响,即任何依赖'WriteFile'到'GetStdHandle'返回的'HANDLE'的任何东西仍然会写入原始设备。看到解决方案,我能够做到这一点不hacky,并与OS – Duncan

+0

同意!你的解决方案好得多!这是没有黑客,像我一样... – xMRi

+0

我不相信,因为我相信用标准输出(或任何标准设备)调用freopen是未定义的行为?至少在我的研究中出现了这种情况;至少在使用'cout'方面没有任何保证。恰巧,Windows上的实现(我相信大多数其他主要平台也是如此)会实现你所期望的。 – Duncan

0

下面是我放在一起的解决方案(当然远非完美)。它为每个写入STDOUT的字符调用一个自定义函数。在我的例子中,它将流转发到OutputDebugString调用。

#include <windows.h> 
#include <io.h> 
#include <functional> 
#include <iostream> 

#define STDOUT_FILENO 1 
#define STDERR_FILENO 2 

enum StdHandleToRedirect { 
    STDOUT, STDERR 
}; 

class StdRedirect { 
public: 
    /// Assumes the specified handle is still assigned to the default FILENO (STDOUT_FILENO/STDERR_FILENO) 
    /// TODO allow redirection in every case 
    /// callback will run in a new thread and will be notified of any character input to 
    /// the specified std handle 
    StdRedirect(StdHandleToRedirect h, std::function<void(char)> callback) : callback(callback) { 
     CreatePipe(&readablePipeEnd, &writablePipeEnd, 0, 0); 
     SetStdHandle(h == STDOUT ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE, writablePipeEnd); 

     // Redirect (TODO: ERROR CHECKING) 
     int writablePipeEndFileStream = _open_osfhandle((long)writablePipeEnd, 0); 
     FILE* writablePipeEndFile = NULL; 
     writablePipeEndFile = _fdopen(writablePipeEndFileStream, "wt"); 
     _dup2(_fileno(writablePipeEndFile), h == STDOUT ? STDOUT_FILENO : STDERR_FILENO); 

     CreateThread(0, 0, (LPTHREAD_START_ROUTINE)stdreader, this, 0, 0); 
    } 

    // TODO implement destructor, cleanup, reset 

private: 
    // DWORD (WINAPI *PTHREAD_START_ROUTINE)(LPVOID lpThreadParameter) 
    static void WINAPI stdreader(StdRedirect* redirector) { 
     while (1) { 
      char c; 
      DWORD read; 
      ::fflush(NULL); // force current stdout to become readable 
      // TODO add error handling 
      ReadFile(redirector->readablePipeEnd, (void*)&c, 1, &read, 0); // this blocks until input is available 
      if (read == 1) 
       redirector->callback(c); 
     } 
    } 

    HANDLE readablePipeEnd, writablePipeEnd; 
    const std::function<void(char)> callback; 
}; 

int main() { 
    std::function<void(char)> toOutputDebugString = [](char x) { 
     char str[2] = {x, 0}; 
     OutputDebugStringA(str); 
    }; 

    StdRedirect so(STDOUT, toOutputDebugString); 
    std::cout << "test stdout\n"; 
    while (1); // busy loop to give the thread time to read stdout. 
    // You might want to look at "Output: Show output from: Debug" now. 
    return 0; 
}