-
Oct 10th, 2019, 08:17 AM
#1
Thread Starter
Junior Member
Details on How to Use Named Pipe for Communicating with Chess Engine
I'm looking to communicate my VB6 program with the Stockfish (UCI) chess engine. I found the following code which I saved into a module called "ConsolePipe.bas".
Code:
Option Explicit
Private Declare Function CreatePipe Lib "kernel32" (phReadPipe As Long, phWritePipe As Long, lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Long) As Long
Private Declare Sub GetStartupInfo Lib "kernel32" Alias "GetStartupInfoA" (lpStartupInfo As STARTUPINFO)
Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, lpProcessAttributes As Any, lpThreadAttributes As Any, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, lpOverlapped As Any) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As Long
lpDesktop As Long
lpTitle As Long
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Byte
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type OVERLAPPED
ternal As Long
ternalHigh As Long
offset As Long
OffsetHigh As Long
hEvent As Long
End Type
Private Const STARTF_USESHOWWINDOW = &H1
Private Const STARTF_USESTDHANDLES = &H100
Private Const SW_HIDE = 0
Private Const EM_SETSEL = &HB1
Private Const EM_REPLACESEL = &HC2
Private Sub Command1_Click()
Command1.Enabled = False
Redirect Text1.Text, Text2
Command1.Enabled = True
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
If Command1.Enabled = False Then Cancel = True
End Sub
Sub Redirect(CmdLine As String, objTarget As Object)
Dim i%, t$
Dim pa As SECURITY_ATTRIBUTES
Dim pra As SECURITY_ATTRIBUTES
Dim tra As SECURITY_ATTRIBUTES
Dim pi As PROCESS_INFORMATION
Dim sui As STARTUPINFO
Dim hRead As Long
Dim hWrite As Long
Dim bRead As Long
Dim lpBuffer(1024) As Byte
pa.nLength = Len(pa)
pa.lpSecurityDescriptor = 0
pa.bInheritHandle = True
pra.nLength = Len(pra)
tra.nLength = Len(tra)
If CreatePipe(hRead, hWrite, pa, 0) <> 0 Then
sui.cb = Len(sui)
GetStartupInfo sui
sui.hStdOutput = hWrite
sui.hStdError = hWrite
sui.dwFlags = STARTF_USESHOWWINDOW Or STARTF_USESTDHANDLES
sui.wShowWindow = SW_HIDE
If CreateProcess(vbNullString, CmdLine, pra, tra, True, 0, 0, vbNullString, sui, pi) <> 0 Then
SetWindowText objTarget.hwnd, ""
Do
Erase lpBuffer()
If ReadFile(hRead, lpBuffer(0), 1023, bRead, ByVal 0&) Then
SendMessage objTarget.hwnd, EM_SETSEL, -1, 0
SendMessage objTarget.hwnd, EM_REPLACESEL, False, lpBuffer(0)
DoEvents
Else
CloseHandle pi.hThread
CloseHandle pi.hProcess
Exit Do
End If
CloseHandle hWrite
Loop
CloseHandle hRead
End If
End If
End Sub
What I need to do is:
1. Call this program which operates from the Windows 10 command prompt. This can be done using: Shell "stockfish.exe".
2. Once it starts, automatically enter "fen position etc. etc. etc." and then ENTER (carriage return).
3. Then automatically enter "depth 30 movetime 15000" and then ENTER (carriage return).
4. Wait 15 seconds.
5. Read the output from Stockfish which appears in the console.
6. Close the command prompt window. Being able to make it invisible right from the beginning is the main intention, actually.
The trouble is:
1. I don't know EXACTLY what to call from the module above and EXACTLY how to call it. If someone could show me in actual code with the actual words (e.g. "stockfish.exe" "fen position etc.") that would be a big help. I've tried messing around with the contents of "Command1_Click" above but nothing seems to work. I'm totally new to this piping business. Thanks in advance.
-
Oct 10th, 2019, 11:12 AM
#2
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Sounds like you are talking about anonymous pipes, a separate but related topic.
Maybe look at VB6 - ShellPipe "Shell with I/O Redirection" control
-
Oct 10th, 2019, 07:03 PM
#3
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
That doesn't work for me either. I just need to do Steps 1-6 above.
-
Oct 11th, 2019, 04:56 AM
#4
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
There is no "fen position etc. etc. etc." command. There is no "depth 30 movetime 15000" command too. You are making it hard for anyone to help.
Here is the Stockfish 10 for Windows console app: https://stockfishchess.org/download/
Here is UCI protocol chess engines are using: http://wbec-ridderkerk.nl/html/UCIProtocol.html
Real commands:
position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
go depth 30 movetime 15000
cheers,
</wqw>
-
Oct 11th, 2019, 05:41 AM
#5
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Yes, I know. The question was, how do I get something like "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" and then "go depth 30 movetime 15000" into the command window (with Stockfish running) using VB6? Right now, all I can do is start stockfish.exe. That's it. Nothing else can be passed to it after that.
-
Oct 11th, 2019, 08:43 AM
#6
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Well, I'm not going to figure out how to do everything you want to do, but as a minimum, I figured I would give dilettante's code a quick test.
Turns out I had downloaded it already sometime in the past.
I downloaded a copy of stockfishchess from the link wqweto provided.
As a quick test, I figured I would do a minimal change to dilettante's code. I wanted to try sending the commands more than once, so I added another button to the form, and I commented out dilettante's test code, and didn't close the Standard Pipe object after opening it.
I tested dilettante's example before making any changes to ensure it ran unaltered, so made some assumptions that it would continue to work after my changes.
So, the modifications were.
Note: When I extracted stockfish, I got and extra directory level and didn't move it up before running the test, so the path is longer than necessary as I have the same directory twice...
In the cmdGo button's click event, change the SP.Run line to run stockfish instead of the vbscript.
Also, commented out dil's test and didn't close the pipe.
Code:
SPResult = SP.Run("c:\c\tmp\stockfish-10-win\stockfish-10-win\Windows\stockfish_10_x64.exe")
Select Case SPResult
Case SP_SUCCESS
' Do Until EOF(1)
' Line Input #1, TextLine
' SP.SendLine TextLine
' Loop
'
' SP.ClosePipe
Then, to watch the return data interactively, change the Print #2 to a Debug.Print, so could watch the response in the Immediate window.
Code:
Private Sub SP_DataArrival(ByVal CharsTotal As Long)
With SP
Do While .HasLine
' Print #2, .GetLine()
Debug.Print .GetLine()
Loop
End With
End Sub
As I said, I added a command button to test the strings wqweto provided to send to the engine.
Code:
Private Sub Command1_Click()
Dim TextLine As String
TextLine = "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
SP.SendLine TextLine
TextLine = "go depth 30 movetime 15000"
SP.SendLine TextLine
End Sub
And that was it.
I started the program, and hit the Go button.
I didn't get an error, so it started the chess engine without issue.
I pressed the Command1 button, and the immediate window showed the chess engine spewing out its evaluations and eventually printing the line "bestmove e2e4 ponder e7e6".
After the "bestmove" was shown , I pressed the common button again, to verify it would repeat and process the input.
That is about a far as I will go, and it appears to work reasonably well to me.
Last edited by passel; Oct 11th, 2019 at 08:51 AM.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
-
Oct 12th, 2019, 03:03 AM
#7
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Okay, thanks. There appears to be some progress. I've adapted the material above to the following function and sub in my program.
Code:
Public Function Invoke_UCI_Engine(engine_path As String) As String
Dim SPResult As SP_RESULTS
Dim TextLine As String
On Error Resume Next
SPResult = SP.Run(engine_path)
Do
DoEvents
Loop Until SPResult = SP_SUCCESS
TextLine = "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
SP.SendLine TextLine
TextLine = "go depth 30 movetime 15000"
SP.SendLine TextLine
End Function
Code:
Private Sub SP_DataArrival(ByVal CharsTotal As Long)
With SP
Do While .HasLine
Engine_Analysis = Engine_Analysis & .GetLine() & vbNewLine
Loop
End With
End Sub
The trouble is, when I replace "Debug.Print .GetLine()" with "Engine_Analysis = Engine_Analysis & .GetLine() & vbNewLine", nothing is being stored in the Engine_Analysis global variable. The lines only appear in the Immediate window if I use "Debug.Print .GetLine()". Is there no way to capture what appears in the Immediate (debug) window so I use the information elsewhere in my program?
-
Oct 12th, 2019, 02:54 PM
#8
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
FWIW - here's a demo-snippet, which makes use of a "Process-Object" (created by "Shell-Exec"),
and which implements StdIn, StdOut and StdErr-Objects (all 3 implementing the Interface of the TextStream-Object).
So, normally no API-calls are needed - the APIs I'm using in the snippet below,
are only to "hide the Console-Window" after starting-up the StockFish-Process via Shell.Exec(...).
(sadly the Shell.Exec-Command does not support "visibility-args" for the shelled process)
Code:
Option Explicit
Private Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal Cmd As Long) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, PID As Long) As Long
Private Declare Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal Cmd As Long) As Long
Private oP As Object
Private Sub Form_Load()
Set oP = CreateObject("WScript.Shell").Exec(App.Path & "\stockfish_10_x64.exe")
SendCommand "" 'initialize the engine
HideProcess oP, Me.hWnd
End Sub
Private Sub Form_Click() 'the command-call-examples here, all behave synchronous
Caption = "Command-Sequence started..."
SendCommand "uci", "uciok"
SendCommand "ucinewgame"
SendCommand "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
SendCommand "go depth 30 movetime 15000", "bestmove" '<- this can take a while, until the call returns...
Caption = "Command-Sequence finished!"
End Sub
Private Function SendCommand(sCmd As String, Optional sWaitFor$ = "readyok") As String
If oP.Status = 0 And Len(sCmd) > 0 Then oP.StdIn.WriteLine sCmd
If oP.Status = 0 And sWaitFor = "readyok" Then oP.StdIn.WriteLine "isready"
Dim sLine As String
Do Until oP.Status Or InStr(sLine, sWaitFor) = 1
sLine = oP.StdOut.ReadLine
SendCommand = SendCommand & sLine & vbCrLf
If Len(sCmd) Then ResponseLineCallback sCmd, sLine
Loop
End Function
Private Sub ResponseLineCallback(sCmd As String, sLine As String)
Debug.Print Split(sCmd, " ")(0), sLine
DoEvents
End Sub
'just a helper, to hide the (by default started "non-hidden") StockFish-Process-ConsoleWindow
Private Function HideProcess(oP As Object, ByVal hWnd As Long) As Long
hWnd = GetWindow(hWnd, 0) 'first sibling
Do While hWnd
GetWindowThreadProcessId hWnd, HideProcess
If HideProcess = oP.ProcessID Then ShowWindow hWnd, 0: Exit Do
hWnd = GetWindow(hWnd, 2) 'next sibling
Loop
End Function
HTH
Olaf
-
Oct 12th, 2019, 03:19 PM
#9
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
That's an option but using the scripting library like that is a little clunky.
First I'd avoid using it late-bound. There is no advantage in doing so and you sacrifice both Intellisense and the ability to look things up in the Object Browser. Those aren't major things but there isn't anything to be gained by sacrificing those programming aids. Early binding gets you performance but nothing that matters at all here. It's just a good habit to always use references and early binding except when you can't due to binary compatibility concerns.
Second though all you have are blocking calls for the standard I/O streams. That works as long as the interaction is all lock-stepped "give then take" and you don't care about blocking your program's user interface thread.
The ShellPipe control is far from perfect. It has to use Timer-driven polling and the dread DoEvents to accomplish async operation. Without a background thread it doesn't get much better than that. If I just used blocking calls the code could be radically simplified.
I always wondered why we never got a Microsoft OCX for anonymous and Named pipes. I used to use a proprietary Named Pipes OCX from a 3rd party that was actually written for them by Microsoft, so I know it can be done. Sadly neither of them ever released it for general use.
But at least now he has two options to choose from.
-
Oct 12th, 2019, 05:11 PM
#10
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Here is a 3-rd option: cExec.zip
This class allows to start the console app hidden and w/ limit flags like JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. The latter one marks the child process to be terminated when the parent is terminated (if child is still running that is) in order not to leave orphaned (hidden) stockfish processes when your main app is getting closed or crashing. The class allows basic polling on output/error and writing to child process input too so it's not events based.
I've been using this class to start (hidden) putty.exe in order to open SSH tunnels to some DB servers.
cheers,
</wqw>
Last edited by wqweto; Oct 13th, 2019 at 03:03 AM.
-
Oct 12th, 2019, 06:22 PM
#11
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by dilettante
That's an option but using the scripting library like that is a little clunky.
Well, IMO it's a (very robust) working example, which uses LateBinding on the used "Process-encapsulating-Object" -
for the sake of "easy-Copy&Paste-ness" (into an empty VB-Form-Project, which has the ChessEngine-executable in its App.Path)
So, to make this example "non-clunky", one simply has to change the declaration of the sole Private Variable:
Private oP As IWshRuntimeLibrary.WshExec '<- formerly As Object
Originally Posted by dilettante
Second though all you have are blocking calls for the standard I/O streams...
Quite deliberately so (for this "uci-scenario")...
Because "callig into the exe" is then exactly as one would expect when working against most StdDll-Binaries:
Synchronous! ... (DoEvents - albeit contained in the little "LineCallback"-routine - not really needed).
Olaf
-
Oct 13th, 2019, 12:03 AM
#12
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Schmidt,
Thanks. Your code works a lot better for me. The earlier one, actually, wasn't even getting full output from the UCI engine. For instance, in positions like this: "1r2b3/2p5/3kB2r/K7/p3QP2/8/6P1/8 w - - 0 1", it was getting stuck at depth 9 to 12, never actually producing the best move. I'm not sure why. Your code seems to handle everything. I had to modify it as follows, however, to actually work within my own program.
Code:
Public Function Invoke_UCI_Engine(engine_path As String, the_fen As String, the_depth As Integer, move_time As Double) As String
Engine_Analysis = vbNullString
Set oP = CreateObject("WScript.Shell").Exec(engine_path)
SendCommand "" 'initialize the engine
HideProcess oP, Me.hWnd
Call SendCommand("position fen " & the_fen)
Call SendCommand("go depth " & the_depth & " movetime " & move_time, engine_path, "bestmove") '<- this can take a while, until the call returns...
Call Delay_in_Sec((move_time + 2000) / 1000)
Invoke_UCI_Engine = Engine_Analysis
End Function
I had to add a delay (a couple of seconds longer than the move time) otherwise the information won't register in the "Engine_Analysis" global string variable.
Code:
Private Function SendCommand(sCmd As String, Optional engine_path As String, Optional sWaitFor$ = "readyok") As String
Dim sLine As String
If oP.Status = 0 And Len(sCmd) > 0 Then oP.StdIn.WriteLine sCmd
If oP.Status = 0 And sWaitFor = "readyok" Then oP.StdIn.WriteLine "isready"
Do Until oP.Status Or InStr(sLine, sWaitFor) = 1
sLine = oP.StdOut.ReadLine
SendCommand = SendCommand & sLine & vbCrLf
If Len(sCmd) Then ResponseLineCallback sCmd, sLine
Loop
If engine_path <> vbNullString Then
Shell "taskkill.exe /s " & ComputerName & " /u " & Environ("USERDOMAIN") & "\" & Environ("USERNAME") & " /t /im " & FileNamefromPath(engine_path) & " /f", _
vbHide
End If
If I don't "kill" the UCI engine after each invocation, I can't enter a new position to analyze (it won't work).
Code:
Private Sub ResponseLineCallback(sCmd As String, sLine As String)
If InStr(sLine, "readyok") = 0 Then
Engine_Analysis = Engine_Analysis & sLine & vbNewLine
End If
DoEvents
End Sub
I also had to ensure I only got the analysis lines in the Engine_Analysis string and not all the "readyok" stuff that comes out too.
Code:
Private Function HideProcess(oP As Object, ByVal hWnd As Long) As Long
hWnd = GetWindow(hWnd, 0) 'first sibling
Do While hWnd
GetWindowThreadProcessId hWnd, HideProcess
If HideProcess = oP.ProcessID Then ShowWindow hWnd, 0: Exit Do
hWnd = GetWindow(hWnd, 2) 'next sibling
Loop
End Function
I have a question for about this last segment, though. Is there any way to hide the UCI engine window completely? Now, it appears as a distracting pop-up and disappears almost instantly but it's still distracting (as I have to call it many times). Also, I found that, on occasion, it minimized my main program instead of the UCI engine command window. Being able to hide it completely would be fantastic. I've actually seen this done in Python. It makes a call to the engine and gets its output without any window appearing at all. Thanks again.
wqweto,
I imported your 'cExec.zip' class but have no idea how to use it. Could you provide some examples? Like starting the UCI engine, sending it a test position and getting the output into a string? Thanks.
-
Oct 13th, 2019, 03:54 AM
#13
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by victor_knight
Could you provide some examples? Like starting the UCI engine, sending it a test position and getting the output into a string?
IMO you had to provide these test samples in OP, not waiting for Olaf to research the UCI protocol commands w/ expected successful response that stockfish outputs. We are missing the failure responses documented and that is why currently SendCommand can hang on unexpected input which is something that has to be taken care of before completing this project, probably.
cheers,
</wqw>
-
Oct 14th, 2019, 05:29 AM
#14
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
What about with this part?
Is there any way to hide the UCI engine window completely?
Do you know how to modify the following code so there is no pop-up (even for a second)? I would like command window to be completely invisible since I call it many times in my program.
Code:
Private Function HideProcess(oP As Object, ByVal hWnd As Long) As Long
hWnd = GetWindow(hWnd, 0) 'first sibling
Do While hWnd
GetWindowThreadProcessId hWnd, HideProcess
If HideProcess = oP.ProcessID Then ShowWindow hWnd, 0: Exit Do
hWnd = GetWindow(hWnd, 2) 'next sibling
Loop
End Function
-
Oct 14th, 2019, 06:53 AM
#15
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by victor_knight
Do you know how to modify the following code so there is no pop-up (even for a second)?
There is no way to pass SW_HIDE to WshExec from scripting runtime.
This is already implemented sanely in cExec class w/ StartHidden parameter of Run method.
cheers,
</wqw>
-
Oct 14th, 2019, 08:09 AM
#16
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
There is no way to pass SW_HIDE to WshExec from scripting runtime.
And there is apparently no way to send and receive information from the UCI engine otherwise. A catch-22.
-
Oct 14th, 2019, 09:01 AM
#17
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by victor_knight
And there is apparently no way to send and receive information from the UCI engine otherwise. A catch-22.
Or you can build WshExec alternative yourself by using cExec class to borrow ideas. . .
JFYI, the pipes used throughout this topic are *anonymous* (not named) which get *redirected* (might help w/ google searches).
cheers,
</wqw>
-
Oct 14th, 2019, 07:16 PM
#18
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Couldn't find anything much online about how to do that, exactly. Perhaps I'll look into Python; write a simple piping EXE in that (which doesn't cause the UCI engine window to pop-up at all) and then simply call it from my VB6 program. Not very elegant, but what the hell. It should work.
-
Oct 15th, 2019, 06:57 AM
#19
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by victor_knight
Okay, thanks. There appears to be some progress. I've adapted the material above to the following function and sub in my program.
Code:
Public Function Invoke_UCI_Engine(engine_path As String) As String
Dim SPResult As SP_RESULTS
Dim TextLine As String
On Error Resume Next
SPResult = SP.Run(engine_path)
Do
DoEvents
Loop Until SPResult = SP_SUCCESS
TextLine = "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
SP.SendLine TextLine
TextLine = "go depth 30 movetime 15000"
SP.SendLine TextLine
End Function
Code:
Private Sub SP_DataArrival(ByVal CharsTotal As Long)
With SP
Do While .HasLine
Engine_Analysis = Engine_Analysis & .GetLine() & vbNewLine
Loop
End With
End Sub
The trouble is, when I replace "Debug.Print .GetLine()" with "Engine_Analysis = Engine_Analysis & .GetLine() & vbNewLine", nothing is being stored in the Engine_Analysis global variable. The lines only appear in the Immediate window if I use "Debug.Print .GetLine()". Is there no way to capture what appears in the Immediate (debug) window so I use the information elsewhere in my program?
Worked fine for me. Who knows at what point you clear the Engine_Analysis global variable, and if you've waited long enough. Do you really need all the analysis, or just the bestmove and ponder line?
In any case, I just changed the code to add a variable, and to verify that it wasn't taking too long, looked for the "bestmove" line and timed how much time was spent processing. Here is the output I was getting with a few presses of command1. Of course, I waited until I got a response before pressing command1 again. Code to disable Command1 and reenable it again after receiving the bestmove could be added, but shouldn't be necessary for a quick test if the tester is disciplined.
Code:
bestmove e2e4 ponder e7e6 took 15.03739 seconds to compute
bestmove e2e4 ponder e7e6 took 15.06038 seconds to compute
bestmove d2d4 ponder g8f6 took 15.05502 seconds to compute
The additional code modifications I made to my previous post.
Code:
'added a couple of Form scope variables
Private Engine_Analysis As String
Private TimeToProcess As Single
'I also commented out the opening of file #1 in the cmdGo_Click Sub.
'Not sure why it wasn't commented out before, but didn't find the file when I ran it this time, so I commented it out as it isn't needed.
'The Command1_Click() sub now looks like this.
'I clear the Engine_Analysis string, and note the StartTime
'(realize this will not give a correct time if you compute through midnight, I didn't bother compensating for this simple test)
Private Sub Command1_Click()
Dim TextLine As String
Engine_Analysis = ""
TextLine = "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
SP.SendLine TextLine
TextLine = "go depth 30 movetime 15000"
SP.SendLine TextLine
TimeToProcess = Timer
End Sub
'The SP_DataArrival sub was modified to update the Engine_Analysis string with the full output of the run,
'and the Debug output changed to just reflect when the best move was returned, and how much wall time, in seconds, had passed.
Private Sub SP_DataArrival(ByVal CharsTotal As Long)
Dim LastLine As String
With SP
Do While .HasLine
' Print #2, .GetLine()
' Debug.Print .GetLine()
LastLine = .GetLine()
Engine_Analysis = Engine_Analysis & LastLine & vbNewLine
Loop
End With
If InStr(LastLine, "bestmove") <> 0 Then
TimeToProcess = Timer - TimeToProcess
Debug.Print LastLine, "took "; TimeToProcess; " seconds to compute"
End If
End Sub
I'm not saying that any one of the three methods is preferable to the other, I don't have any vested interest in the task, but I certainly didn't see any reason that you couldn't capture the text in the Engine_Analysis string (although I do worry about a lot of string concatenation, but the elapsed time shows that it is apparently not an issue here. You ask the engine to take 15 seconds, and it takes about 15 seconds).
So, while I ignored it for a couple of days, I decided to test setting Engine_Analysis for myself to verify there should be no issue. Since we didn't have the discerning parts of your code (where all and when you touch Engine_Analysis), I won't try to guess how your code went wrong.
p.s. After the bestmove line was printed to the Immediate window the third time, I did Pause the program and typed
? TimeToProcess
in the Immediate window to print the contents of the TimeToProcess string to verify the string was indeed being filled with all the output of the process.
Last edited by passel; Oct 15th, 2019 at 07:16 AM.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
-
Oct 15th, 2019, 09:02 AM
#20
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Btw, here is Olaf's sample translated to cExec class w/ StartHidden:=True does not show engine's console.
thinBasic Code:
Option Explicit
Private oP As cExec
Private Sub Form_Load()
Set oP = New cExec
oP.Run App.Path & "\stockfish_10_x64.exe", vbNullString, StartHidden:=True
SendCommand "" 'initialize the engine
End Sub
Private Sub Form_Click() 'the command-call-examples here, all behave synchronous
Caption = "Command-Sequence started..."
SendCommand "uci", "uciok"
SendCommand "ucinewgame"
SendCommand "position fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
SendCommand "go depth 30 movetime 15000", "bestmove" '<- this can take a while, until the call returns...
Caption = "Command-Sequence finished!"
End Sub
Private Function SendCommand(sCmd As String, Optional sWaitFor$ = "readyok") As String
Dim sLine As String
If Not oP.AtEndOfOutput And Len(sCmd) > 0 Then
oP.WriteInput sCmd & vbCrLf
End If
If Not oP.AtEndOfOutput And sWaitFor = "readyok" Then
oP.WriteInput "isready" & vbCrLf
End If
Do While Not oP.AtEndOfOutput And InStr(sLine, sWaitFor) = 0
sLine = oP.ReadLineOutput
SendCommand = SendCommand & sLine
If LenB(sCmd) <> 0 Then
ResponseLineCallback sCmd, sLine
End If
Loop
End Function
Private Sub ResponseLineCallback(sCmd As String, sLine As String)
Debug.Print Split(sCmd, " ")(0), Split(sLine, vbCrLf)(0)
DoEvents
End Sub
cheers,
</wqw>
-
Oct 15th, 2019, 11:16 PM
#21
Thread Starter
Junior Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
wqweto,
Thanks a lot. That works perfectly.
-
Oct 16th, 2019, 05:12 AM
#22
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Btw you can use LimitFlags to instruct OS always to terminate stockfish child processes when you app exits (even if it crashes) like this
thinBasic Code:
Private Sub Form_Load()
'-- note: uses CreateJobObject and SetInformationJobObject so that closing the last
'-- job object handle terminates all associated processes (all child's
'-- spawned sub-processes become part of the job too by default)
Const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE As Long = &H2000
oP.Run App.Path & "\stockfish_10_x64.exe", vbNullString, StartHidden:=True, _
LimitFlags:=JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
End Sub
cheers,
</wqw>
-
May 4th, 2020, 10:04 PM
#23
Fanatic Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by wqweto
Here is a 3-rd option: cExec.zip
This class allows to start the console app hidden and w/ limit flags like JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. The latter one marks the child process to be terminated when the parent is terminated (if child is still running that is) in order not to leave orphaned (hidden) stockfish processes when your main app is getting closed or crashing. The class allows basic polling on output/error and writing to child process input too so it's not events based.
I've been using this class to start (hidden) putty.exe in order to open SSH tunnels to some DB servers.
cheers,
</wqw>
wqw .i found your cls have a bug 'Endless loop '
can fix
Code:
Public Function AtEndOfOutput() As Boolean
Dim lTotal2 As Long
If m_hReadOutput <> 0 Then
Sleep 100
If PeekNamedPipe(m_hReadOutput, ByVal 0, 0, 0, lTotal2, 0) = 0 Then
Call CloseHandle(m_hReadOutput)
m_hReadOutput = 0
AtEndOfOutput = (m_hReadOutput = 0)
End If
End If
m_buff = lTotal2
If lTotal2 = 0 Then AtEndOfOutput = True
End Function
Last edited by xxdoc123; May 5th, 2020 at 12:01 AM.
-
May 4th, 2020, 10:14 PM
#24
Fanatic Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by wqweto
Here is a 3-rd option: cExec.zip
This class allows to start the console app hidden and w/ limit flags like JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. The latter one marks the child process to be terminated when the parent is terminated (if child is still running that is) in order not to leave orphaned (hidden) stockfish processes when your main app is getting closed or crashing. The class allows basic polling on output/error and writing to child process input too so it's not events based.
I've been using this class to start (hidden) putty.exe in order to open SSH tunnels to some DB servers.
cheers,
</wqw>
wqw .i found your cls have a bug 'Endless loop '
can fix
Code:
Public Function AtEndOfOutput() As Boolean
Dim lTotal2 As Long
If m_hReadOutput <> 0 Then
Sleep 100
If PeekNamedPipe(m_hReadOutput, ByVal 0, 0, 0, lTotal2, 0) = 0 Then
Call CloseHandle(m_hReadOutput)
m_hReadOutput = 0
AtEndOfOutput = (m_hReadOutput = 0)
End If
End If
m_buff = lTotal2
If lTotal2 = 0 Then AtEndOfOutput = True
End Function
Last edited by xxdoc123; May 5th, 2020 at 12:01 AM.
-
May 5th, 2020, 02:10 AM
#25
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
This is not my code. In the original code there is no loop at all
Code:
Public Function AtEndOfOutput() As Boolean
Dim lTotal2 As Long
If m_hReadOutput <> 0 Then
If PeekNamedPipe(m_hReadOutput, ByVal 0, 0, 0, lTotal2, 0) = 0 Then
Call CloseHandle(m_hReadOutput)
m_hReadOutput = 0
End If
End If
AtEndOfOutput = (m_hReadOutput = 0)
End Function
Are you having troubles with the ReadPendingOutput method instead?
cheers,
</wqw>
-
May 5th, 2020, 03:11 AM
#26
Fanatic Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by wqweto
This is not my code. In the original code there is no loop at all
Code:
Public Function AtEndOfOutput() As Boolean
Dim lTotal2 As Long
If m_hReadOutput <> 0 Then
If PeekNamedPipe(m_hReadOutput, ByVal 0, 0, 0, lTotal2, 0) = 0 Then
Call CloseHandle(m_hReadOutput)
m_hReadOutput = 0
End If
End If
AtEndOfOutput = (m_hReadOutput = 0)
End Function
Are you having troubles with the ReadPendingOutput method instead?
cheers,
</wqw>
i used your cls.dont edit .
you can test
Code:
Option Explicit
Private oP As cExec
Private Sub Form_Load()
Set oP = New cExec
oP.Run "cmd.exe", vbNullString ', StartHidden:=True
SendCommand "dir " 'initialize the engine
op.ReadPendingOutput----------------will Not responding
End Sub
Code:
Public Function ReadPendingOutput() As String
Dim lTotal As Long
Do While Not AtEndOfOutput
lTotal = 0
If PeekNamedPipe(m_hReadOutput, ByVal 0, 0, 0, lTotal, 0) = 0 Then
Call CloseHandle(m_hReadOutput)
m_hReadOutput = 0
End If
If lTotal > 0 Then
ReadPendingOutput = ReadPendingOutput & ReadOutput(lTotal)
Else
Exit Function
End If
Loop
End Function
because Do While Not AtEndOfOutput -------------will loop again and again,Non-stop loop
-
May 5th, 2020, 05:49 AM
#27
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
First, don't send snippets that do not compile if you want to get faster responses. Just take your time to create a *separate* project with working code before posting.
Everything is working fine with the class. There is no infinite loop in the class, the problem is only in your code.
For bi-directional communication through console I/O the "protocol" implemented needs to have a sentinel marker, a terminator which will mark end-of-message from the engine. This can be as simple as vbCrlLf & "ok" & vbCrlf or something similar like "ready", "done" or whatever.
This terminator can be searched in the ReadPendingOutput result, because generally you cannot loop until AtEndOfOuput. The idea of AtEndOfOutput property is to indicate when the engine has closed it's StdOut stream and this usually happens on process termination very rearly before that. Before that you have to depend on markers in the protocol to delineate messages in the output stream.
Here is a *working* sample with timeout and everything you need to communicate with a chess engine or similar console I/O from a hidden process
thinBasic Code:
Option Explicit Private oP As cExec Private Sub Form_Load() Set oP = New cExec oP.Run "cmd.exe", vbNullString, StartHidden:=True End Sub Private Sub Form_Click() Cls Print oP.ReadPendingOutput; '--- for terminator emulate command prompt with [tt]CurDir$ & ">"[/tt] Print SendCommand("dir", CurDir$ & ">") End Sub Public Function SendCommand(sCommand As String, sTerminator As String, Optional Timeout As Double) As String Dim sRetVal As String Dim dblTimer As Double dblTimer = Timer oP.WriteInput sCommand & vbCrLf Do While Not oP.AtEndOfOutput sRetVal = sRetVal & oP.ReadPendingOutput If InStr(sRetVal, sTerminator) > 0 Then Exit Do End If If Timeout > 0 Then If Timer > dblTimer + Timeout Then Exit Do End If End If Loop SendCommand = sRetVal End Function
You might want to call a Sleep(1) in the loop to yield CPU to other threads if you notice 100% peaks during SendCommand execution.
cheers,
</wqw>
Last edited by wqweto; May 5th, 2020 at 05:52 AM.
-
May 5th, 2020, 08:34 PM
#28
Fanatic Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
-
Jun 18th, 2021, 12:59 AM
#29
Fanatic Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by wqweto
First, don't send snippets that do not compile if you want to get faster responses. Just take your time to create a *separate* project with working code before posting.
Everything is working fine with the class. There is no infinite loop in the class, the problem is only in your code.
For bi-directional communication through console I/O the "protocol" implemented needs to have a sentinel marker, a terminator which will mark end-of-message from the engine. This can be as simple as vbCrlLf & "ok" & vbCrlf or something similar like "ready", "done" or whatever.
This terminator can be searched in the ReadPendingOutput result, because generally you cannot loop until AtEndOfOuput. The idea of AtEndOfOutput property is to indicate when the engine has closed it's StdOut stream and this usually happens on process termination very rearly before that. Before that you have to depend on markers in the protocol to delineate messages in the output stream.
Here is a *working* sample with timeout and everything you need to communicate with a chess engine or similar console I/O from a hidden process
thinBasic Code:
Option Explicit
Private oP As cExec
Private Sub Form_Load()
Set oP = New cExec
oP.Run "cmd.exe", vbNullString, StartHidden:=True
End Sub
Private Sub Form_Click()
Cls
Print oP.ReadPendingOutput;
'--- for terminator emulate command prompt with [tt]CurDir$ & ">"[/tt]
Print SendCommand("dir", CurDir$ & ">")
End Sub
Public Function SendCommand(sCommand As String, sTerminator As String, Optional Timeout As Double) As String
Dim sRetVal As String
Dim dblTimer As Double
dblTimer = Timer
oP.WriteInput sCommand & vbCrLf
Do While Not oP.AtEndOfOutput
sRetVal = sRetVal & oP.ReadPendingOutput
If InStr(sRetVal, sTerminator) > 0 Then
Exit Do
End If
If Timeout > 0 Then
If Timer > dblTimer + Timeout Then
Exit Do
End If
End If
Loop
SendCommand = sRetVal
End Function
You might want to call a Sleep(1) in the loop to yield CPU to other threads if you notice 100% peaks during SendCommand execution.
cheers,
</wqw>
hi wqw
now i want used your cExec.but i can not work
if i usde adb to connect my phone
i usde
Code:
Option Explicit
Private oP As cExec
Private Sub Form_Load()
Set oP = New cExec
oP.Run "cmd.exe", vbNullString ', StartHidden:=True
' oP.Run "scrcpy --help", vbNullString ', StartHidden:=True 'can not run cmd
' oP.Run "scrcpy", " --help" ', StartHidden:=True 'can not run cmd
End Sub
Private Sub Form_Click()
Cls
Print oP.ReadPendingOutput;
'--- for terminator emulate command prompt with CurDir$ & ">"
Print SendCommand("scrcpy --help", CurDir$ & ">") 'https://github.com/Genymobile/scrcpy/'
' if found cmd can changed to "cmd.exe -scrcpy --help" ,this is wrong cmd..
End Sub
Public Function SendCommand(sCommand As String, sTerminator As String, Optional Timeout As Double) As String
Dim sRetVal As String
Dim dblTimer As Double
dblTimer = Timer
oP.WriteInput sCommand & vbCrLf
Do While Not oP.AtEndOfOutput
' sleep 1 '
sRetVal = sRetVal & oP.ReadPendingOutput
If InStr(sRetVal, sTerminator) > 0 Then
Exit Do
End If
If Timeout > 0 Then
If Timer > dblTimer + Timeout Then
Exit Do
End If
End If
Loop
SendCommand = sRetVal
End Function
can you help me? thanks
-
Jun 18th, 2021, 01:41 AM
#30
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
This works here
Code:
Option Explicit
Private oP As cExec
Private Sub Form_Click()
Set oP = New cExec
oP.Run "D:\TEMP\scrcpy-win32-v1.17\scrcpy.exe", "-V debug", StartHidden:=True
Do While Not oP.AtEndOfOutput
Print oP.ReadPendingOutput & oP.ReadPendingError;
DoEvents
Loop
End Sub
cheers,
</wqw>
-
Jun 18th, 2021, 10:35 AM
#31
Fanatic Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by wqweto
This works here
Code:
Option Explicit
Private oP As cExec
Private Sub Form_Click()
Set oP = New cExec
oP.Run "D:\TEMP\scrcpy-win32-v1.17\scrcpy.exe", "-V debug", StartHidden:=True
Do While Not oP.AtEndOfOutput
Print oP.ReadPendingOutput & oP.ReadPendingError;
DoEvents
Loop
End Sub
cheers,
</wqw>
yes this can run ok
but i want usde open cmd, next run scrcpy.exe or adb.exe command
like my post code of the above.
Code:
Option Explicit
Private oP As cExec
Private Sub Form_Load()
Set oP = New cExec
oP.Run Environ$("COMSPEC"), "/Q" ', StartHidden:=True
' oP.Run "scrcpy --help", vbNullString ', StartHidden:=True 'can not run cmd
' oP.Run "scrcpy", " --help" ', StartHidden:=True 'can not run cmd
End Sub
Private Sub Form_Click()
Cls
Print oP.ReadPendingOutput;
'--- for terminator emulate command prompt with CurDir$ & ">"
Print SendCommand("scrcpy --help", CurDir$ & ">") 'https://github.com/Genymobile/scrcpy/'
' if found cmd can changed to "cmd.exe -scrcpy --help" ,this is wrong cmd..
End Sub
Public Function SendCommand(sCommand As String, sTerminator As String, Optional Timeout As Double) As String
Dim sRetVal As String
Dim dblTimer As Double
dblTimer = Timer
oP.WriteInput sCommand & vbCrLf
Do While Not oP.AtEndOfOutput
' sleep 1 '
sRetVal = sRetVal & oP.ReadPendingOutput
If InStr(sRetVal, sTerminator) > 0 Then
Exit Do
End If
If Timeout > 0 Then
If Timer > dblTimer + Timeout Then
Exit Do
End If
End If
Loop
SendCommand = sRetVal
can this code run ok?
-
Jun 18th, 2021, 03:57 PM
#32
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Try changing this line
sRetVal = sRetVal & oP.ReadPendingOutput
to something like this
sRetVal = sRetVal & oP.ReadPendingOutput & oP.ReadPendingError
The idea is to collect both stdout and stderr into the returned string.
cheers,
</wqw>
-
Jun 19th, 2021, 06:37 AM
#33
Fanatic Member
Re: Details on How to Use Named Pipe for Communicating with Chess Engine
Originally Posted by wqweto
Try changing this line
sRetVal = sRetVal & oP.ReadPendingOutput
to something like this
sRetVal = sRetVal & oP.ReadPendingOutput & oP.ReadPendingError
The idea is to collect both stdout and stderr into the returned string.
cheers,
</wqw>
work ok thanks
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|