The problem is that stdin
and cin
are buffered I/O wrappers around the low-level operating system routines, and are separate from one another. (That's also why you shouldn't mix them for input, because one could buffer parts of a read.)
freopen()
will replace the stdin
wrapper, but the underlying operating system file descriptor and/or handle will not necessarily be replaced (and on your system it won't), so cin
will still be connected to the underlying handle.
To my knowledge there is no portable way of changing cin
in C++.
However, if you only want to support the most common operating systems, there are possibilities:
POSIX: On POSIX-based operating systems, such as Linux, macOS, FreeBSD, etc., you can use the low-level open()
function (#include <fcntl.h>
) to open the file and then use dup2()
to replace the file descriptor 0
, which is the standard input file descriptor. You'd want to use the following code:
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdexcept>
// for std::format, C++20 and higher
#include <format>
int fd = open(fileName, O_RDONLY);
if (fd < 0) {
int error = errno;
throw std::runtime_error(std::format("Couldn't open file: {}", strerror(error)));
}
int r = dup2(fd, 0);
if (r < 0) {
int error = errno;
close(fd);
throw std::runtime_error(std::format("Couldn't replace input file descriptor: {}", strerror(error)));
}
// Close the original opened descriptor
close(fd);
(Technical note for experts: if your OS supports it you might also want to pass O_CLOEXEC
to the flags of open()
, but please make sure that dup2()
also clears that flag on your OS, which it does on Linux, because the standard descriptors should not have that flag set. This implementation has a tiny race condition due to not supplying that flag, but since we immediately close fd
after the call to dup2()
, I'm personally ok with that, you typically don't replace your standard input descriptor in a multi-threaded environment anyway.)
Windows: On Windows it's a bit more complicated. You can use the SetStdHandle()
Win32 API function to replace the input file handle itself, but that doesn't help with the C runtime (which also underlies the C++ runtime in some cases), because that has a copy of the underlying handle. So you need to also use _open_osfhandle()
to get a file descriptor from the handle, and use _dup2()
to dup it (similar to the POSIX version, but the file descriptors here are just for the C runtime itself).
The following code should work on Windows:
#include <windows.h>
#include <fcntl.h>
#include <stdexcept>
// for std::format, C++20 and higher
#include <format>
// This is so that the handle can be inherited by
// child processes.
SECURITY_ATTRIBUTES attrs{};
attrs.nLength = sizeof(attrs);
attrs.lpSecurityDescriptor = nullptr;
attrs.bInheritHandle = TRUE;
// use CreateFileW if fileName is a Unicode wide string
HANDLE handle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, &attrs, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
throw std::runtime_error(std::format("Couldn't open file: error code {:d}", error));
}
BOOL ok = SetStdHandle(STD_INPUT_HANDLE, handle);
if (!ok) {
DWORD error = GetLastError();
CloseHandle(handle);
throw std::runtime_error(std::format("Couldn't use handle as standard input: error code {:d}", error));
}
/* _open_osfhandle will take ownership of the handle itself, so we
* have to provide it with a duplicate, because we call _close()
* at the bottom, and we don't want the handle we passed to
* SetStdHandle() to be closed, because that causes some weird
* failures in some weird places
*/
HANDLE handleForDescriptor{INVALID_HANDLE_VALUE};
ok = DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), &handleForDescriptor, 0, FALSE, DUPLICATE_SAME_ACCESS);
if (!ok) {
DWORD error = GetLastError();
/* Don't CloseHandle() here, because we already used
* SetStdHandle, so the handle is already part of the
* global state.
*/
throw std::runtime_error(std::format("Couldn't duplicate handle: error code {:d}", error));
}
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(handleForDescriptor), _O_RDONLY | _O_TEXT);
if (fd < 0) {
DWORD error = GetLastError();
CloseHandle(handleForDescriptor);
throw std::runtime_error(std::format("Couldn't get file descriptor for handle: error code {:d}", error));
}
int r = _dup2(fd, 0);
if (r < 0) {
DWORD error = GetLastError();
_close(fd);
throw std::runtime_error(std::format("Couldn't get file descriptor for handle: error code {:d}", error));
}
_close(fd);
A word of caution: this will only work if you are using the same C runtime as all of the code that is using cin
/stdin
. If for example you use the msvcrt.dll
as the C runtime for your project, but you are using functions from a DLL that uses msvcr120.dll
itself, that DLL will not see the change, because the different C runtime it uses will track a different internal file descriptor. If you compile this code and the code that reads data from cin
with the same compiler, this shouldn't be an issue, but if you run third-party code that you don't explicitly recompile with your compiler, I don't know of an easy way around that kind of limitation.
Disclaimer: I've not tested this explicitly, I have similar code for replacing stderr
that I've adapted here. I may have made some mistakes translating it.