Stderr write from c++ bindings missing on Windows

Hello!
I'm calling c++ library from Rust in gui mode with

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

And for still show output to stderr / stdout I use AttachConsole from WinAPI like that:

#[cfg(windows)]
pub fn attach_console() {
    use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS};
    let _ = unsafe { AttachConsole(ATTACH_PARENT_PROCESS) };
}

Then println! and eprintln! show output in Windows console.
The problem is that if I call c++ function from Rust that writes to stdout/stderr it doesn't show the output in the console in gui mode (release mode in our case due to cfg_attr).

I also created example for better understanding the issue:

Since rust doesn't catch foreign exceptions it's crucial for me at least to catch stderr logs from c++ functions. I hope that it's possible to get it into the console in GUI mode.

Thanks

Maybe related: c++ - Trying to redirect stdout and stderr on Windows - _fileno(stdout) returning -2 - Stack Overflow

The MSVC logging utilities for C++ generally don't output to stderr, but instead to the attached debugger (if present), which is a different output device.

1 Like

I added the following and now it prints to stderr/stdout both from c++ or from Rust

use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS};
use std::ffi::CString;
unsafe {
    let _ = AttachConsole(ATTACH_PARENT_PROCESS);
    let conout = CString::new("CONOUT$").expect("CString::new failed");
    let stdout = libc_stdhandle::stdout();
    let stderr = libc_stdhandle::stderr();
    let mode = CString::new("w").unwrap();
    libc::freopen(conout.as_ptr(), mode.as_ptr(), stdout);
    libc::freopen(conout.as_ptr(), mode.as_ptr(), stderr);
}

Is it ok to use it like that in release mode or it can possibly fail and crash the program?

CONOUT$ might not exist if there's no console in the parent process either. You might want to check for an error in AttachConsole and use AllocConsole() to create a new console window.

You can also use SetStdHandle() instead of freopen() for potentially slightly smaller code, since you already are pulling in the windows crate.

2 Likes

Thanks. I'll see how to use SetStdHandle
Does it possible also to get stdout/stderr handles without libc_stdhnalde create?

Yeah, you just need the constants from the documentation, eg STD_OUTPUT_HANDLE, for which standard handle you are setting, and the file handle you are setting it to. You can either use raw CreateFile() for that, a but more fiddling to get the right parameters, or just grab the raw handle out of a std::fs::File with, eg AsRawHandle in std::os::windows::io - Rust, though be careful about the File closing that handle when it gets dropped.

Now sure where am I wrong. I tried this way but it doesn't output from c++.

// GUI mode in release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

#[cfg(windows)]
pub fn attach_console() {
    use windows::Win32::{Foundation::HANDLE, System::Console::{AttachConsole, SetStdHandle, ATTACH_PARENT_PROCESS, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE}};
    use std::{fs::OpenOptions, os::windows::io::AsRawHandle};

    unsafe {
        if AttachConsole(ATTACH_PARENT_PROCESS).is_ok() {
            let conout = OpenOptions::new()
                .write(true)
                .open("CONOUT$")
                .expect("Failed to open CONOUT$");
            SetStdHandle(STD_OUTPUT_HANDLE, HANDLE(conout.as_raw_handle() as _)).unwrap();
            SetStdHandle(STD_ERROR_HANDLE, HANDLE(conout.as_raw_handle() as _)).unwrap();
            std::mem::forget(conout);
        }
    }
}

The following works:

// GUI mode in release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

#[cfg(windows)]
pub fn attach_console() {
    use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS};
    use std::ffi::CString;
    unsafe {
        if AttachConsole(ATTACH_PARENT_PROCESS).is_ok() {
            let conout = CString::new("CONOUT$").expect("CString::new failed");
            let stdout = libc_stdhandle::stdout();
            let stderr = libc_stdhandle::stderr();
            let mode = CString::new("w").unwrap();
            libc::freopen(conout.as_ptr(), mode.as_ptr(), stdout);
            libc::freopen(conout.as_ptr(), mode.as_ptr(), stderr);
        }
    }
}

hmm, that does look roughly correct... I'll need to check it out later, see if I've missed something.

1 Like

Maybe it's not possible without libc
See c++ - Redirect standard output to console in GUI application - Stack Overflow

The c++ standard streams are managed internally by the c runtime library. SetStdHandle affects other WinAPI calls that use the handle, but not the c++ runtime's streams. That's why I need to use freopen() or similar functions from libc to change the c++ runtime streams.

Ah, that would make some sense that libc has an internal copy of the old handles. I basically never used libc to print even when using C, so I never ran into that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.