|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I'm sure that we've all had to deal with the sort of user interface where we are working on a data aware form and only want to enable the OK/Save button when a series of conditions (other than simply required which the 4GL can happily enforce) are met. And like me you've probably written a generic validateOKRequirements: subroutine and wired it into every relevant LOSTFOCUS, CHANGED, CLICK event. Tedious, but necessary. Or so I thought. Enter SAVEWARN and SYSMSG. If you're thinking, "Oh I know about that" then please resume normal programming. If, like me, you apparently have been living under a rock for all these years then read on. The SAVEWARN property of the Window is a "Dirty" flag. If it is true then the data on the screen has changed from how it was when it was originally loaded. This is the flag the system checks the generate the "Changes will be lost... Continue?" prompt. So to enable/disable our own button all we need to do it check the value of SAVEWARN and if it is true, gosub our validation, and enable/disable accordingly. All well and good - but the thing that has evaded me all of these years, was how to actually do that. It seems the answer has been staring me in the face for years. The system already tells you when something becomes dirty and it does so through code 21 with SYSMSG.. The SYSMSG event is raised to a Window to allow the developer to intercept and modify/replace system responses to such situations as "Data will be lost, continue?" or "Are you sure you wish to delete?". Each of these passes the SYSMSG event a code to identify itself, such as 1 for the former and 2 for the latter. But scan to the end of the list and there at 21 is equ SYSMSG_SAVEWARNINFO$ to 21 ; // Save warn has been changed - null msg This is triggered each time SAVEWARN changes state. So when it becomes true and when it becomes false. So now we can do away with all of our LOSTFOCUS,CLICK,CHANGE type events and instead simply have one SYSMSG event with code like the following thereby centralising all of the checking in one easy to use place. We would label this a tricky gotcha but truth be told BUT if it got you you must have had more time on your hands than sense. A recent announcement from Revelation explained that the <> syntax has been updated - and that due to huge improvements in <>< processing speeds " you can continue using the familiar “<>” syntax for sequential dynamic array processing without needing to rewrite code using LOOP/REMOVE or LOOP/[] parsing patterns.". Historically nobody in their right mind would have processed the 65,536th element of a dynamic array using <>. If they wanted to get there they'd have used loop/remove. When the Revelation compiler was originally written, the maximum row size was 64K - so the maximum possible populated columns was 32K. So to speed up processing, the compiler replaced a < N > reference to a special opcode optimised for such extractions. Using < N, N > and < N, N, N > don't redirect to this special opcode, redirecting to the EXTRACT opcode instead. All well and good except for one slight snag. Remember the 64K limit? Well, then it made sense to store the value for the <> opcode as an unsigned 32 bit integer. Trouble is, that can only accommodate 64K. So what happens if you try and extract from an array where N is > 64K? A picture allegedly speaks louder than words One of the best parts of the new IDE is that every control type has its own designer: open it in-place, tweak the editable properties, and inspect the repository metadata. We live in these designers every day when we open a form, a program, a popup, or even a menu. What’s less obvious is that there’s also a designer for object code. It turns a lot of “system sleuthing” from writing code to take object code apart into something closer to browsing. To get to it: File → Open → Entity, then choose Stored Procedure Executables. Here's an example of using the designer to look at RLIST Open RLIST and the first thing you notice is how readable fragments are more easily visible due to them being plain text. Once you recognise the layout, that visibility gives you quick signals about what the routine is doing and which external routines it calls. On the right, the ObjectCode Properties panel tells you the basics immediately: RLIST is a subroutine (not a function) and it takes five parameters. The parameter names are obscured, but the Options button expands a popup showing the full list clearly. That’s the tool in the abstract. Here’s where it becomes genuinely useful. A concrete use: “How many users are in the system right now?” We can safely discard most of those if what we are looking for is a routine from RTI to GET the USERCOUNT. Let's try opening that up in the designer It's a function. If you call it with three blank variables, it populates them with:
One caveat: it only works when called with the UD in place. I’m sure you’ll find your own favourite uses for the object code designer, but if you haven’t tried it yet, it’s worth five minutes. If you’ve ever had to take object code apart by hand, this saves real time. OI 10 marked the introduction of a new licensing method, replacing the old `oengine.dll` stamping. Historically, the licensing information was baked into `oengine.dll`. If you wanted to “change” the characteristics of your OI system you could just drop in a different `oengine.dll` and, effectively, you had a new system. OI 10 introduces a separate licence metadata file called `revengine.lic`. It’s a simple XML file that looks like this (with some minor obfuscation): <OI> The `Signature` element is the important part. It’s a cryptographic signature over the real licence attributes – serial number, user count, expiry date and type – generated with a secret key that only Revelation knows. OI trusts *that* value and treats the rest of the XML purely as metadata for human consumption. You can edit `SerialNumber`, `NumUsers`, `ExpirationDate` and `ExpirationType` to your heart’s content; it doesn’t change the way OI behaves because those fields are not trusted. At this point you might think: “Fine, I’ll just copy the `.lic` file instead of the `.dll`.” But there’s a catch. OI 10 also moves to industry-standard security. Various user rows in the system are now encrypted using a key derived from (and salted with) the Signature. Change the Signature and you change the encryption seed. Anything that was encrypted under the old Signature will no longer decrypt under the new one and, if you’re using the default security policy, you will lock yourself out of the system. So yes, `revengine.lic` is technically portable – but once you’ve started using the system in anger, all of your sensitive user data is cryptographically tied to that specific licence Signature. Swapping licences is no longer a harmless way to “refresh” a system; it has real consequences for your encrypted data. An old client recently got in contact about the system we had written for them in AREV in the early 90s and subsequently ported to AREV32. Over the years, we had added some bells and whistles including an incredibly specifically tailored OI export utility which we installed about 10 years ago. Just recently, we added another OI export utility, and it was working as expected. But now, the client has reported that when including a specific set of columns, the report just hung - no messages, nothing. Eventually they just had to kill it in the task manager. They wondered if this was in any way connected to the recent upgrade? Now here's where it gets interesting. The columns in question are used frequently throughout the system, displayed on entry screens, reports and the like with no issues, so we thought the client's concern might be justified. We spun up our test system and attempted to produce the report the client was trying to create. Immediately upon selecting the columns in question, the system broke into the debugger with 'Unable to load program XXX'. At least this explained why the report was failing (the live system runs with the debugger disabled so they wouldn't see the error message). But it didn't explain how anything we might had done in the latest upgrade could have caused this. So down to TCL and EDIT BP XXX. The program is there. Perhaps it hadn't been compiled? Recompile. The issue was not resolved. Maybe, just maybe it hadn't been cataloged? EDIT VOC XXX. The catalog pointer is there and pointing to the correct program. But still the system crashes with being unable to load XXX. Then a vestigial memory oozed from the nether regions of our consciousness. In AREV, VOC and MD are synonyms. In AREV, CATALOG writes to the VOC file. In OI VOC and MD are two separate files. One used for AREV32 and one used for CTO/OI. So EDIT MD XXX New Record. COPY VOC XXX TO:(MD and all was fixed, nothing, fortunately, to do with the upgrade. Having been bitten by one missing VOC record we thought discretion was the better part of valour, and copied over all missing MD entries from VOC. All should be good yes? So we logged out of the system. Next time we logged in, disaster struck. ENG0711 RLIST. Cannot start engine... Once again it's LH logs to the rescue. We tried to start OI, and again it failed. But from the log we could see that the system wanted to load RLIST. So it went to the Global MD file and looked for an RLIST entry. Regretfully it found one pointing to the AREV RLIST which doesn't take parameters - it parses @SENTENCE - and called it instead of OI RLIST. Armed with this knowledge we could restore the Global MD files and sanity was restored. Almost happy with the solution we hit a realisation. ALL of the catalog pointers in the client's app are in VOC - not MD. So why isn't the system falling over left right and centre? The answer lies in OI's RTP27 program loader optimisations. When OI knows that it's running as a native windows app, RTP27 first looks in the Global MD for a pointer to the program in question, and if finding a pointer, executes that program, and if failing follows the inheritance chain looking for the program in SYSOBJ. In theory RTP27 then checks your VOC file, but this currently (10.2.3) tends not to work. When OI knows that it's running as AREVNN, it first checks VOC, then MD, then looks for the program. The issue here was that despite the fact we were running AREV32, the Export Window was running as a native window and so skipped the VOC step. If only we'd taken our own advice from over a decade ago... In summation, RTP27 loads programs in the following order
Some seven years ago, REVDEX documented the then newly introduced concept of #region to allow folding of code. We find this to be incredibly useful when working on large commuter programs as it allows easy navigation between code sections. There's no point in reproducing the linked article, so briefly, you can bracket sections of code between #region regionname and #endregion regionname and then fold that section of code. Here's an example of one of our commuter routines with the various code sections "regioned". The editor then takes me to the line number where that region begins. However, there is a minor caveat to be aware of when we're cutting and pasting collapsed regions. In the following pseudo-code (with a nod to Jackson Structured Programming throwback for those as ancient as myself) we've simply placed an individual subroutine within a region. That's not normally a real life scenario. You use regions to group logically connected groups of code. Of course you may choose to do this if you wish to be able to collapse individual subroutines. and collapse it to make cutting and pasting easier Let's decide that we want to move the process region to after the wrapup region, so we select the "#region process" and Ctrl-X to cut the region then move to the end of the program and press return and Ctrl-V to paste the region All looks sort of good. But let's expand everything again and see where we are It has brought across the process region line but inserted it within the wrapup region. So let's revisit and this time change the selection Note that we have now selected to the beginning of the next line - you can see the cursor. Cut, move down and paste and the expansion now looks like this the process region has been inserted within the wrapup region, not after. To fix this what we have to do is expand the wrapup region before doing the paste, like so Well worth paying a little attention to detail. An interesting query made its way into the office this week. Our client wanted to be able to validate data using Regular Expressions (REGEXP) for Email validation (^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$ if you must know) and so unsurprisingly searched the documentation for REGEXP and came across its usage in RTI_JSON. This led to a series of mutual misunderstandings which led to a far greater understanding of what JSON actually is (see footnote) but still didn't help in actually validating using a REGEXP. Fortunately, we have resources in the office who were able to advise that what we wanted to achieve was actually relatively simple using the OI ActiveX Scripting Host. This is something that is not yet "officially" documented in the product - but like quite a few things in OI, there is profuse documentation in the Equates. In version 10 this has been handily encapsulated for us the the RTI_AXSH function - the ActiveX Scripting Host. ActiveX Scripting and Active Scripting are the same thing. The X was dropped over the years. But first - what actually is a Scripting Host? An Active Scripting Host is a technology used in Windows to support component-based scripting. It allows different scripting engines to be integrated into applications. This enables the execution of scripts written in various languages like VBScript, JScript (Microsoft’s implementation of JavaScript), Python, Perl and others. If we have a task we wish to execute that is better suited to say, JScript - performing a REGEXP for example - we can use RTI_AXSH to do this for us. To show how easy it is, we'll first provide a small sample program to do this, then explain the program and finally document the routine. The Codecompile function test_revaxsh_regexp( void ) $insert rti_AXSH_Equates call set_Status( FALSE$ ) errStat = SETSTAT_OK$ code = 're = new RegExp( "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" );' createParams = "" hAXSH = rti_AXSH( REVAXSH_MTD_CREATE$, createParams ) errStat = get_Status( errText ) if errStat then call rti_AXSH( REVAXSH_MTD_ADDCODE$, hAXSH, code ) errStat = get_Status( errText ) if errStat then string = "amcauley@sprezzatura" if get_Status() else if ( hAXSH ) then if errStat then return retVal The ExplanationThe first thing that we need to do is insert RTI_AXSH_Equates. Once this has been done, we construct some simple JScript code in a variable called code. For those not fluent in JScript it's probably worth explaining how the code operates. We start by create a global variable called "re". Think of it as being like labelled common. It'll be there until the scripting host is destroyed. Loading the code instantiates the re object as a REGEXP type. We then define a JScript function called testRE so that we can call it from within Basic+. We initialise our create parameter to tell OI which scripting language to use and then use the CREATE method to initialise our Script Hosting. Now that we have a container to put our code in, we can add it using the ADDCODE method. Assuming no errors we then use the RUNEX method, telling it the name of the function in our code we wish to execute (testRE). We now have the result we want to do with as we will. Finally in an act of polite housekeeping we tell the system to DESTROY the scripting host we created so as not to leak resources. We hope that this information makes the Scripting Host a little less scary! The Documentation - taken from RTI_AXHS_EQUATES.The RTI_AXSH routine provides a set of methods for interacting with the ActiveX Scripting Host. Below is a detailed guide on how to use these methods, including their parameters, return values, and error handling. The following methods are supported
CREATECreates an ActiveX scripting host instance and returns it's handle. <1> Script Language (required)
DESTROYDestroys a script host instance.
GETPROPReturns the value of a specified property
The supported properties are "Language", "Allow UI", "SiteHnd" and "Timeout" ANY DEFAULTS CARL? SETPROPSets the value of a specified property e.g. propName = REVAXSH_PROP_ALLOWUI$ propValue = FALSE$ call rti_AXSH( REVAXSH_MTD_SETPROP$, hAXSH, propName, propValue )
ADDCODEAdds a block of code to the scripting host (Any immediate code will be executed as normal) e.g. scriptCode = "function add( a, b ) { return a + b; }" call rti_AXSH( REVAXSH_MTD_ADDCODE$, hAXSH, scriptCode )
EVALEvaluates a statement and returns the result. e.g. statement = "3+10;" retVal = rti_AXSH( REVAXSH_MTD_EVAL$, hAXSH, statement )
EXECUTEExecutes one or more statements (no result is returned) e.g. scriptCode = "function add10( a, b ) { return a + 10; }; add10( 3 );" call rti_AXSH( REVAXSH_MTD_EXECUTE$, hAXSH, scriptCode )
RUNExecutes a specific script function and returns the result (if any). e.g. scriptCode = "function add( a, b ) { return a + b; }" call rti_AXSH( REVAXSH_MTD_ADDCODE$, hAXSH, scriptCode ) method = "add" args = 3 : @rm : 4 retVal = rti_AXSH( REVAXSH_MTD_RUN$, hAXSH, method, args )
GETENGINESReturns a list of ActiveX script engines installed on the workstation. e.g. engineList = rti_AXSH( REVAXSH_MTD_GETENGINES$ )
ADDOBJECTAdds a global named object to the scripting host e.g. objName = "MyXmlDoc" objDoc = OleCreateInstance( "Msxml2.DOMDocument" ) call rti_AXSH( REVAXSH_MTD_ADDOBJECT$, hAXSH, objName, objDoc )
RUNEXExecutes a specific script function and returns the result (if any), allowing the script function parameters to be passed as separate arguments (This method is the only method that can pass OLE objects to a script function) The maximum number of parameters that can be passed is 10. Note that this method stops looking for script function arguments when it encounters the first unassigned one. e.g. scriptCode = "function add( a, b ) { return a + b; }" method = "add"
A note relating to all scripting errorsThe Execute, AddCode, Eval and Run methods can all return parsing and execution errors from the scripting engine. In this case an @svm delimited list of error information is returned with the following structure: <0,0,1> Error Source Footnote:JSON (JavaScript Obect Notation) is a way of serializing JS variables/objects to a human-friendly string format so they can be saved and reloaded. It is not intended as a way to create JS objects and then call their methods and such... despite what the author thought Pro TipCoPilot understands RegExp well. It suggests replacing the REGEXP we started with with "^[\w.%+-]+@[\w.-]+\\.\w{2,}$".
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||