delphi程序中如何截获第三方库打印到std err的信息
Submitted by hubdog on Mon, 2025-03-31 05:48
第三方库很多Log输出是fprintf到std err的信息,调试的时候为了显示这些信息,我们需要给delphi gui程序绑定一个控制台窗口,下面的函数
procedure AllocateDebugConsole; var StdOutHandle, StdErrHandle, StdInHandle: THandle; Success: Boolean; begin // 尝试分配一个控制台窗口 if AllocConsole then begin // AllocConsole 成功后,进程的标准句柄已指向新控制台 // 现在需要让 Delphi 的标准 TextFile 变量使用这些新句柄 try // 重新打开 ErrOutput,使其关联到新的 STD_ERROR_HANDLE Rewrite(ErrOutput); // <--- 使用 Rewrite WriteLn(ErrOutput, 'ErrOutput redirected.'); // 重新打开 Output,使其关联到新的 STD_OUTPUT_HANDLE Rewrite(Output); // <--- 使用 Rewrite WriteLn(Output, 'Output redirected.'); // 重新打开 Input,使其关联到新的 STD_INPUT_HANDLE Reset(Input); // <--- 使用 Reset // WriteLn(Output, 'Input redirected.'); // 不容易测试Input,先注释掉 // 可选:设置控制台标题 SetConsoleTitle('Debug Output Console'); Flush(ErrOutput); Flush(Output); except on E: Exception do begin // 如果 Rewrite/Reset 失败 (虽然不太可能在成功 AllocConsole 后立即失败) OutputDebugString(PChar('Failed to redirect standard handles: ' + E.Message)); end; end; end else begin // 分配控制台失败 // OutputDebugString(PChar('Failed to allocate console: ' + SysErrorMessage(GetLastError))); end; end; 用法 begin AllocateDebugConsole; <----add at here Application.Initialize; Application.CreateForm(TFormMain, FormMain); Application.Run; end.
如果要将日志重定向到文件,可以使用下面的处理
procedure RedirectProcessOutputToFile; var SecurityAttr: TSecurityAttributes; StdOutFileHandle, StdErrFileHandle, StdInFileHandle: THandle; LogFilePathStdOut: string; LogFilePathStdErr: string; DelphiErrorLogFile: TextFile; // For Delphi's ErrOutput redirection DelphiOutputLogFile: TextFile; // For Delphi's Output redirection Success: Boolean; begin // --- 1. 准备日志文件路径 --- LogFilePathStdOut := TPath.Combine(ExtractFilePath(ParamStr(0)), 'process_stdout_log.txt'); LogFilePathStdErr := TPath.Combine(ExtractFilePath(ParamStr(0)), 'process_stderr_log.txt'); // --- 2. (可选) 为 Delphi 的 ErrOutput 和 Output 单独设置日志文件 --- // 如果你希望 Delphi 的 ErrOutput/Output 和 C++ 的 stdout/stderr 分开记录, // 或者你想利用 Delphi TextFile 的一些特性。 // 如果不需要,可以注释掉这部分,Delphi 的输出也会跟随 SetStdHandle。 // 重定向 Delphi 的 ErrOutput AssignFile(DelphiErrorLogFile, TPath.Combine(ExtractFilePath(ParamStr(0)), 'delphi_error_log.txt')); try Rewrite(DelphiErrorLogFile); Move(TTextRec(DelphiErrorLogFile), TTextRec(ErrOutput), SizeOf(TTextRec)); WriteLn(ErrOutput, 'Delphi ErrOutput redirected to delphi_error_log.txt'); Flush(ErrOutput); except on E: EInOutError do OutputDebugString(PChar('Failed to redirect Delphi ErrOutput to file: ' + E.Message)); end; // 重定向 Delphi 的 Output AssignFile(DelphiOutputLogFile, TPath.Combine(ExtractFilePath(ParamStr(0)), 'delphi_output_log.txt')); try Rewrite(DelphiOutputLogFile); Move(TTextRec(DelphiOutputLogFile), TTextRec(Output), SizeOf(TTextRec)); WriteLn(Output, 'Delphi Output redirected to delphi_output_log.txt'); Flush(Output); except on E: EInOutError do OutputDebugString(PChar('Failed to redirect Delphi Output to file: ' + E.Message)); end; // --- 3. 重定向进程的标准句柄 (STD_OUTPUT_HANDLE 和 STD_ERROR_HANDLE) --- // 这将影响 C++ 的 printf 和 stderr FillChar(SecurityAttr, SizeOf(TSecurityAttributes), 0); SecurityAttr.nLength := SizeOf(TSecurityAttributes); SecurityAttr.bInheritHandle := True; // 子进程可以继承这些句柄 SecurityAttr.lpSecurityDescriptor := nil; // 为标准输出 (stdout) 创建或打开文件 StdOutFileHandle := CreateFile( PChar(LogFilePathStdOut), GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, // 允许其他进程读取 @SecurityAttr, CREATE_ALWAYS, // 总是创建新文件,如果存在则覆盖 FILE_ATTRIBUTE_NORMAL, 0 ); // 为标准错误 (stderr) 创建或打开文件 StdErrFileHandle := CreateFile( PChar(LogFilePathStdErr), GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, @SecurityAttr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); // (可选) 重定向标准输入,通常对于日志记录不那么重要 // StdInFileHandle := CreateFile(PChar('NUL'), GENERIC_READ, FILE_SHARE_READ, @SecurityAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); Success := False; if StdOutFileHandle <> INVALID_HANDLE_VALUE then begin if SetStdHandle(STD_OUTPUT_HANDLE, StdOutFileHandle) then begin WriteLn(Output, 'Process STD_OUTPUT_HANDLE redirected to: ' + LogFilePathStdOut); // 这条会去 delphi_output_log.txt // 为了验证 SetStdHandle 是否成功,我们需要一种独立于 Delphi Output 的方式写入 // 例如,如果有一个简单的 C DLL 调用 printf,它现在应该会写入 LogFilePathStdOut Success := True; end else begin WriteLn(ErrOutput, 'Failed to set STD_OUTPUT_HANDLE. Error: ' + SysErrorMessage(GetLastError)); CloseHandle(StdOutFileHandle); // 关闭我们创建的句柄 end; end else begin WriteLn(ErrOutput, 'Failed to create/open file for STD_OUTPUT_HANDLE (' + LogFilePathStdOut + '). Error: ' + SysErrorMessage(GetLastError)); end; if StdErrFileHandle <> INVALID_HANDLE_VALUE then begin if SetStdHandle(STD_ERROR_HANDLE, StdErrFileHandle) then begin WriteLn(ErrOutput, 'Process STD_ERROR_HANDLE redirected to: ' + LogFilePathStdErr); // 这条会去 delphi_error_log.txt // 同样,C DLL 调用 fprintf(stderr, ...) 会写入 LogFilePathStdErr if not Success then Success := True; // 如果 stdout 失败了,但 stderr 成功了,也算部分成功 end else begin WriteLn(ErrOutput, 'Failed to set STD_ERROR_HANDLE. Error: ' + SysErrorMessage(GetLastError)); CloseHandle(StdErrFileHandle); end; end else begin WriteLn(ErrOutput, 'Failed to create/open file for STD_ERROR_HANDLE (' + LogFilePathStdErr + '). Error: ' + SysErrorMessage(GetLastError)); end; // (可选) 重定向标准输入 // if StdInFileHandle <> INVALID_HANDLE_VALUE then // begin // if not SetStdHandle(STD_INPUT_HANDLE, StdInFileHandle) then // WriteLn(ErrOutput, 'Failed to set STD_INPUT_HANDLE. Error: ' + SysErrorMessage(GetLastError)); // // 通常不需要立即关闭 StdInFileHandle,除非你不再需要它 // end // else // WriteLn(ErrOutput, 'Failed to open NUL for STD_INPUT_HANDLE. Error: ' + SysErrorMessage(GetLastError)); // 重要:在 SetStdHandle 之后,Delphi 的标准输入输出流 (Input, Output, ErrOutput) // 可能需要重新初始化,以使它们使用新的操作系统句柄。 // 不过,由于我们之前已经用 Move 将它们重定向到了 Delphi 的 TextFile 变量, // 它们将继续写入我们为 Delphi 指定的文件。 // 如果你没有对 Delphi 的 ErrOutput/Output 做特殊处理(注释掉了第2步), // 那么在 SetStdHandle 之后,你需要调用: // Reset(Input); // Rewrite(Output); // Rewrite(ErrOutput); // 来让 Delphi 的标准流与新的 OS 句柄同步。 // 但这样它们就会和 C++ 的输出写入同一个文件 (process_stdout_log.txt 和 process_stderr_log.txt)。 if Success then begin // 可以在这里进行一些测试输出,看看它们去了哪里 // 例如,如果你有一个 C++ DLL 导出一个函数 DoPrintfTest() // DoPrintfTest(); // 调用它,检查 process_stdout_log.txt 和 process_stderr_log.txt end; // 注意:通过 SetStdHandle 设置的句柄 StdOutFileHandle 和 StdErrFileHandle // 通常不应该在这里立即 CloseHandle,因为进程的标准句柄现在正指向它们。 // 操作系统会在进程结束时负责关闭这些被标准句柄引用的文件句柄。 // 如果你在这里关闭了它们,那么后续的 printf/stderr 输出可能会失败或行为异常。 // 只有在你确定不再需要重定向,并且想恢复到原始句柄(例如控制台)时, // 才应该获取原始句柄,用 SetStdHandle 恢复,然后再关闭你创建的文件句柄。 // 对于简单的日志记录到文件直到程序结束的场景,让系统处理关闭是安全的。 end;