Redirecting the current I / O device for third-party routine communication

I have many M-Code routines provided by the supplier as part of a much larger product that uses READ and WRITE directly to interact with the current device. I can not change this code. I want to wrap some of these routines in a system where I can provide input and output capture interactively.

This is currently implemented by opening a TCP connection to the remote host and making it the current device. READ and WRITE really connected to the socket. This is quite inconvenient, since it requires that a separate service that listens for a TCP socket is configured and coordinated with a local job for the entire process to work. I also have to turn off the insolence and skip the buffering, or the connection becomes latent or closed. (for example, the TCP option OPEN /SEN=1 aka +Q ). Unfortunately, this leads to many 1-byte TCP segments and is also very inefficient.

I would rather manage all interactions through one process. Ideally, I could call READ , WRITE , and other functions on the current device trigger some M-codes or callbacks in the Caché Callin C or user extension module to provide the required functions on the rear panel. Thus, I can manage IO on my own terms without the need for coordination between processes. I could not find an entry point to set this, though.

Is there such a thing as a user device in Caché?

For UNIX hosts, there is a way to use an existing file descriptor as a device, which may be useful, but it doesn't seem to be implemented on Windows.

One thing I considered is to create a new process, redirect Windows STDIN and STDOUT using SetStdHandle to the channels I control from the same process, use Callin to connect to Caché and let it use the default device, which should be STDIN and STDOUT . Does anyone know if this really works?

+4
source share
1 answer

Caché actually supports arbitrary I / O redirection. This is possible with an undocumented function. Not recommended, but unlikely to change.

Annotated code is given below. In this example, I choose to redirect IO to% Stream.GlobalBinary - you can, as you like, with it. The key is the specific label names - you can specify another procedure using $ io :: call and specify these labels elsewhere.

 //The ProcedureBlock = 0 is important // it allows for calling of labels within the ClassMethod ClassMethod testIORedirection() [ ProcedureBlock = 0 ] { //Create a stream that we will redirect to set myStream = ##class(%Stream.GlobalBinary).%New() //Redirect IO to the current routine - makes use of the labels defined below use $io::("^"_$ZNAME) //Enable redirection do ##class(%Device).ReDirectIO(1) //Any write statements here will be redirected to the labels defined below write "Here is a string", !, ! write "Here is something else", ! //Disable redirection do ##class(%Device).ReDirectIO(0) //Print out the stream, to prove that it worked write "Now printing the string:", ! write myStream.Read() //Labels that allow for IO redirection //Read Character - we don't care about reading rchr(c) quit //Read a string - we don't care about reading rstr(sz,to) quit //Write a character - call the output label wchr(s) do output($char(s)) quit //Write a form feed - call the output label wff() do output($char(12)) quit //Write a newline - call the output label wnl() do output($char(13,10)) quit //Write a string - call the output label wstr(s) do output(s) quit //Write a tab - call the output label wtab(s) do output($char(9)) quit //Output label - this is where you would handle what you actually want to do. // in our case, we want to write to myStream output(s) do myStream.Write(s) quit } 
+7
source

All Articles