Home page Home page Home page Home page
Pixel
Pixel Header R1 C1 Pixel
Pixel Header R2 C1 Pixel
Pixel Header R3 C1 Pixel
Pixel
By Captain C | Wednesday 30 September 2009 09:00 | 0 Comments
As documented the TEXT_BY_POS message may be used to retrieve the contents of a nominated cell. What is not documented is the fact that you can use it to update the contents of a cell as well. It's simply a matter of adding an extra parameter containing the data you wish to set.

E.g.

0001     * // Example to show setting cell contents with the
0002     * // TEXT_BY_POS message.
0003     
0004     * // Set the contents of cell [3,4]
0005     colNo    = 3
0006     rowNo    = 4
0007     cellText = "New Cell Data"
0008     
0009     call send_Message( @window : ".TABLE_1", |
0010                        "TEXT_BY_POS",        |
0011                        colNo,                |
0012                        rowNo,                |
0013                        cellText ) 


OpenInsight trivia bonus: The EditTable CELLPOS property is a simple wrapper around the TEXT_BY_POS message.

Labels: , ,

By Captain C | Thursday 24 September 2009 09:15 | 0 Comments
While recently adding a new window into our internal admin system we ran into a subtle problem with the CHANGED event and the NOTIFYPOS property.

NOTIFYPOS is an EditTable property that is always updated to contain the coordinates of the last cell to raise an event, but this is not restricted to the CHANGED event: Any EditTable event that is cell-oriented will also update NOTIFYPOS when triggered, common examples being DBLCLK and POSCHANGED.

In our case the sequence of events ran like so:
  1. The user edited data in a cell and hit the down arrow to move to the cell beneath.

  2. The EditTable registered that the data had changed and set NOTIFYPOS to the edited cell position.

  3. The EditTable raised a CHANGED event.

  4. The EditTable registered that the cell position had changed and set NOTIFYPOS to the position of the 'new' cell.

  5. The EditTable raised a POSCHANGED event.

  6. The Basic+ event handler for the CHANGED event executed - but now NOTIFYPOS was pointing to the 'new' cell, not the edited one - and our code mangled the data!

Now you'd think that step (6) would actually have taken place straight after step (3) but unfortunately that's not the case due to the way that OpenInsight communicates with OpenEngine to run Basic+ event handlers.

Normal Basic+ event handlers are executed in an asynchronous fashion, i.e. they are not executed directly when the notification is received, but are placed into a queue and executed when the queue is processed by the application's "message pump" (For those of you familiar with the Windows API they are dispatched via the PostMessage function).

The solution to the problem was to ensure that our Basic+ event handler ran in a synchronous fashion instead - i.e. it should be executed as soon as OpenInsight is notified by the EditTable that the CHANGED event has taken place. That way we know that NOTIFYPOS will still contain the correct coordinates when our handler runs.

Doing this was simply a matter of qualifying the CHANGED event with the synchronous flag in the window CREATE event handler like so:

0001     * // This is from the CREATE event handler for the window
0002       
0003     * // Set the synchronous flag for the CHANGED event 
0004     * // of the EDT_DETAILS edit table.
0005     
0006     tmp    = TRUE$ ; * // .. to ensure event is registered.
0007     tmp<4> = TRUE$ ; * // Sync flag -> TRUE$  == Synchronous
0008                    ; * //              FALSE$ == Asynchronous
0009     
0010     call send_Message( @window : ".EDT_DETAILS", |
0011                        "QUALIFY_EVENT",          |
0012                        "CHANGED",                |
0013                        tmp )


The new sequence of events now ran like this:
  1. The user edited data in a cell and hit the down arrow to move to the cell beneath.

  2. The EditTable registered that the data had changed and set NOTIFYPOS to the edited cell position.

  3. The EditTable raised a CHANGED event.

  4. The Basic+ event handler for the CHANGED event executed

  5. The EditTable registered that the cell position had changed and set NOTIFYPOS to the position of the 'new' cell.

  6. The EditTable raised a POSCHANGED event.


Labels: , ,

By Captain C | Wednesday 23 September 2009 11:04 | 0 Comments
For any of you using our Basic+ Source code publisher we've now got an update to correct a Notes parsing issue when creating content for the Revelation Forum.

You can download version 1.5.1 from here in standard RDK format or here as an NSIS installer version.

Labels: , , ,

By Captain C | Tuesday 22 September 2009 09:00 | 0 Comments
Many of the applications we write need to display things like option dialog boxes near a specific control. In most cases this is quite easy to handle as we can easily obtain the SIZE property of a control and work out our positioning from that. A slightly more difficult task is to position something relative to an EditTable cell because OpenInsight doesn't expose this information as a property or a method.

We've seen many attempts to calculate cell coordinates in Basic+ - we've even done it a few times ourselves and it's quite a pain, having to take into account all the different styles of the edit table, the width of columns, which columns are hidden and so forth.

Well, there's a really easy way to do this, and that's by asking the EditTable itself what the coordinates are via the standard Windows API SendMessage function. We just need to know what message to send to the EditTable.


DTM_READCELLRECT

The message we need is called DTM_READCELLRECT, and it returns the coordinates of the cell identified via the ACCESSPOS property. All we need to do is pass it the address of a RECT structure to fill in, which we then translate into a dynamic array which we can use further.

Here's a simple function to demonstrate this:

0001  compile function edt_GetCellRect( edtID, colNo, rowNo )
0002  /*
0003     Author   : Darth C, Sprezzatura Actual
0004     Date     : Sep 09
0005     Purpose  : Function to return edit table cell coordinates
0006     
0007     Parameters
0008     ==========
0009     
0010       edtID    -> Fully qualified name of the edit table 
0011       
0012       colNo    -> Column number of the target cell. Defaults 
0013                   to currentPos 
0014       
0015       rowNo    -> Row number of the target cell. Defaults to 
0016                   currentPos
0017       
0018     Returns
0019     =======
0020     
0021       Returns the edit table cell coordinates as per the RECT
0022       structure layout, i.e.
0023       
0024          <1> Left
0025          <2> Top
0026          <3> Right
0027          <4> Bottom
0028       
0029       Note these coordinates are relative to the Edit Table 
0030       CLIENT area, NOT the desktop/screen!
0031       
0032  */
0033     declare function sendMessage, blank_Struct, struct_To_Var
0034     declare function get_Property
0035     
0036     equ DTM_READCELLRECT$ to 1079      ; * // (WM_USER + 55)
0037     equ DTA_ACCESS$       to 0x0000
0038     
0039     if assigned( edtID ) else edtID = ""
0040     if assigned( colNo ) else colNo = ""
0041     if assigned( rowNo ) else rowNo = ""
0042     
0043     if len( edtID ) else
0044        return ""
0045     end
0046     
0047     if len( colNo ) and len( rowNo ) then
0048        call set_Property( edtID, "ACCESSPOS", colNo : @fm : rowNo )
0049     end else
0050        * // Use the current "caret" position - ensure ACCESSPOS
0051        * // is sync'd with CARETPOS
0052        call set_Property( edtID, "ACCESSPOS",               |
0053                           get_Property( edtID, "CARETPOS" ) )
0054     end
0055     
0056     * // Create a blank RECT structure for the edit table
0057     * // to fill for us and lock it
0058     rc = blank_Struct( "RECT" )
0059     lockVariable rc as BINARY
0060     
0061     * // Send the DTM_READCELLRECT message. 
0062     * //
0063     * // The third parameter (wParam) contains a value that 
0064     * // tells the edittable which cell we want. DTA_ACCESS
0065     * // means "use the ACCESSPOS property".
0066     * //
0067     * // We send the address of the RECT structure to fill in
0068     * // as the last parameter (lParam).
0069     call sendMessage( get_Property( edtID, "HANDLE" ), |
0070                       DTM_READCELLRECT$,               |
0071                       DTA_ACCESS$,                     |
0072                       getPointer( rc ) )
0073     
0074     * // Unlock and translate the structure to a
0075     * // dynamic array
0076     unlockVariable rc
0077     rc = struct_To_Var( rc, "RECT" )
0078     
0079  return rc


(The more pedantic amongst you may notice that we didn't reset ACCESSPOS after we updated it. The reason for this is simple - every low-level function in the EditTable updates ACCESSPOS to the required coordinates before executing, and you should never assume ACCESSPOS is at the correct coordinates - always set it yourself before use!)

You can download a text version of edt_GetCellRect here

Labels: , ,

By Captain C | Sunday 20 September 2009 12:05 | 0 Comments
Whenever we have to deal with raw pointers in Basic+, as we did recently, we frequently end up using the GetPointer function. The documentation for GetPointer states that you should always use the LockVariable statement to "lock the string" before retrieving the pointer, but what does that actually mean?


In the beginning...

OpenInsight was originally designed as a 16-bit application running on a 16-bit platform (Windows 3.x), and that platform had a habit of moving chunks of memory around for performance reasons which meant that direct pointers to the memory could be made invalid unless certain steps were taken to guard against this.

So that pointers could be used safely in a Windows 3.x, program memory was tracked by handles instead, and when we wanted a pointer we "locked" the memory via it's handle and got a pointer to it which we could then use, because Windows promised not to pull the bytes out from under our feet.

This is essentially what the Basic+ LockVariable statement did. It marked the memory section used by the Basic+ variable as unmoveable, which meant that a subsequent GetPointer() statement got a valid pointer to it. Afterwards we used the UnlockVariable statement to release the memory section so Windows could move it as needed.


Did you say "did"?

Yep - Memory management changed with the advent of 32-bit Windows operating systems - each application (or "Process" to use the proper term) could access memory in a linear fashion via a virtual address space. This basically meant that the OS handled mapping all pointers to "real memory" internally - no need for any more handle and locking shenanigans.

This of course rendered the primary function of the LockVariable statement obsolete on 32-bit operating systems, but we can't just do away with it entirely because it can perform one other very important function: variable-type coercion.


Basic+ Variable Typing

As we mentioned in our previous post variables in Basic+ are typeless - i.e. they can change their type at runtime based on the context in which they are used. When we're dealing with a typed language like C/C++, we must ensure that any Basic+ variables we pass to a DLL function are actually held internally in the correct binary format.

This is where the other function of the LockVariable statement comes into play - it allows us to specify how we want the Basic+ variable to be typed.

E.g.

0001     * // Example DLL function call with the following prototype
0002     * // that wants a pointer to an Integer
0003     * //
0004     * // VOID STDCALL SomeDLLFunc( LPVOID )
0005      
0006     someArray = "3" : @fm : "43"
0007     
0008     someNum = someArray<1> ; * // someNum is held as a string. To
0009                            ; * // force it to a integer we can do
0010                            ; * // this:
0011                        
0012     * // Call lockVariable to make sure we have an integer
0013     lockVariable someNum as INT
0014    
0015     call someDLLFunc( getPointer( someNum ) )
0016     
0017     unlockVariable( someNum ) 


Likewise for a string we could do this:

0001     * // Example DLL function call with the following prototype
0002     * // that wants a pointer to a string, and it's length in 
0003     * // bytes
0004     * //
0005     * // VOID STDCALL SomeDLLStrFunc( LPVOID, UINT )
0006      
0007     someStr = 3 + 7 + 4567940 ; * // someStr is held as a number. To
0008                               ; * // force it to a string we can do
0009                               ; * // this:
0010                        
0011     * // Call lockVariable to make sure we have a string
0012     lockVariable someStr as CHAR
0013    
0014     call someDLLStrFunc( getPointer( someStr ), getByteSize( someStr ) )
0015     
0016     unlockVariable( someStr ) 


Of course there are other ways of coercing a variable to a certain type:

  1. Adding 0 to a numeric string will ensure it is held in numeric format

  2. Concatenating an empty string to a numeric variable will ensure it's held as a string.

We must beware of using method (1) above to ensure a number however, because Basic+ holds numeric variables in one of two binary formats internally - one as an integer, the other is as a floating point number. The "adding 0" method will not let us specify which one to use - only the LockVariable statement can do that, so when we need to ensure a type we must use it in these circumstances.


So do we really need LockVariable for strings then?

Providing that we're sure of how Basic+ is holding the format of a variable we can actually get away without using the LockVariable statements in our programs, and call GetPointer directly.

However, we would recommend using it in your programs in case the memory model that OpenInsight uses changes at some point in the future. For example, if it changed to a fully garbage-collected environment akin to the .NET runtime, we might need to lock or "pin" the variable in memory. Basically it's just a good habit to adopt.


What about the UnlockVariable statement?

If we're going to use LockVariable then we must also ensure that we call UnlockVariable. Right now it does absolutely nothing whatsoever. Of course that may not always be the case, so we'd recommend you use this too.

Labels: , , ,

By Captain C | Tuesday 15 September 2009 16:28 | 0 Comments
Welcome to part 3 of the "Strings and Things" series on OpenInsight DLL prototyping. So far we've covered the theory of how Windows defines functions that use ANSI and Unicode versions (in Part 1), and we've also looked at the standard way of calling those functions from Basic+ (in Part 2).

This time we're going to look at another way of passing string data to a Windows API function, but we'll show you how to handle the entire process yourself, and afterwards we'll look at why this is sometimes necessary.

(Note: This article assumes you are familiar with the basics of DLL prototyping in OpenInsight. If not please consult the OpenInsight on-line help for more details)


Passing Basic+ variables as string parameters - The Long Way Round

Previously we looked at a method whereby OpenInsight took care of all the low-level details for you when passing string parameters, but you do have the option to handle this process yourself via the following steps:

  1. Prototype the DLL function so that it expects a raw pointer to be passed rather than a Basic+ variable.

  2. Ensure that the variable is null-terminated - i.e. that it ends with a Char(0).

  3. Encode the Basic+ variable as a Unicode or ANSI string depending on the function you are passing the data to, taking into account your application's UTF8 mode.

  4. Get a pointer to the variable that you want to pass to the DLL function.

  5. Invoke the function

  6. Clean up any loose ends.

It appears like quite a lot of work but it's not as bad as it looks when you put it into practice.


Creating the LPVOID prototype

The first task you have to do is prototype the DLL function, and for now we'll use the same Unicode SetWindowText function that we used before so you can see the difference. Here's how it's documented:

BOOL SetWindowText( HWND hwnd, LPCTSTR lpString );


Which we know is actually means this for the Unicode version:

BOOL SetWindowTextW( HWND hwnd, LPCWSTR lpString );


So far so good but as you're handling the string passing yourself the prototype must be created like this instead:

INT STDCALL SetWindowTextW( HANDLE, LPVOID )


Notice that for the string parameter we are now using the LPVOID prototype rather than the LPWSTR prototype as we did in Part 2. This means that when we call SetWindowTextW OpenInsight is expecting us to pass a pointer to the string data rather than the string data itself.

Now we have the prototyped function we can look at using it in a program, but as mentioned above we have to do some preparation to the Basic+ variables before they can be used.


Null termination

Strings used in C use a Char(0) as an end of string marker (i.e. it is "null-terminated"), so you must ensure that any strings you pass also follow this convention - simply appending a Char(0) to the end of the string will suffice here.

0001     * // Null-terminate the string
0002     strToPass = "Some text" : char( 0 )


String Encoding

Before the string is passed to the DLL function you must make sure that it is encoded correctly. OpenInsight provides an easy way to do this via the str_ANSI and str_Unicode functions, which automatically take into account your application's UTF8 setting:

0001     declare function str_Unicode
0002     
0003     * // Ensure the string we are going to pass is in Unicode
0004     * // format
0005     
0006     strToPass = "Some text"
0007     strWide = str_Unicode( strToPass )


The str_ANSI and str_Unicode functions are very useful and save you a bit of coding, otherwise you would have to do something like this to get a Unicode string:

0001     declare function ANSI_Unicode, UTF8_Unicode, isUTF8
0002     
0003     * // Ensure the string we are going to pass is in Unicode
0004     * // format. This is the long way round and emulates the
0005     * // str_Unicode() function
0006     
0007     strToPass = "Some text"
0008     if isUTF8() then
0009        strWide = UTF8_Unicode( strToPass )
0010     end else
0011        strWide = ANSI_Unicode( strToPass )
0012     end


A note on variable typing

Variables in Basic+ are typeless - i.e. they can change their type at runtime based on the context in which they are used (They are actually very similar to the Variant type in Microsoft COM/OLE programming). While having typeless variables is very convenient from a standard Basic+ programming viewpoint, it's not so helpful when you're dealing with a typed language like C/C++, so you must ensure that any Basic+ variables you pass to a DLL function expecting strings are being held internally by OpenInsight as strings also.

There are several ways this can be done but the most common is to use the concatenation operator and append a null variable like so:

0001     * // Adding X and Y below will produce a numeric result 
0002     * // that will be held internally in the engine as a binary 
0003     * // integer
0004  
0005     x = 1
0006     y = 2
0007     z = x + y ; * // z is in a binary numeric format
0008  
0009     z := ""   ; * // z is now in a string format
0010               ; * // (the ASCII character "3" )


For our purposes in the SetWindowText example the act of appending a Char(0) to the variable we are passing performs any required coercion to a string (as would the Unicode encoding functions too actually).


Getting the pointer

All that remains to do now is obtain a pointer to the OI string so it can be passed to the DLL function, and for this there is the aptly-named GetPointer function:

0001     myVar  = "Some text"
0002     
0003      *// Always use LockVariable before GetPointer
0004     lockVariable myVar as CHAR
0005     
0006     * // Get the pointer
0007     pMyVar = getPointer( myVar )
0008     
0009     * // Use the pointer
0010     call someFunc( pMyVar )
0011     
0012     * // Cleanup
0013     unlockVariable myVar


(You'll notice the use of the LockVariable statement here as well - We're going to fully cover LockVariable later in separate post.)


Putting it altogether

And you're now in a position to finally call your DLL function. As an example look at how you would use all this with SetWindowText:

0001     declare function SetWindowTextW, str_Unicode, get_Property
0002  
0003     hwnd    = get_Property( @window, "HANDLE" )
0004     newText = get_Property( @window : ".EDITLINE_1", "TEXT" )
0005  
0006     // Null terminate
0007     newText := char( 0 )
0008  
0009     // Ensure we have a UNICODE string
0010     newText = str_Unicode( newText )
0011  
0012     // Force string type - redundant but make sure
0013     // we're future proof
0014     lockVariable newText as CHAR
0015  
0016     // Get a pointer to the string
0017     pNewText = getPointer( newText )
0018  
0019     // Invoke the function
0020     x = SetWindowTextW( hwnd, pNewText )
0021  
0022     // Cleanup
0023     unlockVariable newText


Clean-up

Any clean-up tasks you may need to perform are somewhat dependant on the actual function called, but the primary cleanup task is to call the UnlockVariable statement if you used the LockVariable statement as you can see in the example above. Another common occurrence is to convert a returned string to the correct OI string type as you can see from the GetWindowText example below:

0001     * // We're using the following User32 DLL prototypes:
0002     * //
0003     * // INT STDCALL GetWindowTextLength( HANDLE )
0004     * // INT STDCALL GetWindowTextW( HANDLE, LPVOID, INT )
0005  
0006     declare function getWindowTextLength, getWindowTextW
0007     declare function str_Unicode, unicode_Str
0008  
0009     hwnd    = get_Property( @window, "HANDLE" )
0010  
0011     * // Find out how much text the window contains in CHARACTERS 
0012     * // (not BYTES) and create a buffer large enough to contain it
0013  
0014     textLen = getWindowTextLength( hwnd )
0015     textBuf = str( char(0), textLen + 1 ) ; * // add space for a 
0016                                           ; * // null terminator!
0017  
0018     * // Make sure that the buffer contains enough space for
0019     * // Unicode chars as we're calling the "W" function
0020     textBuf = str_Unicode( textBuf )
0021  
0022     * // Not needed at the moment but it won't hurt!
0023     lockVariable textBuf as CHAR
0024  
0025     * // Get the pointer
0026     pBuf = getPointer( textBuf )
0027  
0028     * // Get the window text
0029     x = getWindowTextW( hwnd, pBuf, textLen + 1 )
0030  
0031     * // Clean up - we need to ensure the Unicode string we've got 
0032     * // back is translated to UTF8/ANSI
0033     unlockVariable textBuf
0034  
0035     * // Convert the string
0036     textBuf = unicode_Str( textBuf )
0037  
0038     * // Get the text, removing the null terminator which we don't 
0039     * // need in Basic+ 
0040     winText = textBuf[1,char(0)]


So, tell me again why I need to do this?

Well, as well as being a good intellectual exercise and helping understand how OpenInsight works behind the scenes there's one important area where the ability to pass a pointer is really critical - and that's when you need to pass a NULL pointer (i.e. the numeric value '0') to a function.

For example many Windows API functions exhibit special behaviour when passed a NULL pointer such as returning the length of a buffer needed to contain a value (e.g. the GetShortPathName function), so it is quite important to be able to do this. However, you cannot to this with the LPWSTR and LPASTR prototypes we looked at in part 2, because they will always pass a pointer to something even if it's a null OpenInsight variable!

e.g. This will NOT work:

0001     * // We're using the following Kernel32 DLL prototypes:
0002     * //
0003     * // UINT STDCALL GetShortPathNameW( LPWSTR, LPWSTR, UINT )
0004     
0005     declare function getShortPathNameW
0006          
0007     * // Attempt to get the size of the buffer for the short path
0008     longPath  = "c:\temp\somelongfilename.txt"   
0009     shortPath = ""
0010     
0011     bufLen = getShortPathNameW( longPath, shortPath, 0 )
0012     
0013     * // and so on ...


Neither will this:

0001     * // We're using the following Kernel32 DLL prototypes:
0002     * //
0003     * // UINT STDCALL GetShortPathNameW( LPWSTR, LPWSTR, UINT )
0004     
0005     declare function getShortPathNameW
0006          
0007     * // Attempt to get the size of the buffer for the short path
0008     longPath  = "c:\temp\somelongfilename.txt"   
0009     shortPath = 0
0010     
0011     bufLen = getShortPathNameW( longPath, shortPath, 0 )
0012     
0013     * // and so on ...


The first example will end up passing a pointer to a Char(0) (i.e. an empty string), while the second example will pass a pointer to an ASCII '0' character.

The proper way to tackle this is:

0001     * // We're using the following Kernel32 DLL prototypes:
0002     * //
0003     * // UINT STDCALL GetShortPathNameW( LPWSTR, LPVOID, UINT )
0004     
0005     declare function getShortPathNameW
0006          
0007     * // Attempt to get the size of the buffer for the short path
0008     longPath  = "c:\temp\somelongfilename.txt"   
0009     
0010     * // Pass a NULL (0) pointer ...
0011     bufLen = getShortPathNameW( longPath, 0, 0 )
0012     
0013     * // and so on ...


The best of both worlds

Of course having to do all of this work just to pass a NULL pointer seems slightly unreasonable, so thanks to the magic of DLL function aliasing you can "have your cake and eat it". It's simply a matter of how you prototype the function - you prototype one version to use the LPWSTR/LPASTR prototype, and another to use LPVOID, just ensuring that you give them different names.

E.g. continuing with the GetShortPathName example here's how you would prototype the functions:

0001     * // We're using the following Kernel32 DLL prototypes:
0002     * //
0003     * // UINT STDCALL GetShortPathNameW( LPWSTR, LPWSTR, UINT )
0004     * // UINT STDCALL GetShortPathNameW( LPWSTR, LPVOID, UINT ) 
0005     * //                                 As GetShortPathNameWByPtr
0006  
0007     declare function getShortPathNameW, getShortPathNameWByPtr
0008  
0009     * // Attempt to get the size of the buffer for the short path
0010     longPath  = "c:\temp\somelongfilename.txt"   
0011  
0012     * // Pass a NULL (0) pointer ... 
0013     bufLen = getShortPathNameWByPtr( longPath, 0, 0 )
0014  
0015     * // buflen includes space for null-terminator, so create the buffer
0016     shortPath = str( char(0), bufLen )
0017     
0018     * // And get the path
0019     x = getShortPathNameW( longPath, shortPath, bufLen )


Conclusion

That concludes this small series of posts on string handling in DLL Prototyping. In the next post in the series we'll take a closer look at the LockVariable statement and why it is still relevant in 32-bit OpenInsight.

Labels: , , ,

Pixel
Pixel Footer R1 C1 Pixel
Pixel
Pixel
Pixel