Help needed for porting to Windows

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
Evert
Posts: 2929
Joined: Sat Jan 22, 2011 12:42 am
Location: NL

Help needed for porting to Windows

Post by Evert »

I need a bit of help getting Windows equivalents for some functions. I can find information on how to do it, but since I don't have a Windows system to test on turning this into actual working code is extremely tedious.

First, some background. I once wrote a match-playing program, similar in spirit to cute-chess-cli, for the purpose of playing chess-variant games without needing an X-server. This was tightly integrated with the old version of my variant engine Sjaak. I updated this recently, and now it is completely stand-alone. In addition to the two playing engines, it loads a third engine that acts as a referee. This referee is responsible for monitoring move legality and testing whether games are over. Through a minor extension of CECP, it is also responsible for producing FEN positions and SAN moves when needed. It all works pretty well.

However, it makes rather heavy use of posix functions to set up a bi-directional pipe (actually two pipes, one for reading and one for writing). I think this is all fairly standard fare (XBoard does the same thing, for instance), but a different setup is needed for Windows.

What I need Windows equivalents of is the following. First of all, I need a way to test if a child process is still alive. I use

Code: Select all

bool child_is_alive(program_t * const prog)
{
   int status;
   pid_t result = waitpid(prog->pid, &status, WNOHANG);
   if (result == 0) {
      return true;
   } else if (result == -1) {
      perror(NULL);
   }

   // Child exited
   return false;
}
The program_t is a struct that stores information about each child process (the process ID, the pipe through which you can communicate).
I have a function that waits for input from either child:

Code: Select all

void wait_input(program_t **prog, int nprog, int musec)
{

   struct timeval timeout;
   fd_set readfds, errfds;

   FD_ZERO(&readfds);
   FD_ZERO(&errfds);
   for &#40;int n = 0; n<nprog; n++) &#123;
      int fd = prog&#91;n&#93;->f->in_fd;
      FD_SET&#40;fd, &readfds&#41;;
      FD_SET&#40;fd, &errfds&#41;;
   &#125;

   timeout.tv_sec = musec / 1000000;
   timeout.tv_usec = musec % 1000000;
   select&#40;32, &readfds, NULL, &errfds, &timeout&#41;;
&#125;
It is called with the process objects for the two playing programs, and it waits until either of them has output available for processing.

Then there is the bidirectional pipe stuff. This is again based on a struct,

Code: Select all

typedef struct &#123;
   FILE *in;      /* Input stream for parent */
   FILE *out;     /* Output stream for parent */
   int in_fd, out_fd;
   pid_t pid;     /* pid of child process */
&#125; pipe2_t;
with the following functions that operate on it:

Code: Select all

pipe2_t *p2open&#40;const char *cmd, char *const argv&#91;&#93;)
&#123;
   pipe2_t *p2 = NULL;
   int writepipe&#91;2&#93; = &#123;-1,-1&#125;,	/* parent -> child */
       readpipe &#91;2&#93; = &#123;-1,-1&#125;;	/* child -> parent */
   pid_t	child_pid;

   /* Open read and write pipes */
   if &#40;pipe&#40;readpipe&#41; < 0&#41;
      goto done;
   if &#40;pipe&#40;writepipe&#41; < 0&#41; &#123;
      close&#40;readpipe&#91;0&#93;);
      close&#40;readpipe&#91;1&#93;);
      goto done;
   &#125;

   /* Convenient defines to make code easier to read */
#define PARENT_READ	readpipe&#91;0&#93;
#define CHILD_WRITE	readpipe&#91;1&#93;
#define CHILD_READ	writepipe&#91;0&#93;
#define PARENT_WRITE	writepipe&#91;1&#93;

   int lifeline&#91;2&#93;;
   if &#40;pipe&#40;lifeline&#41; < 0&#41; &#123;
      close&#40;readpipe&#91;0&#93;);
      close&#40;readpipe&#91;1&#93;);
      close&#40;writepipe&#91;0&#93;);
      close&#40;writepipe&#91;1&#93;);
      goto done;
   &#125;

   /* Spawn child process */
   child_pid = fork&#40;);

   /* Failed to launch child process */
   if &#40;child_pid < 0&#41; &#123;
      close&#40;readpipe&#91;0&#93;);
      close&#40;readpipe&#91;1&#93;);
      close&#40;writepipe&#91;0&#93;);
      close&#40;writepipe&#91;1&#93;);
      goto done;
   &#125;

   /* Child process */
   if &#40;child_pid == 0&#41; &#123;
      close&#40;lifeline&#91;0&#93;);
      fcntl&#40;lifeline&#91;1&#93;, F_SETFD, FD_CLOEXEC&#41;;

      /* Close parent file descriptors */
      close&#40;PARENT_WRITE&#41;;
      close&#40;PARENT_READ&#41;;

      /* Attach input and output pipes to stdin, stdout */
      dup2&#40;CHILD_READ,  STDIN_FILENO&#41;;  close&#40;CHILD_READ&#41;;
      dup2&#40;CHILD_WRITE, STDOUT_FILENO&#41;; close&#40;CHILD_WRITE&#41;;
      execvp&#40;cmd, argv&#41;;

      /* Write error code to the lifeline so the parent will know execvp
       * has failed.
       */
      write&#40;lifeline&#91;1&#93;, &errno, sizeof errno&#41;;
      exit&#40;EXIT_FAILURE&#41;;
   &#125;

   /* Monitor the lifeline */
   close&#40;lifeline&#91;1&#93;);
   char buf&#91;10&#93;;
   ssize_t res = read&#40;lifeline&#91;0&#93;, buf, 10&#41;;
   if &#40;res > 0&#41; &#123; /* Received error code from child process */
      close&#40;lifeline&#91;0&#93;);
      close&#40;readpipe&#91;0&#93;);
      close&#40;readpipe&#91;1&#93;);
      close&#40;writepipe&#91;0&#93;);
      close&#40;writepipe&#91;1&#93;);
      goto done;
   &#125;
   close&#40;lifeline&#91;0&#93;);

   /* Set up parent end of the pipe */
	close&#40;CHILD_READ&#41;;
	close&#40;CHILD_WRITE&#41;;

   FILE *in, *out;
   if (!&#40;in=fdopen&#40;PARENT_READ,"r"))) &#123;
      close&#40;PARENT_READ&#41;;
      close&#40;PARENT_WRITE&#41;;
      goto done;
   &#125;
   if (!&#40;out=fdopen&#40;PARENT_WRITE,"w"))) &#123;
      fclose&#40;out&#41;;
      close&#40;PARENT_WRITE&#41;;
      goto done;
   &#125;

   /* Turn off buffering for pipes */
   setvbuf&#40;in,  NULL, _IONBF, 0&#41;;
   setvbuf&#40;out, NULL, _IONBF, 0&#41;;

   p2 = malloc&#40;sizeof *p2&#41;;

   p2->in_fd  = PARENT_READ;
   p2->in     = in;
   p2->out_fd = PARENT_WRITE;
   p2->out    = out;
   p2->pid    = child_pid;

done&#58;
   return p2;
&#125;

int p2close&#40;pipe2_t *pipe&#41;
&#123;
   if &#40;pipe&#41; &#123;
      int res1 = fclose&#40;pipe->in&#41;;
      int res2 = fclose&#40;pipe->out&#41;;
      if &#40;res1==EOF || res2==EOF&#41; return EOF;

      int status;
      while &#40;waitpid&#40;pipe->pid,&status,0&#41;<0&#41;
         if &#40;errno != EINTR&#41; return EOF;
   &#125;

   return 0;
&#125;

char *p2gets&#40;char *s, size_t n, pipe2_t *pipe&#41;
&#123;
   return fgets&#40;s, n, pipe->in&#41;;
&#125;

static bool input_waiting&#40;FILE *file&#41;
&#123;
   if (!file&#41; return false;

   struct timeval timeout;
   fd_set readfds;

   FD_ZERO&#40;&readfds&#41;;
   FD_SET&#40;fileno&#40;file&#41;, &readfds&#41;;

   /* Set to timeout immediately */
   timeout.tv_sec = 0;
   timeout.tv_usec = 0;
   int ready = select&#40;fileno&#40;file&#41;+1, &readfds, 0, 0, &timeout&#41;;

   return &#40;FD_ISSET&#40;fileno&#40;file&#41;, &readfds&#41;);
&#125;

bool p2_input_waiting&#40;pipe2_t *pipe&#41;
&#123;
   int status;
   if (!pipe&#41; return false;

   if &#40;waitpid&#40;pipe->pid, &status, WNOHANG&#41; != 0&#41; return false;

   return input_waiting&#40;pipe->in&#41;;
&#125;

size_t p2write&#40;pipe2_t *pipe, const void *ptr, size_t size&#41;
&#123;
   return fwrite&#40;ptr, size, 1, pipe->out&#41;;
&#125;
What I'm specifically looking for is Windows implementations for these functions. That way, I don't need to modify any of the other code.
Any suggestions?
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: Help needed for porting to Windows

Post by mar »

I use WinAPI (overlapped IO) plus two threads (one for child stdout and stderr each) with IO completion callbacks (so I get Read callbacks from another thread).

From what I understood your code uses non-blocking polling, it might be possible to do this on Windows, not sure. I was happy to get it working and haven't touched my implementation since that.

Not sure how to test for a child being alive, maybe WaitForSingleObject on child process handle for 0 msec and check result?
I would expect some of the read functions to return error in the case the child has exited.

Anyway if you want I can send you my code (it's a Process wrapper) but it might be sort of overkill (plus old code so might be messy) for your purposes, neverthelless it's guaranteed to work (on Windows),
just that you'd have to dig through it and extract what's important :)

Another option might be to look at what Winboard does. The last option is to choose the hard way and dig through WinAPI docs yourself,
but you'll need a VM for debugging (or maybe just Wine?)
User avatar
Evert
Posts: 2929
Joined: Sat Jan 22, 2011 12:42 am
Location: NL

Re: Help needed for porting to Windows

Post by Evert »

mar wrote:I use WinAPI (overlapped IO) plus two threads (one for child stdout and stderr each) with IO completion callbacks (so I get Read callbacks from another thread).

From what I understood your code uses non-blocking polling, it might be possible to do this on Windows, not sure. I was happy to get it working and haven't touched my implementation since that.
:D
Not sure how to test for a child being alive, maybe WaitForSingleObject on child process handle for 0 msec and check result?
I would expect some of the read functions to return error in the case the child has exited.
I guess it's not so much that I want to know if it's alive as I want to know if it exits/crashes. Otherwise there's the risk of waiting for input that will never come.
Anyway if you want I can send you my code (it's a Process wrapper) but it might be sort of overkill (plus old code so might be messy) for your purposes, neverthelless it's guaranteed to work (on Windows),
just that you'd have to dig through it and extract what's important :)
It could be a start, so sure!
Another option might be to look at what Winboard does.
I tried that, but as far as I can tell, the Windows implementation is completely different in this respect compared to XBoard. It's not function-for-function equivalent.
The last option is to choose the hard way and dig through WinAPI docs yourself,
but you'll need a VM for debugging (or maybe just Wine?)
Ah, I guess testing through Wine could work. That's an idea.
mar
Posts: 2554
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: Help needed for porting to Windows

Post by mar »

Sent you a PM.
jdart
Posts: 4366
Joined: Fri Mar 10, 2006 5:23 am
Location: http://www.arasanchess.org

Re: Help needed for porting to Windows

Post by jdart »

You are not going to find 1-1 equivalents of the POSIX functions on Windows.

For starting, communicating with, and monitoring an engine in a child process under Windows, you can look at what the Arasan GUI does (download the source from http://www.arasanchess.org, look at file engineco.cpp).

--Jon