Neues Forum für TeamDeveloper, SqlBase, TDMobile und ReportBuilder

Hi folks,

es gibt ein neues offenes Forum für TeamDeveloper, SqlBase, TDMobil und ReportBuilder hier: http://tdforum.daverabelink.net/

Leider hat OpenText das bislang offene Forum (https://support.guptatechnologies.com) geschlossen es ist nun read only.

Sie haben zwar ein neues Forum innerhalb von OpenText „My Support“ erstellt (https://knowledge.opentext.com/knowledge/cs.dll/Open/Knowledge). Aber das ist nur mit einem GLS-Vertrag zugänglich und auch dann nur nach einem komplizierten Registrierungsprozess.

Da die Gemeinschaft der TeamDeveloper-Entwickler  nicht allzu groß ist bezweifle ich, dass eine Menge Leute die Hürde zum offiziellen Forum nehmen und es so gar nicht erst zum Leben erwachen wird.

Damit die Gemeinschaft erhalten bleibt, tragt bitte zum neuen offenen Forum http://tdforum.daverabelink.net/ bei.

Happy coding

Advertisements

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

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!

UTF-8 Strings mit Team Developer 3.1 erstellen

Neulich hatten wir die Aufgabe, mit Team Developer 3.1 einen Text in UTF-8 zu erstellen. Eine Recherche in der Windows API fördert zwei geeignete Funktionen zu Tage:

MultiByteToWideChar(..) und WideCharToMultiByte(..). Mit der ersten Funktion ist es möglich, irgendeine Codierung (ANSI, UTF-8 und eine Menge Codepages) in UTF-16 (WideChar) umzuwandeln. Mit der zweiten kann UTF-16 in irgendeine Codierung umwandeln. Der Weg ist also:
GuptaString — MultiByteToWideChar(..) –> UTF-16 — WideCharToMultiByte(..) –> UTF-8.

Die Bezeichnung der beiden Funktionen finde ich etwas verwirrend: Mit MultiByte ist eine Codierung aus einem großen Fundus gemeint – auch SingleByte-Codierungen wie ANSI. WideChar steht für die Windows-interne UTF-16LE-Codierung.

Stellt sich nur noch die Frage, wie man die Funktionen konkret in Gupta verwendet:

MultiByteToWideChar

mit den Parametern

_In_      UINT   CodePage = Codepage des umzuwandelnden String – in unserem Fall die Standard Windows ANSI Codepage – also die Konstante CP_ACP = 0x00
_In_      DWORD  dwFlags = Steuert Feinheiten in der Umwandlung einzelner Zeichen. Beispielsweise kann der Buchstabe „Ä“ in Unicode auf zwei Arten dargestellt werden. Einmal als einzelnes Zeichen Ä (U+00C4) mit dwFlags = MB_PRECOMPOSED (0x01)  oder als kombiniertes Zeichen aus A + ̈ (U+0041 und U+0308) mit dwFlags = MB_COMPOSITE (0x02)
_In_      LPCSTR lpMultiByteStr = der zu codierende String
_In_      int    cbMultiByte = Länge des zu codierenden Strings oder -1, wenn der zu codierende String mit 0 terminiert ist – was bei Gupta der Fall ist.
_Out_opt_ LPWSTR lpWideCharStr = Buffer, in den die Funktion den konvertierten UTF-16 String schreibt
_In_      int    cchWideChar = Länge des zur Verfügung gestellten Buffers in Zeichen(!) oder 0, wenn man nur die Länge des nötigen Buffers ermitteln möchte.

Zunächst muss man also diese Funktion aufrufen mit cchWideChar = 0 und erhält die nötige Länge des Buffers in Zeichen(!) zurück.
Aber was bedeutet das konkret? Wieviel Bytes sind das?
Die Antwort ist einfach – ein WideChar-Zeichen ist immer 2 Byte lang – also muss man den Rückgabewert mit 2 multiplizieren und kann per SalStrSetBufferLength(..) entsprechend Speicher bereitstellen.
Aber stimmt das wirklich? Denn Unicode umfasst mehr Zeichen als mit 2 Bytes codiert werden können. Ein Standardbeispiel ist der Violinenschlüssel musical_symbol_g_clef
(U+1D11E) mit der UTF-16-Codierung 0xD834 0xDD1E – also vier Bytes.
Die Antwort ist: Ja es stimmt wirklich. Ich habe das getestet. Liefert man den Violinenschlüssel in UTF-8 Codierung an (0xF0 0x9D 0x84 0x9E) gibt die Funktion MultiByteToWideChar(..) den Wert „2“ zurück – obwohl es sich in Wahrheit nur um ein Zeichen handelt.

WideCharToMultiByte

mit den Paramtern

_In_      UINT    CodePage = gewünschte Ziel-Codepage
_In_      DWORD   dwFlags = Flags die das Verhalten steuern, wenn ein umzuwandelndes Zeichen nicht in der Zielcodepage enthalten ist.
_In_      LPCWSTR lpWideCharStr = der zu codierende UTF-16 String
_In_      int     cchWideChar = Länge des zu codierenden Strings oder -1, wenn dieser terminiert ist
_Out_opt_ LPSTR   lpMultiByteStr = Buffer für den umgewandelten String
_In_      int     cbMultiByte = Länge des Buffers in Bytes
_In_opt_  LPCSTR  lpDefaultChar = Zeichen, das verwendet werden soll, wenn ein UTF-16-Zeichen nicht in der Zielcodepage enthalten ist
_Out_opt_ LPBOOL  lpUsedDefaultChar = Eigentlich eine Receive-Variable, die angibt, ob ein Default bei der Umwandlung verwendet werden muss. Für die Umwandlung nach UTF-8 muss die Variable aber mit dem Wert NULL übergeben werden. Das wiederum lässt Gupta leider nicht zu: Wenn man den Receive-Parameter für eine externe Funktion auf NULL setzt (z.B. so: Set bUsedDefaultChar = NUMBER_Null und damit die externe Funktion aufruft, bekommt man einen Laufzeitfehler). Die einzige Möglichkeit ist, in der Deklaration der externen Funktion diesen Parameter als BOOL zu definieren und den Wert 0 zu übergeben.)

StringToUtf8

Damit steht der Umwandlung nichts mehr im Weg:

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

Quellcode gibt’s hier: 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

Selbstreferenz mit Arrays aus Functional Classes in Gupta

Neulich hatte ich gezeigt, wie in Gupta eine Functional Class „sich selbst“ als Instance Variable enthalten kann. Man muss sie lediglich mit OBJ_Null initialisieren.
SomeClass: class1 = OBJ_Null
Martin fragte mich daraufhin, ob es auch möglich sei, ein Array der Functional Class als Instance Variable zu verwenden. Diese Datenstruktur ist äußerst nützlich, wenn man z.B. eine Baumstruktur abbilden möchte, wie sie in Dateisystemen vorkommt.
SomeClass: aClasses[*]
Leider quittiert der TeamDeveloper das mit einer Fehlermeldung beim Kompilieren. „Error: Circularly defined class.“ Martin hat daraufhin eine listige Lösung gefunden, den TD auszutricksen. Allerdings produziert dieser in meiner Entwicklungsumgebung Abstürze, wenn ich auf ein Arrayelement zugegriffen habe.
Eine andere Lösung für das Problem ist, eine weitere Listenklasse zu definieren:
Ausgehend von einem Datentyp (Functional Class) namens „Node“ wird eine weitere Klasse „ListNodes“ erstellt, die als Instance Variablen ein Array aus Nodes sowie einen internen Zähler enthält:
Functional Class: ListNodes
 Instance Variables
  Node: __nodes[*]
  Number: __nCountNodes
Diese Listenklasse kann nun in der Klasse „Node“ als Instance Variable verwendet werden, wenn sie mit OBJ_Null initialisiert wird:
Functional Class: Node
 Instance Variables
  ListNodes: __listChildNodes = OBJ_Null
Fügt man noch ein paar Zugriffsfunktionen hinzu, kann man damit richtig gut arbeiten:
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
Im Anhang ist ein Beispiel, das die Directory-Struktur des Laufwerks C: ausliest (es kann allerdings eine Weile dauern, bis alle Informationen ausgelesen sind).
Happy Coding

Vorsicht bei SalStrLop (in TD3.1)

Wenn SalStrLop auf eine leere Zeichenfolge (sString) angewendet wird, können seltsame Dinge geschehen. Grund dafür ist, dass Gupta die interne Pufferlänge auf null setzt. Wenn diese Zeichenfolge mit einer anderen Zeichenfolge (sString2) verkettet wird, wird deren Puffer jedes Mal um eins vermindert. Wenn eine solche Zeichenfolge an eine externe DllFunktion übergeben wird, treten Abstürze auf, weil beim Kopieren des Strings nur nBufferLen Zeichen kopiert werden. In der Regel fehlt die terminierende Null. Den Rest könnt ihr euch denken.
Hier ist Code, der das Problem veranschaulicht:
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: SalStrLop nur auf nicht leere Strings anwenden. Z.B. mit einer Funktion wie dieser:

!!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 existiert das Problem übrigens nicht.

Happy coding!