From db4d104ed965592c1d89c31d96301f0a44640df7 Mon Sep 17 00:00:00 2001
From: Loic Guegan <manzerbredes@mailbox.org>
Date: Thu, 3 Feb 2022 07:10:32 +0100
Subject: [PATCH] Integrate windows process

---
 CMakeLists.txt              |   8 +-
 README.md                   |   2 +-
 src/Process.hpp             |   4 +-
 src/ProcessWindows.cpp      | 148 ++++++++++++++++++++++++++++++++++++
 src/ProcessWindows.hpp      |  27 +++++++
 src/{UCI.hpp => UCI.hpp.in} |   6 +-
 6 files changed, 187 insertions(+), 8 deletions(-)
 create mode 100644 src/ProcessWindows.cpp
 create mode 100644 src/ProcessWindows.hpp
 rename src/{UCI.hpp => UCI.hpp.in} (94%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9b83789..954e92f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.10)
 project(uciadapter)
 
 # Configure Process
-add_definitions(-DUNIX)
 SET(process src/ProcessLinux.cpp)
+SET(COMPILE_PLATFORM UNIX)
 if(WIN32)
-    remove_definitions(-DUNIX)
-    message(FATAL_ERROR "uciadapter is not yet compatible with Windows")
+    SET(process src/ProcessWindows.cpp)
+    SET(COMPILE_PLATFORM WIN32)
 endif()
 add_library(uciadapter SHARED src/UCI.cpp ${process})
 
@@ -14,7 +14,7 @@ add_library(uciadapter SHARED src/UCI.cpp ${process})
 set(UCIADAPTER_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/includes) # For conveniance
 set(UCIADAPTER_INCLUDE_DIR ${UCIADAPTER_INCLUDE_DIR} PARENT_SCOPE) # To be used by other projects with add_subdirectory()
 file(MAKE_DIRECTORY ${UCIADAPTER_INCLUDE_DIR})
-configure_file(src/UCI.hpp ${UCIADAPTER_INCLUDE_DIR} COPYONLY)
+configure_file(src/UCI.hpp.in ${UCIADAPTER_INCLUDE_DIR}/UCI.hpp)
 configure_file(src/Process.hpp ${UCIADAPTER_INCLUDE_DIR} COPYONLY)
 configure_file(src/ProcessLinux.hpp ${UCIADAPTER_INCLUDE_DIR} COPYONLY)
 include_directories(${UCIADAPTER_INCLUDE_DIR})
diff --git a/README.md b/README.md
index af03e51..a0341ae 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 # uciadapter
 *uciadapter* is a C++ library that allows you to communicate with any chess
 engines that follows the [UCI Protocol](http://wbec-ridderkerk.nl/html/UCIProtocol.html).
-It aims to work on Linux and Windows (not yet on windows).
+It works on both platforms, Linux and Windows.
 
 # How to use it ?
 PGNP can be used as a shared library in your project.
diff --git a/src/Process.hpp b/src/Process.hpp
index 3265cc9..e1f71ad 100644
--- a/src/Process.hpp
+++ b/src/Process.hpp
@@ -1,5 +1,4 @@
 #include <string>
-
 #define ENGINE_TIMEOUT 5 // In seconds
 #define BUFFER_SIZE 1024
 
@@ -12,7 +11,8 @@ public:
   virtual void Kill() = 0;
   /// @brief Start the engine from file path
   virtual void Start(std::string) = 0;
-  /// @brief Read one line from the stdout of the engine (could raise a ReadTimeoutExpire)
+  /// @brief Read one line from the stdout of the engine (could raise a
+  /// ReadTimeoutExpire)
   virtual std::string ReadLine() = 0;
   /// @brief Write to engine stdin
   virtual void Write(std::string) = 0;
diff --git a/src/ProcessWindows.cpp b/src/ProcessWindows.cpp
new file mode 100644
index 0000000..77ca13c
--- /dev/null
+++ b/src/ProcessWindows.cpp
@@ -0,0 +1,148 @@
+#include "ProcessWindows.hpp"
+#include <atlstr.h>
+
+namespace uciadapter {
+
+void ProcessWindows::CreateChildProcess(std::string engine_path)
+// Create a child process that uses the previously created pipes for STDIN and
+// STDOUT.
+{
+  TCHAR szCmdline[1024];
+  _tcscpy_s(szCmdline, CA2T(engine_path.c_str()));
+  PROCESS_INFORMATION piProcInfo;
+  STARTUPINFO siStartInfo;
+  BOOL bSuccess = FALSE;
+
+  // Set up members of the PROCESS_INFORMATION structure.
+
+  ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
+
+  // Set up members of the STARTUPINFO structure.
+  // This structure specifies the STDIN and STDOUT handles for redirection.
+
+  ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
+  siStartInfo.cb = sizeof(STARTUPINFO);
+  siStartInfo.hStdError = g_hChildStd_OUT_Wr;
+  siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
+  siStartInfo.hStdInput = g_hChildStd_IN_Rd;
+  siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+  // Create the child process.
+
+  bSuccess = CreateProcess(NULL,
+                           szCmdline,    // command line
+                           NULL,         // process security attributes
+                           NULL,         // primary thread security attributes
+                           TRUE,         // handles are inherited
+                           0,            // creation flags
+                           NULL,         // use parent's environment
+                           NULL,         // use parent's current directory
+                           &siStartInfo, // STARTUPINFO pointer
+                           &piProcInfo); // receives PROCESS_INFORMATION
+
+  // If an error occurs, exit the application.
+  if (!bSuccess)
+    ErrorExit(TEXT("CreateProcess"));
+  else {
+    // Close handles to the child process and its primary thread.
+    // Some applications might keep these handles to monitor the status
+    // of the child process, for example.
+
+    CloseHandle(piProcInfo.hProcess);
+    CloseHandle(piProcInfo.hThread);
+
+    // Close handles to the stdin and stdout pipes no longer needed by the child
+    // process. If they are not explicitly closed, there is no way to recognize
+    // that the child process has ended.
+
+    CloseHandle(g_hChildStd_OUT_Wr);
+    CloseHandle(g_hChildStd_IN_Rd);
+  }
+}
+
+void ProcessWindows::ErrorExit(PTSTR lpszFunction)
+
+// Format a readable error message, display a message box,
+// and exit from the application.
+{
+  LPVOID lpMsgBuf;
+  LPVOID lpDisplayBuf;
+  DWORD dw = GetLastError();
+
+  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+                    FORMAT_MESSAGE_IGNORE_INSERTS,
+                NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                (LPTSTR)&lpMsgBuf, 0, NULL);
+
+  lpDisplayBuf =
+      (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) +
+                                         lstrlen((LPCTSTR)lpszFunction) + 40) *
+                                            sizeof(TCHAR));
+  StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR),
+                  TEXT("%s failed with error %d: %s"), lpszFunction, dw,
+                  lpMsgBuf);
+  MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
+
+  LocalFree(lpMsgBuf);
+  LocalFree(lpDisplayBuf);
+  ExitProcess(1);
+}
+
+ProcessWindows::ProcessWindows() {
+
+  printf("\n->Start of parent execution.\n");
+  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+  saAttr.bInheritHandle = TRUE;
+  saAttr.lpSecurityDescriptor = NULL;
+  if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
+    ErrorExit(TEXT("StdoutRd CreatePipe"));
+
+  // Ensure the read handle to the pipe for STDOUT is not inherited.
+
+  if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
+    ErrorExit(TEXT("Stdout SetHandleInformation"));
+
+  // Create a pipe for the child process's STDIN.
+
+  if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
+    ErrorExit(TEXT("Stdin CreatePipe"));
+
+  // Ensure the write handle to the pipe for STDIN is not inherited.
+
+  if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
+    ErrorExit(TEXT("Stdin SetHandleInformation"));
+}
+
+void ProcessWindows::Kill() {}
+
+void ProcessWindows::Start(std::string path) { CreateChildProcess(path); }
+
+std::string ProcessWindows::ReadLine() {
+  DWORD dwRead, dwWritten;
+  CHAR chBuf[2014];
+  BOOL bSuccess = FALSE;
+  HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
+  std::string line = "";
+  for (;;) {
+    CHAR c;
+    bSuccess = ReadFile(g_hChildStd_OUT_Rd, &c, 1, &dwRead, NULL);
+    line += c;
+    if (!bSuccess || dwRead == 0)
+      break;
+    if (c == '\n')
+      break;
+  }
+
+  printf("ejeh\n");
+  return (line);
+}
+
+void ProcessWindows::Write(std::string data) {
+  DWORD dwRead, dwWritten;
+  CHAR chBuf[1024];
+  BOOL bSuccess = FALSE;
+
+  bSuccess =
+      WriteFile(g_hChildStd_IN_Wr, data.c_str(), data.size(), &dwWritten, NULL);
+}
+} // namespace uciadapter
\ No newline at end of file
diff --git a/src/ProcessWindows.hpp b/src/ProcessWindows.hpp
new file mode 100644
index 0000000..6f9f212
--- /dev/null
+++ b/src/ProcessWindows.hpp
@@ -0,0 +1,27 @@
+#include "Process.hpp"
+#include <chrono>
+
+#include <stdio.h>
+#include <strsafe.h>
+#include <tchar.h>
+#include <windows.h>
+
+namespace uciadapter {
+
+class ProcessWindows : public Process {
+  HANDLE g_hChildStd_IN_Rd = NULL;
+  HANDLE g_hChildStd_IN_Wr = NULL;
+  HANDLE g_hChildStd_OUT_Rd = NULL;
+  HANDLE g_hChildStd_OUT_Wr = NULL;
+  SECURITY_ATTRIBUTES saAttr;
+  void ProcessWindows::ErrorExit(PTSTR lpszFunction);
+  void ProcessWindows::CreateChildProcess(std::string);
+
+public:
+  ProcessWindows();
+  void Kill();
+  void Start(std::string);
+  std::string ReadLine();
+  void Write(std::string);
+};
+}; // namespace uciadapter
\ No newline at end of file
diff --git a/src/UCI.hpp b/src/UCI.hpp.in
similarity index 94%
rename from src/UCI.hpp
rename to src/UCI.hpp.in
index 19cc1e9..86925c5 100644
--- a/src/UCI.hpp
+++ b/src/UCI.hpp.in
@@ -1,9 +1,13 @@
+#define @COMPILE_PLATFORM@
 #ifdef UNIX
 #include "ProcessLinux.hpp"
 #define INIT_PROCESS(p)                                                        \
   { p = static_cast<Process *>(new ProcessLinux()); }
-#else
+#endif
+#ifdef WIN32
 #include "ProcessWindows.hpp"
+#define INIT_PROCESS(p)                                                        \
+  { p = static_cast<Process *>(new ProcessWindows()); }
 #endif
 #include <chrono>
 #include <sstream>