gui engine communication help needed

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
ilari
Posts: 750
Joined: Mon Mar 27, 2006 7:45 pm
Location: Finland

Re: gui engine communication help needed

Post by ilari »

Pierre Bokma wrote:this code is really nice. My gui starts the enigine and is able to close it. So i take it teh gui can send stuff to the engine. Now i'am trying to figure out how to read from the pipe to get messages fro the engine into the gui

regards
In the EngineProcess class I posted m_outRead is the read pipe and m_inWrite the write pipe. Data is written to a pipe with WriteFile() and read with ReadFile(). In Cute Chess we start a separate thread (PipeReader) for reading from the pipe. We use blocking reads, so a new thread is necessary to keep the GUI responsive while waiting for input.

The reading part could look like this:

Code: Select all

#define CHUNK_SIZE 256
DWORD dwRead = 0;
char buffer[CHUNK_SIZE];

BOOL ok = ReadFile(pipe, buffer, CHUNK_SIZE, &dwRead, 0);
if (!ok || dwRead == 0)
    engine_terminated();
else
    do_something_with_data_in_buffer();
Pierre Bokma
Posts: 31
Joined: Tue Dec 07, 2010 11:19 pm
Location: Holland

Re: gui engine communication help needed

Post by Pierre Bokma »

thank you, you are very nice helping me. I will try this code this weekend and will post my results once i've finished
kinderchocolate
Posts: 454
Joined: Mon Nov 01, 2010 6:55 am
Full name: Ted Wong

Re: gui engine communication help needed

Post by kinderchocolate »

I will give you a christmas present. I have a working GUI which works for Windows and Unix. I post the full source for the Windows piping below. It works, while it might not be perfect it will give you a perfect starting point. Just adjust it for your own needs.

Pay close attention to open(), read() and write(). In open(), I established piping and created a child engine process by calling CreateChildProcess().

Code: Select all

#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#include <assert.h>
#include <iostream>
#include <stdlib.h>
#include <strsafe.h>
#include "Adapter\EngineAdapterWindows.hpp"
#include "Adapter\AbstractEngineAdapter.hpp"

using namespace std;

#define BUFFER_SIZE 4096

class EngineAdapterWindows : public AbstractEngineAdapter
{
    public:
        EngineAdapterWindows(std::string command);
        ~EngineAdapterWindows();

        /* --------------- AbstractAdapter --------------- */
  
        // Inherited from AbstractAdapter
        void write(const std::string& line);  

        // Inherited from AbstractAdapter
        void open();  

        // Inherited from AbstractAdapter
        void close();

        // Inherited from AbstractAdapter
        std::string read();

    private:
        std::string _command;

        // Internal buffer for IO
        CHAR buffer[BUFFER_SIZE];

        // STDIN redirection read handle
        HANDLE hChildStd_IN_Rd;

        // STDIN redirection write handle
        HANDLE hChildStd_IN_Wr;

        // STDOUT redirection read handle
        HANDLE hChildStd_OUT_Rd;

        // STDOUT redirection write handle
        HANDLE hChildStd_OUT_Wr;

        // Adapter handle
        HANDLE adapterHandle;

        PROCESS_INFORMATION engineProcInfo;
};


/* ---------------------------------- Member Methods ---------------------------------- */


EngineAdapterWindows::EngineAdapterWindows(string command) : _command(command)
{
}


EngineAdapterWindows::~EngineAdapterWindows()
{
}


DWORD WINAPI createWindowsAdapter(LPVOID lpParam)
{
  ((EngineAdapterWindows *) lpParam)->createAdapater(); 
  return TRUE;
}


void ErrorExit(PTSTR lpszFunction)
{
    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);
}

BOOL WriteToFile(LPCSTR name, CHAR *buffer, const int length)
{
    DWORD dwWritten;
    HANDLE file = CreateFile(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (file == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    if (!WriteFile(file, buffer,  length,  &dwWritten, NULL))
    {
        return FALSE;
    }

    if (dwWritten != length)
    {
        return FALSE;
    }

    CloseHandle(file);   
    return TRUE;
}


/*
 * Read a line from the engine's process pipe.
 */
BOOL ReadLine(CHAR *buffer, const int size, HANDLE hChildStd_OUT_Rd)
{
    DWORD dwRead;
    string lines;
    
    while (true)
    {
        if (!ReadFile(hChildStd_OUT_Rd, buffer, size, &dwRead, NULL))
        {
            int error  = GetLastError();
            return FALSE;
        }

        if (dwRead == 0)
        {
            return FALSE;
        }
        else
        {
            bool shouldBreak = false;

            // Break if one of the newline character received
            if (buffer[dwRead - 1] == '\r' || buffer[dwRead - 1] == '\n')
            {        
                buffer[dwRead - 1] = '\0';
                shouldBreak = true;
            }
            else
            {
                // Assume no buffer-overflow
                buffer[dwRead] = '\0';
            }

            // Append the new buffer to the previous
            lines = lines + string(buffer);

            if (shouldBreak)
            {
                break;
            }
        }
    }

    strcpy(buffer, lines.c_str());

    CHAR *p = buffer;

    // In Windows, newline character is "\r\n", remove them to ensure platform indpendence
    for (CHAR *q = buffer; *q != '\0'; q++)
    {
        if (*q != '\r')
        {
            *(p++) = *q;
        }
    }

    *p = '\0';

    return TRUE;
}


void CreateChildProcess(PROCESS_INFORMATION &engineProcInfo, LPCSTR command, HANDLE hChildStd_OUT_Wr, HANDLE hChildStd_IN_Rd)
{
    STARTUPINFO siStartInfo;

    // Set up members of the PROCESS_INFORMATION structure.
    ZeroMemory(&engineProcInfo, sizeof(PROCESS_INFORMATION));

    // Specify the STDIN and STDOUT handles for redirection.
    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb         = sizeof(STARTUPINFO);
    siStartInfo.hStdError  = hChildStd_OUT_Wr;
    siStartInfo.hStdOutput = hChildStd_OUT_Wr;
    siStartInfo.hStdInput  = hChildStd_IN_Rd;
    siStartInfo.dwFlags   |= STARTF_USESTDHANDLES;

    LPSTR tempCommand = strdup(command);

    // Create the child process.
    if (!CreateProcess(NULL,
                       tempCommand,      // 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
                       &engineProcInfo)) // Receives PROCESS_INFORMATION
    {
        free(tempCommand);
        ErrorExit(TEXT("CreateProcess"));
    }

    free(tempCommand);
}


/*
 * Establish inter-process connection to engine.
 */
void EngineAdapterWindows::open()
{
    SECURITY_ATTRIBUTES saAttr;

    // Set the bInheritHandle flag so pipe handles are inherited.

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    // Create a pipe for the child process's STDOUT.
    if (!CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0))
    {
        ErrorExit(TEXT("StdoutRd CreatePipe"));
    }

    // Ensure the read handle to the pipe for STDOUT is not inherited.
    if (!SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
    {
        ErrorExit(TEXT("Stdout SetHandleInformation"));
    }

    // Create a pipe for the child process's STDIN.
    if (!CreatePipe(&hChildStd_IN_Rd, &hChildStd_IN_Wr, &saAttr, 0))
    {
        ErrorExit(TEXT("Stdin CreatePipe"));
    }

    // Ensure the write handle to the pipe for STDIN is not inherited.
    if (!SetHandleInformation(hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
    {
        ErrorExit(TEXT("Stdin SetHandleInformation"));
    }

    CreateChildProcess(engineProcInfo, _command.c_str(), hChildStd_OUT_Wr, hChildStd_IN_Rd);

    // Sleep for 200ms so that the engine could initialize itself
    Sleep(200);

    if ((adapterHandle = CreateThread(NULL, 0, &createWindowsAdapter, this, 0, NULL)) == NULL)
    {
        ErrorExit(TEXT("Failed on creating an adapter thread"));
    }

    _state = ACTIVE;
}


void EngineAdapterWindows::close()
{
    if (_state != TERMINATED && _state != INACTIVE)
    {
        _state = TERMINATED;

        assert(TerminateProcess(engineProcInfo.hProcess, 0));
        assert(TerminateThread(adapterHandle, 0) == TRUE);

        assert(CloseHandle(hChildStd_OUT_Rd));
        assert(CloseHandle(hChildStd_OUT_Wr));
        assert(CloseHandle(hChildStd_IN_Rd));
        assert(CloseHandle(hChildStd_IN_Wr));

        assert(CloseHandle(engineProcInfo.hProcess));
        assert(CloseHandle(engineProcInfo.hThread));

        _receiver = NULL;
    }
}


string EngineAdapterWindows::read()
{
    if (!ReadLine(buffer, BUFFER_SIZE, hChildStd_OUT_Rd))
    {
        throw "ReadLine() failed";
    }

    return string(buffer);
}


void EngineAdapterWindows::write(const string& command)
{
    string commandLine(command + "\n");

    DWORD dwWritten;

    if (!WriteFile(hChildStd_IN_Wr, commandLine.c_str(), command.size() + 1, &dwWritten, NULL))
    {
        throw "Failed to write " + command + " in EngineAdapterWindows::write()";
    }
}

VirtualAdapter * createAdapterWindows(std::string command)
{
    return new EngineAdapterWindows(command);
}

[/code][/quote]
User avatar
ilari
Posts: 750
Joined: Mon Mar 27, 2006 7:45 pm
Location: Finland

Re: gui engine communication help needed

Post by ilari »

kinderchocolate wrote:I will give you a christmas present. I have a working GUI which works for Windows and Unix. I post the full source for the Windows piping below. It works, while it might not be perfect it will give you a perfect starting point. Just adjust it for your own needs.
Nice code, but I see a couple of problems:

- I think you should close the child process' ends of the pipes right after CreateProcess(). If you don't, ReadFile() may block forever when the child process terminates.

- assert() shouldn't be used like this:

Code: Select all

assert(TerminateProcess(engineProcInfo.hProcess, 0)); 
assert(TerminateThread(adapterHandle, 0) == TRUE); 

assert(CloseHandle(hChildStd_OUT_Rd)); 
assert(CloseHandle(hChildStd_OUT_Wr)); 
assert(CloseHandle(hChildStd_IN_Rd)); 
assert(CloseHandle(hChildStd_IN_Wr)); 

assert(CloseHandle(engineProcInfo.hProcess)); 
assert(CloseHandle(engineProcInfo.hThread));
If the above code is compiled with NDEBUG, then none of it is executed.
Pierre Bokma
Posts: 31
Joined: Tue Dec 07, 2010 11:19 pm
Location: Holland

Re: gui engine communication help needed

Post by Pierre Bokma »

thanks ! i will try the code this weekend


Pierre
Pierre Bokma
Posts: 31
Joined: Tue Dec 07, 2010 11:19 pm
Location: Holland

Re: gui engine communication help needed

Post by Pierre Bokma »

Hi friends,

With only two evenings of programming i have my gui up and running. It was not difficult if you know what to do. The difficult part is to get to know what to do but thanks to your magnificant help this part was also difficult.

thanks again!!!!

Pierre
User avatar
hgm
Posts: 28389
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: gui engine communication help needed

Post by hgm »

Pierre Bokma wrote:In order to boost my activities in developing my chess engine i would like to write my own chess gui.
Why does it "boost" the development of your engine? It seems to me it just distracts you from it. In my experience developing a GUI takes about 10 times asmuch effort as developing an engine, and developing a good one even 100 times. All that effort could have gone in developing a dozen of engines if you would use one of the readily available GUIs.
Pierre Bokma
Posts: 31
Joined: Tue Dec 07, 2010 11:19 pm
Location: Holland

Re: gui engine communication help needed

Post by Pierre Bokma »

Dear Harm,

That depends on what you want the gui to do. I want a very simple gui that can play engine-engine matches,walk trough a epd test file and display specific information about the evulation of the possition. Nothing fancy really more like a tarrash gui extended for personal use.

vriendelijke groeten

Pierre