Caché tricks - capture write output to variable

 

Cache tricks

Several years ago, long before Developer Community Portal was launched, I published a series of Caché tricks at one of Czech web sites. In this article, I’m posting translated version of one of them.

Capturing output of someone else’s methods or routines

Suppose you, or someone else created a useful method or routine, that was producing some computation that you’d like to benefit from, but the routine was writing output to process principal device.

You would like to use the data, but you need it not written to a device, but assigned to a variable. And, for any reason, you can’t modify the code. What can you do?

Well, there is a solution to it. A bit tricky, but works very well.

Let’s take an example from not so far past. In Caché 2013 we introduced enhancement to ZEN framework, a class %ZEN.Auxiliary.jsonProvider, that allowed to pass JSON objects between client (browser) and server and vice versa. Unfortunately, the %ObjectToJSON() method was implemented in just that way - it was writing JSON serialized object representation to output device (the TCP / HTTP channel).

So, how to deal with the problem? There is something we call mnemonic space, these spaces are available with Caché IO devices. We can uses these mnemonic spaces to open IO devices. However, online documentation is very concise about mnemonic spaces and is not providing much practical details of use.

Let’s have a look at the following code, that demonstrates one use case.

        set pObject=##class(%ZEN.proxyObject).%New()        // any class can be here
        set pObject.error="my-error-1"
        set pObject.description="Error number one"     

        Set tInitIO = $IO
        // we MUST use %ISCJSONStream variable used by mnemonic space ^%ZEN.Auxiliary.jsonProvider.1 as output redirection container
        set %ISCJSONStream=##class(%Stream.TmpCharacter).%New()
        use tInitIO::("^%ZEN.Auxiliary.jsonProvider.1") // this routine – here as mnemonic space – is created during %ZEN.Auxiliary.jsonProvider class compilation
        do ##class(%Library.Device).ReDirectIO(1)
        $$$THROWONERROR(tSC,##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(pObject))
        If ##class(%Library.Device).ReDirectIO(0) Use tInitIO
        do %ISCJSONStream.Rewind()
        // display result
        w %ISCJSONStream.Read()
        // result: { "error":"my-error-1", "description":"Error number one" }

You can, alternatively, write your own redirection routine

        // save as MAC routine, e.g. test.mac
        // launch by from terminal: d run^test()
run()  
        Set tInitIO = $IO
        // we MUST use variable called %Stream that is used by a mnemonic space created by our routine
        set %Stream=##class(%Stream.TmpCharacter).%New()
        use tInitIO::("^"_$zname)
        do ##class(%Library.Device).ReDirectIO(1)
        // call routine that we want to redirect
        do demo()
        If ##class(%Library.Device).ReDirectIO(0) Use tInitIO
        do %Stream.Rewind()
        // show result
        set res=%Stream.Read() write res
        // result: Hi there!
        q

demo() public
{
        w !,"Hi there!"
}      


redirects()
#; Public entry points for redirection
wstr(s) Do %Stream.Write(s) Quit
wchr(a) Do %Stream.Write($char(a)) Quit
wnl Do %Stream.Write($char(13,10)) Quit
wff Do %Stream.Write($char(13,10,13,10)) Quit
wtab(n) New chars Set $piece(chars," ",n+1)="" Do %Stream.Write(chars) Quit
rstr(len,time) Quit ""
rchr(time) Quit ""

 

Please note that redirects() procedure is not using procedural block and uses public variable %Stream that needs to be initialized before calling code to what we apply redirection.

 

Hope you find this helpful.

Dan

PS: When I searched community portal, I saw an article that was referring to the redirection, too. However, it was in a different context and was not explaining the power of such redirection.

  • + 8
  • 0
  • 1322
  • 4

Comments

Maybe it is worth to mention that there is much simpler solution now in 2016 for the object-to-JSON conversion. It is a tiny part of the new powerful DocDM concept (http://localhost:57772/csp/docbook/DocBook.UI.Page.cls?KEY=GJSON_intro#GJSON_intro_dao_dollar).

try this:

set pObject=##class(%Object).%New()   ///this is one of the ways to create a DocDM object 

pObject.error="my-error-1"

set pObject.description="Error number one"

set  string=pObject.$toJSON()  ///here you call a system method "toJSON()" via dot dollar syntax

write string

........

However, the output capturing can be useful in other situations (or with the older versions of Cache).

 

 

Well, whilst it is true that we have much better ways of handling JSON today, this article mentions is by pure coincidence, driven by the fact that I had that problem some 3 years ago and when searching for solution, I found write redirection code withing the class mentioned in the article.

BTW: there is a %Device class available, that wraps redirection code, so no need to write your own mac code anymore.

That's useful.

set %Stream=##class(%Stream.TmpCharacter).%New() 

Is there any particular reason to use % variable here? I think local variable would be enough.

Sure, thats needs because this variable should be available in any place for this process, just because this code executes in context where works any output commands like write