New Forum for TeamDeveloper, SqlBase, TDMobile, ReportBuilder

Hi Folks,

there is a new community driven forum for TeamDeveloper, SqlBase, TDMobil and ReportBuilder here: http://tdforum.daverabelink.net/

Unfortunately OpenText has closed their open community forum (https://support.guptatechnologies.com) – it’s read only now.

They built up a new forum within the OpenText „My Support“ (https://knowledge.opentext.com/knowledge/cs.dll/Open/Knowledge) – but this one is only accessable when you have a GLS-contract and after a complicated registration process.

Since the community for TeamDeveloper et al is not too large I doubt that a lot of people take the hurdle to the official forum and it will die in fact.

So please use and contribute to the new open forum http://tdforum.daverabelink.net/

Happy coding

Advertisements

Creating UTF-8 String with Team Developer 3.1

Recently we had the task to create UTF-8 strings with Team Developer 3.1. Searching the Windows API we got two promising functions:

MultiByteToWideChar(..) and WideCharToMultiByte(..). The first one converts any character encoding (ANSI, UTF-8 and a lot of Codepages) to UTF-16 (WideChar). The second one encodes UTF-16 to any character encoding.

The way is therefore as follows:
GuptaString — MultiByteToWideChar(..) –> UTF-16 — WideCharToMultiByte(..) –> UTF-8.
I think that the names of the two functions are a bit confusing: „MultiByte“ means any encoding – including SingleByte encodings like ANSI. WideChar means the Windows internal UTF-16LE encoding.
The only question now is how to use these functions in Gupta:

MultiByteToWideChar

with parameters

_In_      UINT   CodePage = Codepage of the source string – in our case standard Windows – so we need to use the constant CP_ACP = 0x00
_In_      DWORD  dwFlags = Controls subtleties of the conversion of characters. E.g. the character „Ä“ can be represented in two different ways in Unicode. As a single Ä (U+00C4) with dwFlags = MB_PRECOMPOSED (0x01)  or as a composed character consisting of  A and ̈ (U+0041 und U+0308) with dwFlags = MB_COMPOSITE (0x02)
_In_      LPCSTR lpMultiByteStr = the source string
_In_      int    cbMultiByte = Bufferlength in bytes of the source string or -1 if it is null terminated – which is the case with Gupta strings.
_Out_opt_ LPWSTR lpWideCharStr = buffer for the target string
_In_      int    cchWideChar = Length of the target buffer in characters(!) or 0 – then the function returns the required bufferlength.

First you have to call this function with cchWideChar = 0 and get the required length of the buffer in characters(!).
But what does that mean? How many bytes is it?

The answer is simple a WideChar characters is always 2 bytes long so you have to multiply the return value by 2.

But is that really true? Because Unicode contains more characters than can be encoded with 2 bytes. A standard example is the treble clef musical_symbol_g_clef
(U + 1D11E) which UTF-16 encoding is 0xD834 0xDD1E – four bytes.
The answer is: Yes it’s true really. I have tested it. I encoded the treble clef in UTF-8 (0xF0 0x9D 0 x 84 0x9E) and requested the UTF-16 buffer length with MultiByteToWideChar(..) which returned „2“ Although it is in fact only one character.

WideCharToMultiByte

with parameters

_In_      UINT    CodePage = Destination codepage.
_In_      DWORD   dwFlags = Flags that control the behaviour when a character cannot be converted because the target codepage does not contain it.
_In_      LPCWSTR lpWideCharStr = Sourcesting as UTF-16.
_In_      int     cchWideChar = Length of the sourcestring in widechar-characters or -1 if it is null terminated.
_Out_opt_ LPSTR   lpMultiByteStr = Buffer for the target string.
_In_      int     cbMultiByte = Length of the target buffer in bytes.
_In_opt_  LPCSTR  lpDefaultChar = Default character which is used when a UTF-16 character is not contained in the target codepage.
_Out_opt_ LPBOOL  lpUsedDefaultChar = This is actually a receive variable that indicates whether a default has been used in the conversion. For the conversion to UTF-8 the variable must be passed with null value. Which in turn is unfortunately disallowed by Gupta: If you set a receive parameter for an external function to null (e.g. Set bUsedDefaultChar = NUMBER_Null and pass it to an external function you get a run-time error). The only workaround is to define this parameter in the declaration of the external function as a BOOL and pass a value of 0.

StringToUtf8

With this knowledge we can write our function:

Function: StringToUtf8  ! __exported
    Description: Converts a Gupta string to UTF-8
    Returns
        String:
    Parameters
        String: sAnsi
    Actions
        Return Utf16ToUtf8( StringToUtf16( sAnsi ) )

Function: StringToUtf16  ! __exported
    Description: Converts a Gupta string (ANSI) to UTF-16
    Returns
        String:
    Parameters
        String: sAnsi
    Local variables
        Number: nBufferUtf16Len
        String: sUtf16
    Actions
        If ( NOT sAnsi )
            Return ''
        Call SalStrSetBufferLength( sUtf16, 1 ) ! Buffer must be 1 or bigger - otherwise we get a Gupta error when calling MultiByteToWideChar(..)
        Set nBufferUtf16Len = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, sAnsi, -1, sUtf16, 0 )
        Call SalStrSetBufferLength( sUtf16, nBufferUtf16Len * 2 ) !  Buffersize in "WideChars" -  must be multiplied by 2 to get buffersize in bytes
        Call MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, sAnsi, -1, sUtf16, nBufferUtf16Len )  !hier wieder Buffer in Characters
        Return sUtf16

Function: Utf16ToUtf8  ! __exported
    Description: Converts a UTF-16-String to UTF-8
    Returns
        String:
    Parameters
        String: sUtf16
    Local variables
        Number: nBufferUtf8Len
        String: sUtf8
    Actions
        If ( NOT sUtf16 )
            Return ''
        Call SalStrSetBufferLength( sUtf8, 1 ) ! Buffer must be 1 or bigger - otherwise we get a Gupta error when calling WideCharToMultiByte(..)
        Set nBufferUtf8Len = WideCharToMultiByte( CP_ACP, 0, sUtf16, -1, sUtf8, 0, STRING_Null, 0 )
        Call SalStrSetBufferLength( sUtf8, nBufferUtf8Len )
        Call WideCharToMultiByte( CP_ACP, 0, sUtf16, -1, sUtf8, nBufferUtf8Len, STRING_Null, 0 )
        Return sUtf8

Sourcecode is here for download: UtfStringConverion.apt

Happy coding!

Selfreference with Arrays of Functional Classes in Gupta

Recently I showed how to implement a Functional Class in Gupta that contains „itself“ as an Instance Variable. You just have to initialize it with OBJ_Null.

SomeClass: class1 = OBJ_Null

Martin asked me then if it is possible to have an array of Functional Classes of the same type as an Instance Variable. This kind datastructure is very useful e.g. if you want to represent a tree structure like a filesystem.

SomeClass: aClasses[*]

Unfortunately TeamDeveloper redeems that with an error while compiling: „Error: Circularly defined class.“ Martin found an artful approach to outsmart TD: First write SomeClass c = OBJ_Null afterwards replace = OBJ_Null through [*]. But that doesn’t work on my machine. It crashes when I access an element of the array.
Another solution to that problem is to define an additional listclass:
Based on a datatype (Functional Class) called „Node“ an additional class „ListNodes“ is created. This one contains an array of „Node“ as an Instance Variable and an internal counter:

Functional Class: ListNodes
 Instance Variables
  Node: __nodes[*]
  Number: __nCountNodes

This listclass can be used in class „Node“ as an Instance Variable when initialized with OBJ_Null:

Functional Class: Node
 Instance Variables
  ListNodes: __listChildNodes = OBJ_Null

If one adds some access functions to class „Node“ it becomes really useful:

 

Function: AddChildNode
 Returns
 Parameters
  Node: node
 Local variables
 Actions
  If ( __listChildNodes = OBJ_Null )
   Set __listChildNodes = new ListNodes
  Set __listChildNodes.aNodes[__listChildNodes.nCountNodes] = node
  Set __listChildNodes.nCountNodes = __listChildNodes.nCountNodes + 1
Function: CountChildNodes
 Returns
 Parameters
 Local variables
 Actions
  If ( __listChildNodes = OBJ_Null )
   Return 0
  Else
   Return __listChildNodes.nCountNodes
Function: GetChildNode
 Returns
  Node:
 Parameters
  Number: nIdx
 Local variables
 Actions
  If ( __listChildNodes = OBJ_Null )
   Return OBJ_Null
  Else
   If ( (nIdx >= 0) AND (nIdx < __listChildNodes.nCountNodes) )
    Return __listChildNodes.aNodes[nIdx]
   Else
    Return OBJ_Null
Function: RemoveChildNode
 Returns
 Parameters
  Number: nIdx
 Local variables
 Actions
  If ( __listChildNodes = OBJ_Null )
   ! do nothing
  Else
   If ( (nIdx >= 0) AND (nIdx < __listChildNodes.nCountNodes) )
    While ( nIdx < __listChildNodes.nCountNodes - 1)
     Set __listChildNodes.aNodes[nIdx] = __listChildNodes.aNodes[nIdx +1 ]
     Set nIdx = nIdx + 1
    Set __listChildNodes.aNodes[__listChildNodes.nCountNodes] = OBJ_Null
    Set __listChildNodes.nCountNodes = __listChildNodes.nCountNodes - 1
In the attachment is an example that reads the whole directory-structure of drive C: (it may run some time to gather all information).
DirectoryTree.zip
Happy Coding

Beware of SalStrLop (in TD3.1)

If SalStrLop( sString ) is applied to an empty string then strange things can happen. This is because Gupta sets the internal buffer length to zero. When this string is concatenated to another string (say sString2) then the buffer length of sString2 will be shrinked by 1 each time. When you pass such a string to an external dll function crashes might occur because the string will be copied but only to the length of the buffer. Usually the terminating zero will then be stripped away. You get the picture.

Here is some code that shows the problem:

Set sTest = ''
Set sTest2 = 'A'
Set nBuffer = SalStrGetBufferLength( sTest ) ! nBuffer is 1 - just the trailing zero
Set nBuffer = SalStrGetBufferLength( sTest2 ) ! nBuffer is 2 - 'A' and trailing zero
Call SalStrLop( sTest )
Set nBuffer = SalStrGetBufferLength( sTest ) ! nBuffer is 0!
Set sTest2 = sTest2 || sTest
Set nBuffer = SalStrGetBufferLength( sTest2 ) ! nBuffer is 1!!

Workaround: Only apply SalStrLop when the string is not empty. Maybe write a function like this:

!!CB!! 134
Function: StrLopSafe
 Description:
 Returns
  Number:
 Parameters
  Receive String: sIn
 Static Variables
 Local variables
 Actions
  If ( sIn )
   Return SalStrLop( sIn )
  Else
   Return 0

In TD6.3 this problem does not exist.

Happy coding!

Calling General Window Class-functions via Window Handle

Gupta provides the ability to define general Window Classes which can be used as a base class for real window classes such as dialogs, FormWindows, controls, etc.
In addition there is the possibility to call a function of a window or control via its window handle. To do so you have to equip the handle with with a so-called window handle qualifier:
Call hWnd.Frm1.SomeFunction()
  • hWnd is the window handle of a form Windows.
  • Frm1 is the template name of a window or a window class.
  • The function SomeFunction() is a function that is defined in Frm1.
So far, so good. This is documented in the Gupta books or help.
But if the function SomeFunction() is defined in a General Window Class and Frm1 is a derivation you would have to write the call:
Call hWnd.GenWndCls.SomeFunction()

But unfortunately Gupta then unnecessarily comes up with a compile error „class GenWndCls, is a general window class. Hence it may serve only as a base class, and may not be qualified with a window handle“. This is very annoying because it is often useful to use a general Window Class as the basis for a form and a dialogue. Fortunately there is a simple workaround for this:

Call hWnd.GenWndCls..SomeFunction()

Yes – a simple additional dot and Gupta accepts the expression. Why is this so? The ..-Operator preceding a function call is a so-called late-bound reference that causes Gupta to look up the called function in all derived classes and executes the „deepest“ implementation. If SomeFunction() would have been defined in Frm1 then this implementation would be executed. If there are no additional implementations Gupta calls the first spurned function without complaint.

Happy coding.

Self Reference with Functional Classes in Gupta

With Gupta Team Developer you can create functional class datatypes (UDV) – comparable to classes from C# or Java. In contrast to the above frameworks with Gupta variables that are based on these data types are not only declared but also instantiated. This has a great positive effect: it avoids the runtimeerror „NullReferenceException“ which is quite common in C# and Java.

Sample C#

class SomeClass
{
        public Int32 someMember;
}
public void SomeMethod()
{
    SomeClass someObject;
        // NullReferenceException
    someObject.someMember = 7;

        // this works
    someObject = new SomeClass();
    someObject.someMember = 7;

}

Sample Gupta

Functional Class: SomeClass
    Instance Variables:
        Number: nSomeMember

Function: SomeFunction
    Local variables:
        SomeClass: someObject
    Actions:
        ! this works
        Set someObject.nSomeMember = 7
At the moment when the function SomeFunction() is called Gupta instantiates the class.

But this default behavior has also a disadvantage: the class (UDV) SomeClass cannot contain itself (easily) as an instance variable:

Functional Class: SomeClass
 Instance Variables:
     ! results in compile error: Circularly defined class.
     SomeClass: cSomeChild
Because as soon as SomeClass would be instantiated the instance variable cSomeChild would also be instantiated then their instance variable cSomeChild and so on.

The same problem occurs if Class1 is an instance variable of type Class2 and Class2 of Class1.

But there is an almost not documented way to create just such constructs: by setting the UDV to OBJ_Null when declaring it:

Functional Class: SomeClass
 Instance Variables:
     SomeClass: cSomeChild = OBJ_Null
A few comments:
  • I have discovered this option randomly in a single place in the help file of Gupta: as sample code in the description of the command SalObjIsNull.
  • Only UDVs can be set in the declaration section. For Number, String, DateTime, etc. TD unfortunately does not allow it. After all a constructor function for UDVs should come with TD 6.3 where you could set values for instance variables.
  • If you want to use the UDV cSomeChild you previously have to either assign an existing instance of the same or derived type or create an instance via the new command:
Set cSomeChild = new SomeClass

Examples of use

There are a lot of scenarios where it is useful or even necessary to use nested classes:

  • Mapping of hierarchical data structures such as XML documents or even all data structures that can be displayed in TreeViews that have more than one hierarchy level.
  • Double linked list structures
  • publish / subscriber classes which are mutually linked
  • tree structures such as red-black-index trees

Have fun experimenting with these structures.

Happy coding.

 

Date Literal in Gupta

The other day I stumbled over a small but fine undocumented feature in the Gupta TeamDeveloper: the date literal.
It does not happen very often but sometimes you need to define a fixed date in the source code. So far we have done like that

dtValue = SalDateConstruct (2015, 11, 21, 22, 35, 12)

But this is easier and more elegant,

dtValue = 2015-11-21-22.35.12

more flexible

dtValue = 2015-11-21

and precise

dtValue = 2015-11-21-22.35.12.123456

down to the microsecond.

Happy coding.