2014-01-27 6 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(); 
} 

cout 대신 GetStdHandle(STD_OUTPUT_HANDLE)을 호출하여 WriteFile을 사용하여 위 코드의 정확성을 확인할 수 있습니다. 내가 이상적으로 필요로하는 것은 에 해당하는 것인데 HANDLE에서 작동합니다. 그 방법으로 을 GetStdHandle으로 반환 한 다음이 MyReopenHandle이 내 기본 설정 파일 인 HANDLE을 자신이 좋아하는 파일로 설정할 수 있습니다. 나는 그것이 printfcout 모두 어딘가에 깊은 아래에 저장된 HANDLE을 가지고 있다고 가정 할 때 그것이 작동 할 것이라고 믿습니다. 나는 "awake it"을 표준 출력 핸들을 복사하여 핸들을 닫은 다음 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

와우, 잠시 후 HANDLEFILE으로 수동 설정하는 방법을 검색 한 결과, 다음을 사용하여이 작업을 수행하는 것이 매우 간단하다는 것을 발견했습니다. 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. 가 FILE + 스택에서를 가져옵니다.
  2. 이 파일 *을 사용하여 stdout을 다시 스왑합니다. (완전한 FILE 구조체를 교환하십시오)
  3. 받은 파일 *을 닫으십시오.

파일 핸들을 사용하여이 작업을 수행하려면 OS 파일 핸들을 FILE *에 연결해야합니다. _open_osfhandle() 및 _fdopen()을 사용하여이 작업을 수행 할 수 있습니다.

파일 작업은 버퍼를 사용하기 때문에 스왑 전에 버퍼를 플러시해야합니다. 이전 출력의 "남은 부분"이 없는지 확인하십시오.

내 해답이 양식이 작동해야합니다.

+0

'GetStdHandle'에서 반환 된'HANDLE'에 대한'WriteFile'에 의존하는 것은 부작용이 있습니다. 여전히 원래 장치에 기록됩니다. 내가 해낼 수 있었던 해법이 좀 덜 해킹 된 것을보고 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; 
} 
관련 문제