Check out the Documentation page for some new updates:
- EDITBOX object
- EDITLINE object
- HTTPCLIENT object
Check out the Documentation page for some new updates:
Over the years there have been several different and disparate ways of managing directories in OpenInsight, and not all of them fully documented. In this post we’re going to take look at the “official” preferred methods, along with a mention of the deprecated ones too.
As you may be aware, an OpenInsight application runs in one of the following contexts:
Therefore, one of the most fundamental considerations when choosing which directory management method to use is the context in which it is called: As a rule, when running in Event Context, you should always prefer to use the FILESYSTEM object for directory management, otherwise you should choose the RTI_OS_Dir stored procedure instead.
Note that if there is a possibility that your Basic+ programs will be executed in different contexts at runtime (i.e. you share them between contexts) then you should invoke the IsEventContext stored procedure to determine which method to use.
For example, here is a simple context-aware code snippet that removes a directory:
Declare Function IsEventContext, RTI_OS_Dir, Exec_Method
$Insert PS_FileSystem_Equates
$Insert RTI_SSP_Equates
ErrText = ""
If IsEventContext() Then
// Use the FILESYSTEM object
If Exec_Method( "FILESYSTEM", "REMOVEDIR", DirName ) Else
ErrInfo = Get_Property( "FILESYSTEM", "FILEOPRESULT" )
ErrCode = ErrInfo<PS_FOR_ERRORCODE$>
ErrText = ErrInfo<PS_FOR_ERRORTEXT$>
End
End Else
// Use RTI_OS_DIR
Call Set_Status( SETSTAT_OK$ )
If RTI_OS_Dir( "REMOVEDIR", DirName ) Else
Call Get_Status( ErrText )
End
End
The FILESYSTEM object supports the following methods to manage directories. It integrates fully with the Windows Shell and provide the best user experience in Event Context:
The FILESYSTEM object is fully documented here.
This stored procedure was added to OpenInsight 10 to provide a non-Event Context version of the functionality exposed by the FILESYSTEM object. It supports the following methods:
The RTI_OS_Dir stored procedure is fully documented here.
(Note that there is no direct equivalent of the FILESYSTEM GETSPECIALDIR method, due to the fact that it is very Windows-specific – the GET_SPECIAL_FOLDER stored procedure should be used instead.)
As long-time readers of this blog will know, this stored procedure was deprecated some years ago at the outset of the OpenInsight 10 project, and it is now basically a thin wrapper around several SYSTEM and FILESYSTEM object methods. It supports the following directory management methods:
When called in Event Context each of these methods forwards the request to the FILESYSTEM object. When called outside of Event Context these methods call the following stored procedures instead:
See below for more details on these.
This stored procedure was deprecated in favor of RTI_OS_Dir as it is very platform specific and makes use internally of RTI_OS_Dir, Utility, and an OLE interface to the Windows Shell, making it less performant than calling those procedures directly. Like Utility it is still supported but will not be updated further with any new functionality.
This stored procedure is a simple wrapper around the RTI_OS_Dir DIREXISTS method and so has been deprecated, but can still be used by existing code.
This venerable stored procedure is a DLL function that calls the Windows API CreateDirectory function. It has been deprecated in favor of the RTI_OS_Dir MAKEDIR method but can still be used by existing code.
These three functions form part of the original internals of the Utility stored procedure, and use a “raw” C interface that requires explicit null-terminated strings. They are all still available to use, but are deprecated in favor of the RTI_OS_Dir MAKEDIR, REMOVEDIR and RENAMEDIR methods.
As you can see, version 10 has pulled together the many different historical methods of directory management and consolidated them into two entities that can be used dependent on the execution context. These should be the preferred methods of directory management in your OpenInsight applications.
(As of the time of writing the full documentation for RTI_OS_DIR and GET_SPECIAL_FOLDER is in preparation and will be available shortly. All methods described here apply to version 10.2.3 and later.)
(EDIT: Full documentation for RTI_OS_DIR and GET_SPECIAL_FOLDER is now live on the Revelation Wiki.)
First released with OpenInsight v10.2, the WEBVIEW object is a control that wraps the Microsoft WebView2 Edge Browser control and allows you to embed web technologies (HTML, CSS and JavaScript) in your OpenInsight forms.
This is just a quick post to let you know that the full documentation has been uploaded and can be found here:
One of the new features that was added to the ListBox and TreeListBox controls in version 10 was the ability to use “in-place” editing on the items in the same manner as the Windows Explorer when you press “F2” or double-click an item. If you’ve done any work with the OpenInsight Menu Designers you will have seen this capability in action.
The READONLY property
Enabling in-place editing is as simple as setting the READONLY property to False – once you’ve done that the user can press “F2” while using the control and edit the text of the currently selected item. Pressing “Enter” when editing will update the item text (as will losing focus or selecting another item), while pressing “Esc” will abandon the changes. Obviously that’s a very simple take on the topic and hardly worth a blog post in and of itself, so next we’ll take a look at some of the properties, methods and events that you can use to tailor the editing process.
The EDITING property
This is a simple boolean property that returns TRUE$ if an item is being edited.
The EDITORHANDLE property
This property returns the HANDLE of the editor control if an item is being edited.
The EDITKEY property
By default, hitting “F2” on an item puts the control into “edit mode”, just like the Windows Explorer. However, if you wish to change this then you may use the EDITKEY property to select another key instead. The edit key is a Windows virtual key code and constants for these codes can be found in the MSWIN_VIRTUALKEY_EQUATES insert record.
The EDITOPTIONS property
This property allows you to fine-tune some of the validation options for the editor:
At runtime this property is an @fm-delimited array – constants for use with this property can be found in the PS_LISTBOX_EQUATES insert record.
The INCLUDEEDITORTEXT property
By default getting item text from the ListBox (e.g. via the LIST property) will include the text from an item currently being edited, even if that edited text has not yet been saved. Setting this property to FALSE$ ensures that the returned item text ignores the value in the editor instead.
The BEGINEDIT method
This method allows you to programmatically put the ListBox into edit mode, as if the user had pressed “F2” (or whatever value the EDITKEY property is set to). You may specify the index of the item to edit, otherwise it will default to the current item.
RetVal = Exec_Method( CtrlEntID, "BEGINEDIT", itemIndex )
The ENDEDIT method
This method allows you to programmatically stop the item editing process, optionally allowing any changes to be accepted as if the “Enter” key had been pressed (the default is to reject any changes as if the “Esc” key had been pressed).
RetVal = Exec_Method( CtrlEntID, "ENDEDIT", bUpdate )
The ITEMEDITING event
This event is raised when an item it put into edit mode, and passes the index of the item as a parameter:
bForward = ITEMEDITING( CtrlEntID, CtrlClassID, ItemIndex )
The ITEMCHANGED event
This event is raised when the item is updated via the editor, i.e. the user hit the “Enter” key, the control lost the focus, or the EDITEDIT method was used with the bUpdate parameter set to TRUE$. The event passes the index of the item that has changed as well as the old and new data:
bForward = ITEMCHANGED( CtrlEntID, CtrlClassID, ItemIndex, |
NewData, |
PrevData )
The ITEMUNCHANGED event
This event is raised when an item leaves edit mode without being updated, i.e. the user hit the “Esc” key or the EDITEDIT method was used with the bUpdate parameter set to FALSE$. The event passes the index of the item that was being edited:
bForward = ITEMUNCHANGED( CtrlEntID, CtrlClassID, ItemIndex )
So that wraps up this short post on ListBox editing – we’re sure that you’ll find many useful ways of implementing this new feature when expanding your application’s functionality.
OpenInsight 10.2 introduces a new control type called HTTPSERVER, which provides a lightweight HTTP web-server control for use in your applications.
Using the control is a simple process:
So, if you set up a HTTPSERVER on port 8089 and execute the form you can use a browser to communicate with it via a URL something like this:
http://localhost:8089/path1/path2/test?arg1=this&arg2=that
As you can see, setting up the control is fairly easy, but the bulk of the work needs to be done in the HTTPREQUEST event where you examine the contents of the request and return the appropriate content.
The event has the following interface:
bForward = HTTPREQUEST( CtrlEntID, |
CtrlClassID, |
RequestID, |
RequestHeaders )
And the following parameters:
| Name | DESCRIPTION |
|---|---|
| CtrlEntID | ID of the HTTP Server control firing the event |
| CtlrClassID | Type of control firing the event – this is always “HTTPSERVER” |
| RequestID | Unique identifier for returning a response to the client – this is used with the various “SETRESPONSE” methods that set response data. |
| RequestHeaders | An @FM delimited dynamic array of data that describes the request. The format is similar to the HTTPRequest argument used in OECGI programming. The full format is described in the PS_HTTPSERVER_EQUATES insert record. |
As mentioned above, the RequestHeaders parameter describes the details of the request using a format similar to the format used in OECGI programming. There are some differences that are worth highlighting however:
Note that out of the box we do not enforce any restrictions or framework on how you handle the request – compare this to classic OECGI programming where the “PathInfo” field is used to determine which “INET_” procedure is executed to fulfill it (via the RUN_INET_REQUEST stored procedure) There is no such requirement with the HTTPSERVER control, and you may create your own framework if you wish (although see the note on RTI_RUN_HTTPSERVER_REQUEST below).
There are several methods described below that you may use to process the content that you return to the client.
Note: With each of these you must use the unique RequestID parameter passed to you in the HTTPREQUEST event.
E.g. Returning HTML in the HTTPREQUEST event
ReposID = @AppID<1> : "*DOC*HTML*INDEX_PAGE" HTML = Repository( "ACCESS", ReposID ) Call Exec_Method( CtrlEntID, "SETRESPONSECONTENT", RequestID, HTML ) Call Exec_Method( CtrlEntID, "SENDRESPONSE" )
E.g. Returning a file in the HTTPREQUEST event
ReposID = @AppID<1> : "*DOC*HTML*INDEX_PAGE" FilePath = Repository( "GETSUBKEY", ReposID ) Call Exec_Method( CtrlEntID, "SETRESPONSEFILE", RequestID, FilePath ) Call Exec_Method( CtrlEntID, "SENDRESPONSE" )
Gets the raw content sent to the server by the client as part of a POST or PUT request.
ReqContent = Exec_Method( CtrlEntID, "GETREQUESTCONTENT", RequestID )
Returns raw content for the response as set via a previous call to the SETRESPONSECONTENT method.
RspContent= Exec_Method( CtrlEntID, "GETRESPONSECONTENT", RequestID )
Returns details for a response cookie as set via a previous call to the SETRESPONSECOOKIE method (see below for the CookieVal format).
CookieVal = Exec_Method( CtrlEntID, "GETRESPONSECOOKIE", RequestID, |
CookieName )
Returns the name and path of the response content file set with a previous call to the SETRESPONSEFILE method.
RspFile = Exec_Method( CtrlEntID, "GETRESPONSEFILE", RequestID )
Returns the details for a response header as set via a previous call to the SETRESPONSEHEADER method.
HeaderVal = Exec_Method( CtrlEntID, "GETRESPONSEHEADER", RequestID, |
HeaderName )
Returns the HTTP status code of the response (defaults to 200).
RspStatus = Exec_Method( CtrlEntID, "GETRESPONSESTATUS", RequestID )
Returns TRUE$ if the specified port is in use on the local machine.
InUse = Exec_Method( CtrlEntID, "ISPORTINUSE", Port, IPv6 )
Sends the response back to the client. This method should be called when you have finished setting the response details (Note that this is called by the promoted system HTTPREQUEST handler in case you forgot to do it in your own code!).
RspSent = Exec_Method( CtrlEntID, "SENDRESPONSE", RequestID )
Sets the content to return to the client, such as a string containing an HTML page.
SetOK= Exec_Method( CtrlEntID, "SETRESPONSECONTENT", RequestID, |
Content )
Sets a cookie that is returned to the client via the “Set-Cookie” header.
SetOK = Exec_Method( CtrlEntID, "SETRESPONSECOOKIE", RequestID, |
CookieName, |
CookieValue )
CookieValue is an @fm-delimited array formatted as follows:
<1> Value
<2> Path
<3> Domain
<4> Expires (internal date format)
<5> Max Age (seconds)
<6> Secure (TRUE$/FALSE$)
<7> HttpOnly (TRUE$/FALSE$)
<8> SameSite
If you have a file that contains the content you wish to return then you should use this method to let the server read the file and return it to the client itself. This offers better performance than reading the contents via Basic+ and using the SETRESPONSECONTENT method as it avoids any unnecessary copying of data.
SetOK = Exec_Method( CtrlEntID, "SETRESPONSEFILE", RequestID, |
FileNamePath )
Sets a response header and value to be returned to the client.
SetOK = Exec_Method( CtrlEntID, "SETRESPONSEHEADER", RequestID, |
HeaderName, |
HeaderValue )
Sets the HTTP status code for the response (200, 404, 500 etc).
SetOK = Exec_Method( CtrlEntID, "SETRESPONSESTATUS", RequestID, |
StatusCode )
As part of version 10.2 we have included a sample HTTPREQUEST event handler called RTI_RUN_HTTPSERVER_REQUEST which you can examine and copy for your own applications if you wish. It emulates the core behavior of the OECGI RUN_INET_REQUEST handler in that it uses the “PathInfo” field to determine the stored procedure to fulfill the request. In this case it looks for a matching procedure that has the prefix “HTTPSVR_” and we have included a couple of example “HTTPSVR_” procedures for you to review as well.
With the addition of the HTTPSERVER control it is now possible to provide HTML content directly from your application, and also provide a means of web-development directly from your desktop without necessarily needing to install a dedicated web-server like IIS.
It is also a good solution for when you want to provide local HTML content in your application’s user-interface via an embedded browser control, because it can avoid the usual security restrictions that browsers enforce for such scenarios.
With the release of version 10.1 a new control type called DIRWATCHER (“Directory Watcher”) has been added to OpenInsight. This is a fairly simple control which allows you to monitor one or more directories on your system and then receive notifications when the contents are changed.
Using the control is very straightforward:
We’ll take a quick look at each of these methods and events below along with a couple of important properties:
This method allows you to specify a directory to monitor along with some optional flags. It may be called multiple times to watch more than one directory.
bSuccess = Exec_Method( CtrlEntID, "WATCHDIR", DirName, bSubtree, Flags )
| Parameter | Description |
|---|---|
| DirName | (required) Specifies the name of the directory to watch. It should be a fully qualified, non-relative directory path. |
| bSubtree | (optional) Set to TRUE$ to monitor all sub-directories beneath DirName as well. Defaults to FALSE$. |
| Flags | (optional) A bit-mask value containing a set of flags that denote the events to monitor in the directories. It defaults to the following flags: FILE_NOTIFY_CHANGE_FILE_NAME$ FILE_NOTIFY_CHANGE_LAST_WRITE$ FILE_NOTIFY_CHANGE_CREATION$ The flag values are specified in the MSWIN_FILENOTIFY_EQUATES insert record, |
This method returns TRUE$ if successful, or FALSE$ otherwise.
This method stops the control monitoring its specified directories.
bSuccess = Exec_Method( CtrlEntID, "STOP" )
This method returns TRUE$ if successful, or FALSE$ otherwise.
(Note – to resume directory monitoring after the STOP method has been called the WATCHDIR method(s) must be executed again).
This event is raised when changes have been detected in the monitored directories.
bForward = CHANGED( NewData )
This event passes a single parameter called NewData which contains an @vm-delimited list of changed items (i.e. notifications). Each item in the list comprises an “action code” and the name and path of the affected file, delimited by an @svm.
Action codes are defined in the MSWIN_FILENOTIFY_EQUATES insert record like so:
equ FILE_ACTION_ADDED$ to 0x00000001 equ FILE_ACTION_REMOVED$ to 0x00000002 equ FILE_ACTION_MODIFIED$ to 0x00000003 equ FILE_ACTION_RENAMED_OLD_NAME$ to 0x00000004 equ FILE_ACTION_RENAMED_NEW_NAME$ to 0x00000005
If a monitored directory experiences a high volume of changes (such as copying or removing thousands of files) it could generate a correspondingly high number of CHANGED events, which in turn could produce an adverse affect on your application and slow it down. In order to deal with this potential issue it is possible to “bundle up” multiple notifications with the NOTIFYTHRESHOLD property into a single CHANGED event so they may be processed more efficiently.
The NOTIFYTHRESHOLD property is an integer that specifies the maximum number of notifications that should be bundled before a CHANGED event is raised.
CurrVal = Get_Property( CtrlEntID, "NOTIFYTHRESHOLD" ) PrevVal = Set_Property( CtrlEntID, "NOTIFYTHRESHOLD", NewVal )
By default it is set to 100.
The NOTIFYTIMER property is an integer that specifies the number of milliseconds before a CHANGED event is raised if the NOTIFYTHRESHOLD property value is not met.
CurrVal = Get_Property( CtrlEntID, "NOTIFYTIMER" ) PrevVal = Set_Property( CtrlEntID, "NOTIFYTIMER", NewVal )
By default it is set to 50 (ms).
If the NOTIFYTHRESHOLD property is set to a value greater than 1 then the control will try to bundle that number of notifications together before raising a CHANGED event. However, when this is set to a high value it is possible that the threshold may not be reached in a timely fashion and the CHANGED event not actually raised.
E.g. If the NOTIFYTHRESHOLD is set to 1000, and only 200 notifications are received then the CHANGED event would not be raised.
To prevent this problem the NOTIFYTIMER property may be used to specify the amount of time after receiving the last notification before a CHANGED event is raised even if the NOTIFYTHRESHOLD is not met.
E.g. in the example above, if the control had a NOTIFYTIMER of 50, then a CHANGED event would be raised 50ms after the last notification (200) was received, even though the NOTIFYTHRESHOLD of 1000 has not actually been met.
The DIRWATCHER control is intended as a “non-visual” control and should probably be hidden at runtime in your own applications. However, it is actually derived from a normal STATIC control so all of the properties and methods that apply to a STATIC apply to the DIRWATCHER as well, and you may use them as normal if you wish.
When executing a long running process in a desktop environment, such as selecting records from a large table, it is important for the user to be able to interact with the application even though it is busy. A common example of this is displaying a dialog box that shows the progress of an operation and allowing the user to cancel it if they wish. Failure to provide this ability generally results in user frustration, or causes the dreaded “Window Ghosting” effect where Windows changes a form’s caption to “Not Responding” (this is never a good look, and usually ends in a quick visit to the Task Manager to kill the application).
In order to avoid this problem we have to allow the application to check for user interaction, a process usually referred to as “yielding” (hence the awful title of this post), and this time we’ll take a look at the various options available to accomplish this and the differences between them. Before we go any further however, here’s a little background information on how OpenInsight runs beneath the hood so that you can appreciate how messages and events are handled.
OpenInsight.exe (aka. the “Presentation Server”, or “PS”) has a main thread (the “UI thread”) with a Windows message loop that manages all of the forms and controls, and it also has an internal “event queue” for storing Basic+ events that need to be executed. The PS also creates an instance of the RevEngine virtual machine (“the engine”), which has its own thread (the “engine thread”) with a Windows message loop, and is responsible for executing Basic+ code.
When the PS needs to execute an event it passes it to the engine directly if possible, otherwise it adds the details to the event queue and then posts a message to itself so the queue can be checked and processed when the engine is not busy. When the engine receives the event data it is executed on the engine thread. Stored procedures such as Get_Property, Set_Property, and Exec_Method provide a way for the Basic+ event to communicate back to the PS to interact with the user interface controls and forms during its execution.
The key point to note here is that Basic+ event code runs in a different thread to the UI, so while the engine thread is processing the event, the UI thread is basically waiting for it to finish, and this means that it may or may not get chance to process it’s own message loop. This is where the problems can begin, and why the need for a yielding ability, because:
So, now we know why “window ghosting” happens we can take a look at the various options to deal with it.
This is a direct call to the Windows API Sleep function, and it puts the engine thread to sleep for at least the specified number of milliseconds. However, while calling this will allow Windows to schedule another thread to run, there’s no guarantee that this would be the UI thread, so it’s not really a good solution.
This is a simple wrapper around the Windows API Sleep function, with a sleep-time of 10ms. This suffers from the same disadvantages discussed for MSWin_Sleep above (This function remains for backwards compatibility with early versions of of OI and Windows).
This is a direct call to the Windows API SwitchToThread function which forces Windows to schedule another thread for execution. Like MSWin_Sleep and WinYield there’s no guarantee that this would cause the UI thread to run, so again it’s not a great solution.
This is a new method in version 10.1 that performs two tasks that solve the problem:
One possible drawback here is that waiting events are also processed, and this might not be a desirable outcome depending on what you are doing. In this case there is another method called PROCESSWINMGS that should be used instead.
(FYI – The PROCESSEVENTS method is essentially a wrapper around the venerable Yield() stored procedure, but allows the same functionality to be called via the standard object-based method API rather than as a “flat” function. Yield() itself is still and will be fully supported in the product).
This is a new method in version 10.1 that tells the PS to process it’s Windows message queue but it does not process any Basic+ events, i.e. it prevents the “ghosting” effect but does not cause events to fire before your application is ready for them.
Version 10.1 has added more functionality to help you avoid the dreaded “Not Responding” message via the PROCESSWINMGS and PROCESSEVENTS methods, and hopefully, armed with the information above, this will help you to write better integrated desktop-applications.
The next release of OpenInsight (version 10.1) includes a couple of updates to the FILEPREVIEW control as a result of using it extensively “out in the field”, and in this post we thought we’d look at these changes and why we made them in case you encounter the same issues yourself.
As mentioned in this previous post, the FILEPREVIEW control relies on third-party DLLs to provide “preview handlers” that OpenInsight uses to display the contents of files such as Word or PDF documents. However, what we found is that not all of these handlers are created equal and some can be quite problematic – in our case the Adobe PDF preview handler (supplied with the Adobe PDF Reader) proved to be one of these.
When the handler is loaded by OpenInsight one of the things that must be specified is the context in which it is created – this can be “in-process” (which means it runs in the same address space as OpenInsight) or “out-of-process” (which runs as a separate executable). This is done internally by a set of flags, and when you use the FILENAME property these flags are set to their default values which, until recently, had proved sufficient. However, extensive testing (by Martyn at RevSoft) found that the Adobe PDF preview handler had stopped working, and further investigation revealed that at some point recent versions of this had become sensitive to these context flags, so the first change we made was to provide a new SETFILENAME method, which allows you to set the flags yourself if need be:
RetVal = Exec_Method( CtrlEntID, "SETFILENAME", FileName, FileExtn, |
ContextFlags )
| Parameter | Required | Description |
|---|---|---|
| FileName | No | Contains the name and path of the file to preview (can be null to remove the preview). |
| FileExtn | No | Specifies an explicit extension to use, overriding the extension passed in the FileName parameter. |
| ContextFlags | No | Specifies a bit-mask of “CLSCTX_” flags used to create the preview handler. Defaults to: BitOr( CLSCTX_INPROC_SERVER$, CLSCTX_LOCAL_SERVER$ ) (Equates for these flags can be found in the MSWIN_CLSCTX_EQUATES insert record) |
If the returned value is 0 then the operation was successful, otherwise this is an error code reported from Windows and can be passed to the RTI_ErrorText stored procedure to get the details:
E.g.
// Load the PDF in an out-of-process context
$Insert MSWin_ClsCtx_Equates
RetVal = Exec_Method( CtrlEntID, "SETFILENAME", "C:\Temp\Test.PDF", "",
CLSCTX_LOCAL_SERVER$ )
If RetVal Then
// Problem...
ErrorText = RTI_ErrorText( "WIN", RetVal )
End
Even with this you may still find problems, as the above code was fine for me, but not for Martyn, even though the PDF preview handler worked fine in Windows Explorer itself for both of us! So, we could only conclude that Adobe made sure that the handler worked with the Windows Explorer, but they were less concerned about third party applications (Per-monitor DPI settings are also not supported by the preview handler which is disappointing as well).
After some more testing we decided to switch to the Foxit PDF reader which worked as expected, so we would recommend using this for PDF previewing in future if needed.
One other change we made was to remove the FILENAME property from the Form Designer so that it could not be set at design-time due to the following reasons:
So, for v10.1 we have provided a new SETFILENAME method to provide a better interface for file-previewing which gives more feedback and more control, and you should use this in preference to the FILENAME property.
We have also found the Adobe PDF preview handler to be somewhat temperamental in use so would recommend the Foxit preview handler instead if you have problems with the former (Note however, that other preview handlers we use regularly, such as Word, Excel and PowerPoint have all worked well without any issues so far).
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:
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 )
| Parameter | Required | Description |
|---|---|---|
| OwnerForm | No | Name of a form to use as a parent for displaying UI messages. |
| Operation | No | Operation to be performed; “open”, “edit”, “print” etc. |
| File | Yes | File to perform the operation on. |
| Parameters | No | If File is an executable file this argument should specify the command line parameters to pass to it. |
| WorkingDir | No | The default working directory for the operation. If null the current working directory is used. |
| ShowCmd | No | Determines how an application is displayed when it is opened (as per the normal VISIBLE property). |
The return value is the value returned by ShellExecuteW.
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 )
| Parameter | Required | Description |
|---|---|---|
| Hwnd | Yes | Handle of a window to use as a parent for displaying UI messages, or null (0) to use the desktop. |
| Verb | No | Operation to be performed; “open”, “edit”, “print” etc. |
| File | Yes | File to perform the operation on. |
| Parameters | No | If File is an executable file this argument should specify the command line parameters to pass to it. |
| Directory | No | The default working directory for the operation. If null the current working directory is used. |
| nShow | No | Determines how an application is displayed when it is opened (as per the normal VISIBLE property). |
| hProcess | No | Returns the handle to the new process. |
The return value is the value returned by ShellExecuteExW.
This is the “raw” DLL function that is included with OI10, and the definition can be found in 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 )
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:
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.
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.
In a recent post we provided a preview of the OpenInsight IMAGE API documentation for the upcoming release of version 10.1. As that proved quite popular we thought we’d provide some more, this time dealing with the Common GUI API (i.e. the basic interface that virtually every GUI object supports) and the WINDOW object API – two core areas of OI GUI programming.
One thing you may notice as you look through these documents is the addition of many new methods, such as SHOWOPTIONS or QBFCLOSESESSION – this is an attempt to tidy up the API into a more logical and coherent format that is a better fit for an object-based interface.
As we went through the product in order to document it, it became very apparent that there were many instances where events were being used to mimic methods, such as sending a WRITE event to save the data in a form, or sending a CLICK event to simulate a button click and so on. In object-based terminology this sort of operation would be performed by a method, which is a directive that performs an action – the event is a notification in response to that action. So, for example, you would call a “write” method to save your data and the system would raise a “write” event so you could deal with it.
Of course, this distinction will probably not bother many developers – just API purists like myself, but this does have another advantage if you like to use Object Notation Syntax (I do) – you can now perform actions such as reading and writing form data by using the”->” notation, whereas before you would have to use the Send_Event stored procedure which essentially breaks the object-based paradigm.
So instead of:
Call Send_Event( @Window, "WRITE" )
you would use the form’s WRITEROW method instead:
@@Window->WriteRow( "" )
which is a more natural fit for this style of programming.
(It is also easier to explain to new OI programmers who are used to other object-based languages and environments where everything is properties, methods and events).
This brings us finally onto the topic of Stored Procedures and the object API, where several of these also fulfill the role of methods. For example, take the venerable Msg stored procedure used to display a message box for a parent form – a different way of treating this would be to have a SHOWMESSAGE method for the parent form rather than using a “raw” Msg call. Likewise for starting a new form: instead of using the raw Start_Window procedure, the SYSTEM and WINDOW objects now support a STARTFORM method instead.
Of course, none of this changes your existing code, nor is it enforced, it’s just something you can use if and when you wish to. However, even if my API pedantry hasn’t persuaded you to change your coding style, some of the new methods are worth investigating as they provide a better opportunity for us to extend the product’s functionality further – take a look at the WINDOW READROW and WRITEROW methods for an example of this – they support new features that we couldn’t do with just sending events.
In any case, here are the links – hopefully some light reading for your weekend!