From: Igor Kulaychuk Date: Wed, 22 Aug 2018 20:31:54 +0000 (+0300) Subject: Enable I/O redirection and other platform functions on Windows X-Git-Tag: accepted/tizen/unified/20180921.143126~2^2~5 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=73fb919fde235394f56312b9b057b2fbe805f475;p=sdk%2Ftools%2Fnetcoredbg.git Enable I/O redirection and other platform functions on Windows --- diff --git a/src/debug/netcoredbg/CMakeLists.txt b/src/debug/netcoredbg/CMakeLists.txt index 5eb428a..13a8877 100644 --- a/src/debug/netcoredbg/CMakeLists.txt +++ b/src/debug/netcoredbg/CMakeLists.txt @@ -64,7 +64,7 @@ endif() add_executable(netcoredbg ${netcoredbg_SRC}) if (WIN32) - target_link_libraries(netcoredbg corguids) + target_link_libraries(netcoredbg corguids wsock32 ws2_32) else() target_link_libraries(netcoredbg corguids dl pthread) endif() diff --git a/src/debug/netcoredbg/platform.cpp b/src/debug/netcoredbg/platform.cpp index cb8dbce..3a940a6 100644 --- a/src/debug/netcoredbg/platform.cpp +++ b/src/debug/netcoredbg/platform.cpp @@ -2,13 +2,25 @@ // Distributed under the MIT License. // See the LICENSE file in the project root for more information. +#ifndef FEATURE_PAL +// Turn off macro definitions named max and min in header file +// to avoid compile error for std::max(). +#define NOMINMAX +#endif +#ifdef _MSC_VER +// Disable compiler warning about unsafe std::copy +#define _SCL_SECURE_NO_WARNINGS +#endif + #include "platform.h" #include #include #include #include +#include +#ifdef FEATURE_PAL #include #include #include @@ -23,13 +35,18 @@ #include #endif +#else +#include +#include +#endif unsigned long OSPageSize() { static unsigned long pageSize = 0; +#ifdef FEATURE_PAL if (pageSize == 0) pageSize = sysconf(_SC_PAGESIZE); - +#endif return pageSize; } @@ -39,6 +56,462 @@ std::string GetFileName(const std::string &path) return i == std::string::npos ? path : path.substr(i + 1); } +// From https://stackoverflow.com/questions/13541313/handle-socket-descriptors-like-file-descriptor-fstream-c-linux +#ifdef WIN32 +typedef HANDLE fd_t; +#define FD_INVALID_VALUE INVALID_HANDLE_VALUE +#else +typedef int fd_t; +#define FD_INVALID_VALUE (-1) +#endif + +class fdbuf : public std::streambuf +{ +private: + enum { bufsize = 1024 }; + char outbuf_[bufsize]; + char inbuf_[bufsize + 16 - sizeof(int)]; + fd_t fd_; +public: + typedef std::streambuf::traits_type traits_type; + + fdbuf(fd_t fd); + virtual ~fdbuf(); + void open(fd_t fd); + void close(); + +protected: + int overflow(int c) override; + int underflow() override; + int sync() override; + +private: + int fdsync(); + + std::streamsize fdread(void *buf, size_t count); + std::streamsize fdwrite(const void *buf, size_t count); + void fdclose(); +}; + +fdbuf::fdbuf(fd_t fd) + : fd_(FD_INVALID_VALUE) { + this->open(fd); +} + +fdbuf::~fdbuf() { + if (this->fd_ != FD_INVALID_VALUE) { + this->fdsync(); + this->fdclose(); + } +} + +std::streamsize fdbuf::fdread(void *buf, size_t count) +{ +#ifdef WIN32 + DWORD dwRead = 0; + BOOL bSuccess = ReadFile(this->fd_, buf, (DWORD)count, &dwRead, NULL); + + if (!bSuccess) + dwRead = 0; + + return dwRead; +#else + return ::read(this->fd_, buf, count); +#endif +} + +std::streamsize fdbuf::fdwrite(const void *buf, size_t count) +{ +#ifdef WIN32 + DWORD dwWritten = 0; + BOOL bSuccess = WriteFile(this->fd_, buf, (DWORD)count, &dwWritten, NULL); + + if (!bSuccess) + dwWritten = 0; + + return dwWritten; +#else + return ::write(this->fd_, buf, count); +#endif +} + +void fdbuf::fdclose() +{ +#ifdef WIN32 + CloseHandle(this->fd_); +#else + ::close(this->fd_); +#endif +} + +void fdbuf::open(fd_t fd) { + this->close(); + this->fd_ = fd; + this->setg(this->inbuf_, this->inbuf_, this->inbuf_); + this->setp(this->outbuf_, this->outbuf_ + bufsize - 1); +} + +void fdbuf::close() { + if (!(this->fd_ < 0)) { + this->sync(); + this->fdclose(); + } +} + +int fdbuf::overflow(int c) { + if (!traits_type::eq_int_type(c, traits_type::eof())) { + *this->pptr() = traits_type::to_char_type(c); + this->pbump(1); + } + return this->sync() == -1 + ? traits_type::eof() + : traits_type::not_eof(c); +} + +int fdbuf::sync() { + return fdsync(); +} + +int fdbuf::fdsync() { + if (this->pbase() != this->pptr()) { + std::streamsize size(this->pptr() - this->pbase()); + std::streamsize done(fdwrite(this->outbuf_, size)); + // The code below assumes that it is success if the stream made + // some progress. Depending on the needs it may be more + // reasonable to consider it a success only if it managed to + // write the entire buffer and, e.g., loop a couple of times + // to try achieving this success. + if (0 < done) { + std::copy(this->pbase() + done, this->pptr(), this->pbase()); + this->setp(this->pbase(), this->epptr()); + this->pbump((int)(size - done)); + } + } + return this->pptr() != this->epptr()? 0: -1; +} + +int fdbuf::underflow() +{ + if (this->gptr() == this->egptr()) { + std::streamsize pback(std::min(this->gptr() - this->eback(), + std::ptrdiff_t(16 - sizeof(int)))); + std::copy(this->egptr() - pback, this->egptr(), this->eback()); + int done((int)fdread(this->eback() + pback, bufsize)); + this->setg(this->eback(), + this->eback() + pback, + this->eback() + pback + std::max(0, done)); + } + return this->gptr() == this->egptr() + ? traits_type::eof() + : traits_type::to_int_type(*this->gptr()); +} + +#ifdef FEATURE_PAL +struct IORedirectServerHandles +{ + int m_sockFd; + int m_realStdInFd; + int m_realStdOutFd; + int m_realStdErrFd; + int m_appStdIn; + + IORedirectServerHandles() : + m_sockFd(-1), + m_realStdInFd(STDIN_FILENO), + m_realStdOutFd(STDOUT_FILENO), + m_realStdErrFd(STDERR_FILENO), + m_appStdIn(-1) + { + } + + ~IORedirectServerHandles() + { + if (m_sockFd != -1) + ::close(m_sockFd); + } + + bool IsConnected() const { return m_sockFd != -1; } + + void RedirectOutput( + std::function onStdOut, + std::function onStdErr); + + int WaitForConnection(uint16_t port); +}; +#else +struct IORedirectServerHandles +{ + SOCKET m_sockFd; + HANDLE m_realStdInFd; + HANDLE m_realStdOutFd; + HANDLE m_realStdErrFd; + HANDLE m_appStdIn; + + IORedirectServerHandles() : + m_sockFd(INVALID_SOCKET), + m_realStdInFd(GetStdHandle(STD_INPUT_HANDLE)), + m_realStdOutFd(GetStdHandle(STD_OUTPUT_HANDLE)), + m_realStdErrFd(GetStdHandle(STD_ERROR_HANDLE)), + m_appStdIn(INVALID_HANDLE_VALUE) + { + } + + ~IORedirectServerHandles() + { + if (m_sockFd != INVALID_SOCKET) + { + ::closesocket(m_sockFd); + } + WSACleanup(); + } + + bool IsConnected() const { return m_sockFd != INVALID_SOCKET; } + + void RedirectOutput( + std::function onStdOut, + std::function onStdErr); + + SOCKET WaitForConnection(uint16_t port); +}; +#endif + +#ifndef FEATURE_PAL + +void AddFilesFromDirectoryToTpaList(const std::string &directory, std::string& tpaList) +{ + const char * const tpaExtensions[] = { + "*.ni.dll", // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir + "*.dll", + "*.ni.exe", + "*.exe", + }; + + std::set addedAssemblies; + + // Walk the directory for each extension separately so that we first get files with .ni.dll extension, + // then files with .dll extension, etc. + for (int extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++) + { + const char* ext = tpaExtensions[extIndex]; + size_t extLength = strlen(ext); + + std::string assemblyPath(directory); + assemblyPath.append("\\"); + assemblyPath.append(tpaExtensions[extIndex]); + + WIN32_FIND_DATAA data; + HANDLE findHandle = FindFirstFileA(assemblyPath.c_str(), &data); + + if (findHandle != INVALID_HANDLE_VALUE) + { + do + { + if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + + std::string filename(data.cFileName); + size_t extPos = filename.length() - extLength; + std::string filenameWithoutExt(filename.substr(0, extPos)); + + // Make sure if we have an assembly with multiple extensions present, + // we insert only one version of it. + if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end()) + { + addedAssemblies.insert(filenameWithoutExt); + + tpaList.append(directory); + tpaList.append("\\"); + tpaList.append(filename); + tpaList.append(";"); + } + } + } + while (0 != FindNextFileA(findHandle, &data)); + + FindClose(findHandle); + } + } +} + +std::string GetExeAbsPath() +{ + char hostPath[MAX_LONGPATH + 1]; + if (::GetModuleFileNameA(NULL, hostPath, MAX_LONGPATH) == 0) + { + return false; + } + + return std::string(hostPath); +} + +bool SetWorkDir(const std::string &path) +{ + return SetCurrentDirectoryA(path.c_str()); +} + +void USleep(uint32_t duration) +{ + HANDLE timer; + LARGE_INTEGER ft; + + ft.QuadPart = -(10*(int32_t)duration); // Convert to 100 nanosecond interval, negative value indicates relative time + + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +} + +void *DLOpen(const std::string &path) +{ + return LoadLibraryA(path.c_str()); +} + +void *DLSym(void *handle, const std::string &name) +{ + return GetProcAddress((HMODULE)handle, name.c_str()); +} + +void UnsetCoreCLREnv() +{ + _putenv("CORECLR_ENABLE_PROFILING="); +} + +SOCKET IORedirectServerHandles::WaitForConnection(uint16_t port) +{ + WSADATA wsa; + SOCKET newsockfd; + int clilen; + struct sockaddr_in serv_addr, cli_addr; + + if (port == 0) + return INVALID_SOCKET; + + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + { + return INVALID_SOCKET; + } + + m_sockFd = ::socket(AF_INET, SOCK_STREAM, 0); + if (m_sockFd == INVALID_SOCKET) + { + WSACleanup(); + return INVALID_SOCKET; + } + + BOOL enable = 1; + if (setsockopt(m_sockFd, SOL_SOCKET, SO_REUSEADDR, (const char *)&enable, sizeof(BOOL)) == SOCKET_ERROR) + { + ::closesocket(m_sockFd); + WSACleanup(); + m_sockFd = INVALID_SOCKET; + return INVALID_SOCKET; + } + memset(&serv_addr, 0, sizeof(serv_addr)); + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(port); + + if (::bind(m_sockFd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) + { + ::closesocket(m_sockFd); + WSACleanup(); + m_sockFd = INVALID_SOCKET; + return INVALID_SOCKET; + } + + ::listen(m_sockFd, 5); + + CloseHandle(m_realStdInFd); + CloseHandle(m_realStdOutFd); + CloseHandle(m_realStdErrFd); + m_realStdInFd = INVALID_HANDLE_VALUE; + m_realStdOutFd = INVALID_HANDLE_VALUE; + m_realStdErrFd = INVALID_HANDLE_VALUE; + + clilen = sizeof(cli_addr); + newsockfd = ::accept(m_sockFd, (struct sockaddr *) &cli_addr, &clilen); + if (newsockfd == INVALID_SOCKET) + { + ::closesocket(m_sockFd); + WSACleanup(); + m_sockFd = INVALID_SOCKET; + return INVALID_SOCKET; + } + + return newsockfd; +} + +#define BUFSIZE 4096 + +static std::function GetFdReadFunction(HANDLE h, std::function cb) +{ + return [h, cb]() { + char buffer[BUFSIZE]; + + while (true) + { + DWORD dwRead = 0; + BOOL bSuccess = ReadFile(h, buffer, BUFSIZE, &dwRead, NULL); + + if (!bSuccess || dwRead == 0) + { + break; + } + cb(std::string(buffer, dwRead)); + } + }; +} + +void IORedirectServerHandles::RedirectOutput( + std::function onStdOut, + std::function onStdErr) +{ + SECURITY_ATTRIBUTES saAttr; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + HANDLE newStdOutRd; + HANDLE newStdOutWr; + + if (!CreatePipe(&newStdOutRd, &newStdOutWr, &saAttr, 0)) + return; + if (!SetHandleInformation(newStdOutRd, HANDLE_FLAG_INHERIT, 0)) + return; + if (!SetStdHandle(STD_OUTPUT_HANDLE, newStdOutWr)) + return; + + HANDLE newStdErrRd; + HANDLE newStdErrWr; + + if (!CreatePipe(&newStdErrRd, &newStdErrWr, &saAttr, 0)) + return; + if (!SetHandleInformation(newStdErrRd, HANDLE_FLAG_INHERIT, 0)) + return; + if (!SetStdHandle(STD_ERROR_HANDLE, newStdErrWr)) + return; + + HANDLE newStdInRd; + HANDLE newStdInWr; + + if (!CreatePipe(&newStdInRd, &newStdInWr, &saAttr, 0)) + return; + if (!SetHandleInformation(newStdInWr, HANDLE_FLAG_INHERIT, 0)) + return; + if (!SetStdHandle(STD_INPUT_HANDLE, newStdInRd)) + return; + + m_appStdIn = newStdInWr; + + std::thread(GetFdReadFunction(newStdOutRd, onStdOut)).detach(); + std::thread(GetFdReadFunction(newStdErrRd, onStdErr)).detach(); +} + +#else + void AddFilesFromDirectoryToTpaList(const std::string &directory, std::string &tpaList) { const char * const tpaExtensions[] = { @@ -192,160 +665,24 @@ void UnsetCoreCLREnv() unsetenv("CORECLR_ENABLE_PROFILING"); } -// From https://stackoverflow.com/questions/13541313/handle-socket-descriptors-like-file-descriptor-fstream-c-linux -class fdbuf : public std::streambuf -{ -private: - enum { bufsize = 1024 }; - char outbuf_[bufsize]; - char inbuf_[bufsize + 16 - sizeof(int)]; - int fd_; -public: - typedef std::streambuf::traits_type traits_type; - - fdbuf(int fd); - virtual ~fdbuf(); - void open(int fd); - void close(); - -protected: - int overflow(int c) override; - int underflow() override; - int sync() override; - -private: - int fdsync(); -}; - -fdbuf::fdbuf(int fd) - : fd_(-1) { - this->open(fd); -} - -fdbuf::~fdbuf() { - if (!(this->fd_ < 0)) { - this->fdsync(); - ::close(this->fd_); - } -} - -void fdbuf::open(int fd) { - this->close(); - this->fd_ = fd; - this->setg(this->inbuf_, this->inbuf_, this->inbuf_); - this->setp(this->outbuf_, this->outbuf_ + bufsize - 1); -} - -void fdbuf::close() { - if (!(this->fd_ < 0)) { - this->sync(); - ::close(this->fd_); - } -} - -int fdbuf::overflow(int c) { - if (!traits_type::eq_int_type(c, traits_type::eof())) { - *this->pptr() = traits_type::to_char_type(c); - this->pbump(1); - } - return this->sync() == -1 - ? traits_type::eof() - : traits_type::not_eof(c); -} - -int fdbuf::sync() { - return fdsync(); -} - -int fdbuf::fdsync() { - if (this->pbase() != this->pptr()) { - std::streamsize size(this->pptr() - this->pbase()); - std::streamsize done(::write(this->fd_, this->outbuf_, size)); - // The code below assumes that it is success if the stream made - // some progress. Depending on the needs it may be more - // reasonable to consider it a success only if it managed to - // write the entire buffer and, e.g., loop a couple of times - // to try achieving this success. - if (0 < done) { - std::copy(this->pbase() + done, this->pptr(), this->pbase()); - this->setp(this->pbase(), this->epptr()); - this->pbump(size - done); - } - } - return this->pptr() != this->epptr()? 0: -1; -} - -int fdbuf::underflow() -{ - if (this->gptr() == this->egptr()) { - std::streamsize pback(std::min(this->gptr() - this->eback(), - std::ptrdiff_t(16 - sizeof(int)))); - std::copy(this->egptr() - pback, this->egptr(), this->eback()); - int done(::read(this->fd_, this->eback() + pback, bufsize)); - this->setg(this->eback(), - this->eback() + pback, - this->eback() + pback + std::max(0, done)); - } - return this->gptr() == this->egptr() - ? traits_type::eof() - : traits_type::to_int_type(*this->gptr()); -} - -IORedirectServer::IORedirectServer( - uint16_t port, - std::function onStdOut, - std::function onStdErr) : - m_in(nullptr), - m_out(nullptr), - m_sockfd(-1), - m_realStdInFd(STDIN_FILENO), - m_realStdOutFd(STDOUT_FILENO), - m_realStdErrFd(STDERR_FILENO), - m_appStdIn(-1) -{ - RedirectOutput(onStdOut, onStdErr); - int fd = WaitForConnection(port); - - if (fd != -1) - { - m_in = new fdbuf(fd); - m_out = new fdbuf(fd); - } - else - { - m_in = new fdbuf(m_realStdInFd); - m_out = new fdbuf(m_realStdOutFd); - } - m_err = new fdbuf(m_realStdErrFd); - - m_prevIn = std::cin.rdbuf(); - m_prevOut = std::cout.rdbuf(); - m_prevErr = std::cerr.rdbuf(); - - std::cin.rdbuf(m_in); - std::cout.rdbuf(m_out); - std::cerr.rdbuf(m_err); -} - -int IORedirectServer::WaitForConnection(uint16_t port) +int IORedirectServerHandles::WaitForConnection(uint16_t port) { int newsockfd; socklen_t clilen; struct sockaddr_in serv_addr, cli_addr; - int n; if (port == 0) return -1; - m_sockfd = ::socket(AF_INET, SOCK_STREAM, 0); - if (m_sockfd < 0) + m_sockFd = ::socket(AF_INET, SOCK_STREAM, 0); + if (m_sockFd < 0) return -1; int enable = 1; - if (setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) + if (setsockopt(m_sockFd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { - ::close(m_sockfd); - m_sockfd = -1; + ::close(m_sockFd); + m_sockFd = -1; return -1; } memset(&serv_addr, 0, sizeof(serv_addr)); @@ -354,14 +691,14 @@ int IORedirectServer::WaitForConnection(uint16_t port) serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(port); - if (::bind(m_sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) + if (::bind(m_sockFd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { - ::close(m_sockfd); - m_sockfd = -1; + ::close(m_sockFd); + m_sockFd = -1; return -1; } - ::listen(m_sockfd, 5); + ::listen(m_sockFd, 5); // On Tizen, launch_app won't terminate until stdin, stdout and stderr are closed. // But Visual Studio initiates the connection only after launch_app termination, @@ -374,28 +711,17 @@ int IORedirectServer::WaitForConnection(uint16_t port) m_realStdErrFd = -1; clilen = sizeof(cli_addr); - newsockfd = ::accept(m_sockfd, (struct sockaddr *) &cli_addr, &clilen); + newsockfd = ::accept(m_sockFd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0) { - ::close(m_sockfd); - m_sockfd = -1; + ::close(m_sockFd); + m_sockFd = -1; return -1; } return newsockfd; } -IORedirectServer::~IORedirectServer() -{ - std::cin.rdbuf(m_prevIn); - std::cout.rdbuf(m_prevOut); - std::cout.rdbuf(m_prevErr); - delete m_in; - delete m_out; - delete m_err; - ::close(m_sockfd); -} - static std::function GetFdReadFunction(int fd, std::function cb) { return [fd, cb]() { @@ -416,8 +742,9 @@ static std::function GetFdReadFunction(int fd, std::function onStdOut, - std::function onStdErr) +void IORedirectServerHandles::RedirectOutput( + std::function onStdOut, + std::function onStdErr) { // TODO: fcntl(fd, F_SETFD, FD_CLOEXEC); m_realStdInFd = dup(STDIN_FILENO); @@ -445,3 +772,62 @@ void IORedirectServer::RedirectOutput(std::function onStdOut, std::thread(GetFdReadFunction(outPipe[0], onStdOut)).detach(); std::thread(GetFdReadFunction(errPipe[0], onStdErr)).detach(); } + +#endif + +IORedirectServer::operator bool() const +{ + return m_handles->IsConnected(); +} + +IORedirectServer::IORedirectServer( + uint16_t port, + std::function onStdOut, + std::function onStdErr) : + m_in(nullptr), + m_out(nullptr), + m_handles(new IORedirectServerHandles()) +{ + m_handles->RedirectOutput(onStdOut, onStdErr); + +#ifdef WIN32 + SOCKET s = m_handles->WaitForConnection(port); + if (s != INVALID_SOCKET) + { + m_in = new fdbuf((HANDLE)s); + m_out = new fdbuf((HANDLE)s); + } +#else + int fd = m_handles->WaitForConnection(port); + if (fd != -1) + { + m_in = new fdbuf(fd); + m_out = new fdbuf(fd); + } +#endif + else + { + m_in = new fdbuf(m_handles->m_realStdInFd); + m_out = new fdbuf(m_handles->m_realStdOutFd); + } + m_err = new fdbuf(m_handles->m_realStdErrFd); + + m_prevIn = std::cin.rdbuf(); + m_prevOut = std::cout.rdbuf(); + m_prevErr = std::cerr.rdbuf(); + + std::cin.rdbuf(m_in); + std::cout.rdbuf(m_out); + std::cerr.rdbuf(m_err); +} + +IORedirectServer::~IORedirectServer() +{ + std::cin.rdbuf(m_prevIn); + std::cout.rdbuf(m_prevOut); + std::cout.rdbuf(m_prevErr); + delete m_in; + delete m_out; + delete m_err; + delete m_handles; +} diff --git a/src/debug/netcoredbg/platform.h b/src/debug/netcoredbg/platform.h index b77fde5..b9eabcf 100644 --- a/src/debug/netcoredbg/platform.h +++ b/src/debug/netcoredbg/platform.h @@ -7,6 +7,8 @@ #include #include +#include + unsigned long OSPageSize(); void AddFilesFromDirectoryToTpaList(const std::string &directory, std::string &tpaList); std::string GetExeAbsPath(); @@ -17,6 +19,8 @@ void *DLOpen(const std::string &path); void *DLSym(void *handle, const std::string &name); void UnsetCoreCLREnv(); +struct IORedirectServerHandles; + class IORedirectServer { std::streambuf *m_in; @@ -25,21 +29,13 @@ class IORedirectServer std::streambuf *m_prevIn; std::streambuf *m_prevOut; std::streambuf *m_prevErr; - int m_sockfd; - int m_realStdInFd; - int m_realStdOutFd; - int m_realStdErrFd; - int m_appStdIn; + IORedirectServerHandles *m_handles; - void RedirectOutput( - std::function onStdOut, - std::function onStdErr); - int WaitForConnection(uint16_t port); public: IORedirectServer( uint16_t port, std::function onStdOut, std::function onStdErr); ~IORedirectServer(); - operator bool() const { return m_sockfd != -1; } + operator bool() const; };