delphi程序中如何截获第三方库打印到std err的信息

第三方库很多Log输出是fprintf到std err的信息,调试的时候为了显示这些信息,我们需要给delphi gui程序绑定一个控制台窗口,下面的函数

  1. procedure AllocateDebugConsole;
  2. var
  3. StdOutHandle, StdErrHandle, StdInHandle: THandle;
  4. Success: Boolean;
  5. begin
  6. // 尝试分配一个控制台窗口
  7. if AllocConsole then
  8. begin
  9. // AllocConsole 成功后,进程的标准句柄已指向新控制台
  10. // 现在需要让 Delphi 的标准 TextFile 变量使用这些新句柄
  11.  
  12. try
  13. // 重新打开 ErrOutput,使其关联到新的 STD_ERROR_HANDLE
  14. Rewrite(ErrOutput); // <--- 使用 Rewrite
  15. WriteLn(ErrOutput, 'ErrOutput redirected.');
  16.  
  17. // 重新打开 Output,使其关联到新的 STD_OUTPUT_HANDLE
  18. Rewrite(Output); // <--- 使用 Rewrite
  19. WriteLn(Output, 'Output redirected.');
  20.  
  21. // 重新打开 Input,使其关联到新的 STD_INPUT_HANDLE
  22. Reset(Input); // <--- 使用 Reset
  23. // WriteLn(Output, 'Input redirected.'); // 不容易测试Input,先注释掉
  24.  
  25. // 可选:设置控制台标题
  26. SetConsoleTitle('Debug Output Console');
  27. Flush(ErrOutput);
  28. Flush(Output);
  29. except
  30. on E: Exception do
  31. begin
  32. // 如果 Rewrite/Reset 失败 (虽然不太可能在成功 AllocConsole 后立即失败)
  33. OutputDebugString(PChar('Failed to redirect standard handles: ' + E.Message));
  34. end;
  35. end;
  36. end
  37. else
  38. begin
  39. // 分配控制台失败
  40. // OutputDebugString(PChar('Failed to allocate console: ' + SysErrorMessage(GetLastError)));
  41. end;
  42. end;
  43.  
  44. 用法
  45.  
  46. begin
  47. AllocateDebugConsole; <----add at here
  48. Application.Initialize;
  49. Application.CreateForm(TFormMain, FormMain);
  50. Application.Run;
  51. end.

如果要将日志重定向到文件,可以使用下面的处理

  1. procedure RedirectProcessOutputToFile;
  2. var
  3. SecurityAttr: TSecurityAttributes;
  4. StdOutFileHandle, StdErrFileHandle, StdInFileHandle: THandle;
  5. LogFilePathStdOut: string;
  6. LogFilePathStdErr: string;
  7. DelphiErrorLogFile: TextFile; // For Delphi's ErrOutput redirection
  8. DelphiOutputLogFile: TextFile; // For Delphi's Output redirection
  9. Success: Boolean;
  10. begin
  11. // --- 1. 准备日志文件路径 ---
  12. LogFilePathStdOut := TPath.Combine(ExtractFilePath(ParamStr(0)), 'process_stdout_log.txt');
  13. LogFilePathStdErr := TPath.Combine(ExtractFilePath(ParamStr(0)), 'process_stderr_log.txt');
  14.  
  15. // --- 2. (可选) 为 Delphi 的 ErrOutput 和 Output 单独设置日志文件 ---
  16. // 如果你希望 Delphi 的 ErrOutput/Output 和 C++ 的 stdout/stderr 分开记录,
  17. // 或者你想利用 Delphi TextFile 的一些特性。
  18. // 如果不需要,可以注释掉这部分,Delphi 的输出也会跟随 SetStdHandle。
  19.  
  20. // 重定向 Delphi 的 ErrOutput
  21. AssignFile(DelphiErrorLogFile, TPath.Combine(ExtractFilePath(ParamStr(0)), 'delphi_error_log.txt'));
  22. try
  23. Rewrite(DelphiErrorLogFile);
  24. Move(TTextRec(DelphiErrorLogFile), TTextRec(ErrOutput), SizeOf(TTextRec));
  25. WriteLn(ErrOutput, 'Delphi ErrOutput redirected to delphi_error_log.txt');
  26. Flush(ErrOutput);
  27. except
  28. on E: EInOutError do
  29. OutputDebugString(PChar('Failed to redirect Delphi ErrOutput to file: ' + E.Message));
  30. end;
  31.  
  32. // 重定向 Delphi 的 Output
  33. AssignFile(DelphiOutputLogFile, TPath.Combine(ExtractFilePath(ParamStr(0)), 'delphi_output_log.txt'));
  34. try
  35. Rewrite(DelphiOutputLogFile);
  36. Move(TTextRec(DelphiOutputLogFile), TTextRec(Output), SizeOf(TTextRec));
  37. WriteLn(Output, 'Delphi Output redirected to delphi_output_log.txt');
  38. Flush(Output);
  39. except
  40. on E: EInOutError do
  41. OutputDebugString(PChar('Failed to redirect Delphi Output to file: ' + E.Message));
  42. end;
  43.  
  44. // --- 3. 重定向进程的标准句柄 (STD_OUTPUT_HANDLE 和 STD_ERROR_HANDLE) ---
  45. // 这将影响 C++ 的 printf 和 stderr
  46.  
  47. FillChar(SecurityAttr, SizeOf(TSecurityAttributes), 0);
  48. SecurityAttr.nLength := SizeOf(TSecurityAttributes);
  49. SecurityAttr.bInheritHandle := True; // 子进程可以继承这些句柄
  50. SecurityAttr.lpSecurityDescriptor := nil;
  51.  
  52. // 为标准输出 (stdout) 创建或打开文件
  53. StdOutFileHandle := CreateFile(
  54. PChar(LogFilePathStdOut),
  55. GENERIC_WRITE,
  56. FILE_SHARE_READ or FILE_SHARE_WRITE, // 允许其他进程读取
  57. @SecurityAttr,
  58. CREATE_ALWAYS, // 总是创建新文件,如果存在则覆盖
  59. FILE_ATTRIBUTE_NORMAL,
  60. 0
  61. );
  62.  
  63. // 为标准错误 (stderr) 创建或打开文件
  64. StdErrFileHandle := CreateFile(
  65. PChar(LogFilePathStdErr),
  66. GENERIC_WRITE,
  67. FILE_SHARE_READ or FILE_SHARE_WRITE,
  68. @SecurityAttr,
  69. CREATE_ALWAYS,
  70. FILE_ATTRIBUTE_NORMAL,
  71. 0
  72. );
  73.  
  74. // (可选) 重定向标准输入,通常对于日志记录不那么重要
  75. // StdInFileHandle := CreateFile(PChar('NUL'), GENERIC_READ, FILE_SHARE_READ, @SecurityAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  76.  
  77. Success := False;
  78. if StdOutFileHandle <> INVALID_HANDLE_VALUE then
  79. begin
  80. if SetStdHandle(STD_OUTPUT_HANDLE, StdOutFileHandle) then
  81. begin
  82. WriteLn(Output, 'Process STD_OUTPUT_HANDLE redirected to: ' + LogFilePathStdOut); // 这条会去 delphi_output_log.txt
  83. // 为了验证 SetStdHandle 是否成功,我们需要一种独立于 Delphi Output 的方式写入
  84. // 例如,如果有一个简单的 C DLL 调用 printf,它现在应该会写入 LogFilePathStdOut
  85. Success := True;
  86. end
  87. else
  88. begin
  89. WriteLn(ErrOutput, 'Failed to set STD_OUTPUT_HANDLE. Error: ' + SysErrorMessage(GetLastError));
  90. CloseHandle(StdOutFileHandle); // 关闭我们创建的句柄
  91. end;
  92. end
  93. else
  94. begin
  95. WriteLn(ErrOutput, 'Failed to create/open file for STD_OUTPUT_HANDLE (' + LogFilePathStdOut + '). Error: ' + SysErrorMessage(GetLastError));
  96. end;
  97.  
  98. if StdErrFileHandle <> INVALID_HANDLE_VALUE then
  99. begin
  100. if SetStdHandle(STD_ERROR_HANDLE, StdErrFileHandle) then
  101. begin
  102. WriteLn(ErrOutput, 'Process STD_ERROR_HANDLE redirected to: ' + LogFilePathStdErr); // 这条会去 delphi_error_log.txt
  103. // 同样,C DLL 调用 fprintf(stderr, ...) 会写入 LogFilePathStdErr
  104. if not Success then Success := True; // 如果 stdout 失败了,但 stderr 成功了,也算部分成功
  105. end
  106. else
  107. begin
  108. WriteLn(ErrOutput, 'Failed to set STD_ERROR_HANDLE. Error: ' + SysErrorMessage(GetLastError));
  109. CloseHandle(StdErrFileHandle);
  110. end;
  111. end
  112. else
  113. begin
  114. WriteLn(ErrOutput, 'Failed to create/open file for STD_ERROR_HANDLE (' + LogFilePathStdErr + '). Error: ' + SysErrorMessage(GetLastError));
  115. end;
  116.  
  117. // (可选) 重定向标准输入
  118. // if StdInFileHandle <> INVALID_HANDLE_VALUE then
  119. // begin
  120. // if not SetStdHandle(STD_INPUT_HANDLE, StdInFileHandle) then
  121. // WriteLn(ErrOutput, 'Failed to set STD_INPUT_HANDLE. Error: ' + SysErrorMessage(GetLastError));
  122. // // 通常不需要立即关闭 StdInFileHandle,除非你不再需要它
  123. // end
  124. // else
  125. // WriteLn(ErrOutput, 'Failed to open NUL for STD_INPUT_HANDLE. Error: ' + SysErrorMessage(GetLastError));
  126.  
  127.  
  128. // 重要:在 SetStdHandle 之后,Delphi 的标准输入输出流 (Input, Output, ErrOutput)
  129. // 可能需要重新初始化,以使它们使用新的操作系统句柄。
  130. // 不过,由于我们之前已经用 Move 将它们重定向到了 Delphi 的 TextFile 变量,
  131. // 它们将继续写入我们为 Delphi 指定的文件。
  132. // 如果你没有对 Delphi 的 ErrOutput/Output 做特殊处理(注释掉了第2步),
  133. // 那么在 SetStdHandle 之后,你需要调用:
  134. // Reset(Input);
  135. // Rewrite(Output);
  136. // Rewrite(ErrOutput);
  137. // 来让 Delphi 的标准流与新的 OS 句柄同步。
  138. // 但这样它们就会和 C++ 的输出写入同一个文件 (process_stdout_log.txt 和 process_stderr_log.txt)。
  139.  
  140. if Success then
  141. begin
  142. // 可以在这里进行一些测试输出,看看它们去了哪里
  143. // 例如,如果你有一个 C++ DLL 导出一个函数 DoPrintfTest()
  144. // DoPrintfTest(); // 调用它,检查 process_stdout_log.txt 和 process_stderr_log.txt
  145. end;
  146.  
  147. // 注意:通过 SetStdHandle 设置的句柄 StdOutFileHandle 和 StdErrFileHandle
  148. // 通常不应该在这里立即 CloseHandle,因为进程的标准句柄现在正指向它们。
  149. // 操作系统会在进程结束时负责关闭这些被标准句柄引用的文件句柄。
  150. // 如果你在这里关闭了它们,那么后续的 printf/stderr 输出可能会失败或行为异常。
  151. // 只有在你确定不再需要重定向,并且想恢复到原始句柄(例如控制台)时,
  152. // 才应该获取原始句柄,用 SetStdHandle 恢复,然后再关闭你创建的文件句柄。
  153. // 对于简单的日志记录到文件直到程序结束的场景,让系统处理关闭是安全的。
  154. end;