Tag Archives: Compiler

String comparison in OpenInsight – Part 4 – String-Only operators

Basic+ is a dynamically-typed language, and sometimes the system will attempt to change the type of a variable before using it in an operation. For example, if we have variable containing a numeric value as a string, and we attempt to compare it to a variable containing an actual numeric value, then the former will be coerced to a numeric format before the comparison, e.g.:

   StrVar = "3"  ; // String representing an integer
   NumVar = 7    ; // Integer

   Ans = ( StrVar = NumVar ) ; // Ans is FALSE$ and StrVar 
                             ; // is converted to an Integer

This works well in the vast majority of cases, but causes issues when attempting to compare strings that that could be numbers but really aren’t for the purposes of the comparison. For example, consider the following:

   Var1 = "12"    ; // String
   Var2 = "012"   ; // String

   Ans = ( VarA = VarB ) ; // Ans = TRUE$ - should be FALSE$!!

The system first attempts to coerce the operands to a numeric format, and of course it can because they both evaluate to the number 12, and are therefore treated as equal. If you wish to compare them both as strings, the usual solution has been to change them so that they cannot be converted to a number, usually by prefixing them with a letter or symbol like so:

   Var1 = "12"    ; // String
   Var2 = "012"   ; // String

   Ans = ( ( "X" : VarA ) = ( "X" : VarB ) ) ; // Ans = FALSE$

Although this works it hurts performance and can be a cumbersome solution.

OpenInsight 10.2 introduces a new set of extended comparison operators to Basic+ that ensure both operands are coerced to a string type (if needed) before being evaluated, and provides the following benefits:

  • Removes the need for any concatenation thereby avoiding unnecessary copying
  • Removes the need to check if a variable can be a number before the comparison
  • Makes it obvious that you are performing a “string-only” comparison so your code looks cleaner.

The new (case-sensitive) operators are:

OperatorDescription
_eqsEquals operator
_nesNot Equals operator
_ltsLess Than operator
_lesLess Than Or Equals operator
_gtsGreater Than operator
_gesGreater Than Or Equals operator

There is also matching set of case-insensitive operators too:

OperatorDescription
_eqscCase-Insensitive Equals operator
_nescCase-Insensitive Not Equals operator
_ltscCase-Insensitive Less Than operator
_lescCase-Insensitive Less Than Or Equals operator
_gtscCase-Insensitive Greater Than operator
_gescCase-Insensitive Greater Than Or Equals operator

So we could write the previous example like so:

   Var1 = "12"    ; // String
   Var2 = "012"   ; // String

   Ans = ( VarA _eqs VarB ) ; // Ans = FALSE$

Hopefully you’ll find this a useful addition when performing string-only comparisons in your own applications.

The Saga of ShellExecute

One of the most popular “raw” Windows API functions that OpenInsight developers have used over the years is the ShellExecute function, which allows you to launch an application via its filename association, e.g. you can launch Word by using a document file name, or Excel using a spreadsheet filename and so on.

However, because it was never really made an “official” part of the product (it was normally passed on in forums), developers were left to create their own DLL Prototype definitions in order to use it – this gave rise to many variations over the years, many of which were not compatible with others. For example, some use LPCHAR as an argument type, some use LPSTR or LPASTR, whilst others use LPVOID with GetPointer(); some definitions use the “Wide” version of the function, some the “Ansi” version, and there are many different aliases, with or without the “A/W” suffix too. The list goes on.

For OpenInsight 10 we decided that we couldn’t move forward with this as we would run the risk of conflicting with established applications, so we moved all of the DLL Prototypes we used into a new namespace called “MSWIN_” and claimed it as our own. This left developers to bring forward their own DLL prototypes into version 10 as and when needed, and therefore we didn’t supply a “ShellExecute” function as such, though we did supply “MsWin_ShellExecute” instead (see below).

Another decision we took was to try and move away from the need for developers to use raw Windows API function calls as much as possible, as some of them can be complex and require knowledge of C/C++ programming, which is not necessarily a skill set that everyone has the time or desire to learn. Ergo, we moved a lot of functionality into the Presentation Server (PS) and created some Basic+ wrapper functions around others to shield developers from the sometimes gory internals.

(We also chose to use the “W” versions of functions rather than the “A” versions where possible, because these would translate better when in UTF8 mode and remove the need for an extra “A”->”W” conversion in Windows itself.)

So, coming back to ShellExecute, and in light of the above, we have three “official” and supported ways of calling it in OpenInsight 10 as detailed below:

  • The SYSTEM object SHELLEXEC method
  • The RTI_ShellExecuteEx stored procedure
  • The MSWin_ShellExecute DLL Prototype stored procedure

The SYSTEM object SHELLEXEC method

If your program is running in “Event Context”, (i.e. it is executing in response to an event originating from the PS) then you may use the SYSTEM SHELLEXEC method which invokes ShellExecuteW internally.

RetVal = Exec_Method( "SYSTEM", "SHELLEXEC", OwnerForm, Operation, File, |
                      Parameters, WorkingDir, ShowCmd )
ParameterRequiredDescription
OwnerFormNoName of a form to use as a parent for displaying UI messages.
OperationNoOperation to be performed; “open”, “edit”, “print” etc.
FileYesFile to perform the operation on.
ParametersNoIf File is an executable file this argument should specify the command line parameters to pass to it.
WorkingDirNoThe default working directory for the operation. If null the current working directory is used.
ShowCmdNoDetermines how an application is displayed when it is opened (as per the normal VISIBLE property).

The return value is the value returned by ShellExecuteW.

The RTI_ShellExecuteEx method

This stored procedure is a wrapper around the Windows API ShellExecuteExW function (which is used internally by ShellExecuteW itself), and may be used outside of event context – it can also return the handle to any new process it starts as a result of executing the document. As you can see it’s quite similar to the SHELLEXEC method:

RetVal = RTI_ShellExecuteEx( Hwnd, Verb, File, Parameters, |
                             Directory, nShow, hProcess )
ParameterRequiredDescription
HwndYesHandle of a window to use as a parent for displaying UI messages, or null (0) to use the desktop.
VerbNoOperation to be performed; “open”, “edit”, “print” etc.
FileYesFile to perform the operation on.
ParametersNoIf File is an executable file this argument should specify the command line parameters to pass to it.
DirectoryNoThe default working directory for the operation. If null the current working directory is used.
nShowNoDetermines how an application is displayed when it is opened (as per the normal VISIBLE property).
hProcessNoReturns the handle to the new process.

The return value is the value returned by ShellExecuteExW.

The MSWin_ShellExecute DLL Prototype stored procedure

This is the “raw” DLL function that is included with OI10, and the definition can be found in the MSWIN_SHELL32 DLLPROTOTYPE entity:

Shows the MSWIN_SHELL32 DLLPROTOTYPE entity

Because we’re using LPWSTR data types there is no need to null-terminate any of your variables so using it is quite simple:

RetVal = MsWin_ShellExecute( 0, "open", "stuff.docx", "", "c:\docs", 1 )

Migrating ShellExecute

Whilst you are free to use one of the methods outlined above, this may not be optimal if you are still sharing code between your existing version 9 application and your new version 10 one. In this case there are a couple of options you could use:

  • Define your preferred DLL prototype in v10.
  • Use a wrapper procedure and conditional compilation.

Defining your own prototype

This is probably the easiest option – you simply use the same prototype in v10 that you did in version 9, with the same alias (if any), and this way the code that uses it doesn’t need to be changed. The only downside to this if you’ve used any 32-bit specific data types instead of 32/64-bit safe types like HANDLE (this could happen if you have a really old prototype) – you must ensure that you use types that are 64-bit compliant.

Using conditional compilation

This is a technique we used when writing the initial parts of v10 in a v9 system so our stored procedures would run the correct code depending on the platform they were executing on (it was actually first used to share code between ARev and OI many years ago!).

The v10 Basic+ compiler defines a token called “REVENG64” which is not present in the v9 compiler – this means that you can check for this in your source code with “#ifdef/#ifndef” directives and write code for the different compiler versions.

For example, you could write your own wrapper procedure for ShellExecute that looks something like this:

Compile Function My_ShellExecute( hwnd, verb, file, params, dir, nShow )

#ifdef REVENG64
   // V10 Compiler - use RTI function
   Declare Function RTI_ShellExecuteEx
   RetVal = RTI_ShellExecuteEx( hwnd, verb, file, params, dir, nShow, "" )
#endif

#ifndef REVENG64
   // V9 Compiler - use existing "raw" prototype
   Declare Function ShellExecute
   RetVal = ShellExecute( hwnd, verb, file, params, dir, nShow )
#endif

Return RetVal

And then call My_ShellExecute from your own code.

So, there ends the Saga of ShellExecute … at least for now.