Using .NET components in Team Developer 3.1

One of the great advantages of TD is that you can extend the functionality of Sal by external components. Thus you can take advantage of the high productivity of the 4GL – low learning curve, very fast programming – with the advantages of the vast capabilities of the .NET framework. Unfortunately.NET can be used only  with version 6.0 of TD easily. For versions below, there is the possibility, however using .NET functionality via COM objects. As an example, I want to call the MD5 hash function from the .NET framework so that you can calculate an MD5 hash value for TD strings and blobs. Remark: It is also easily possible to use the more modern hash functions RIPEMD160, SHA1, SHA256, SHA384, and SHA512 instead. See the MSDN library.

Creating the COM object in C#

Here the recipe to build a COM Object which wraps the .NET function MD5.ComputeHash(..):
  1. In Visual Studio (for example the free visual C# 2010 Express) create a new project of type ClassLibrary named DotNet2Com.
  2. The Assembly must be registered for COM. To do so call the context menu of DotNet2Com in the Project Solution Explorer, select Properties. On the tab Build check Register for COM interop.
  3. In Project Solution Explorer doubleclick ‚AssemblyInfo.cs‘, look for the entry [assembly: ComVisible(true)] and set it to true if necessary.
  4. Rightclick the file called Class1.cs and rename it via context menu to Md5.cs (including the references).
  5. In the file Md5.cs add the using directive
    using System.Runtime.InteropServices;
  6. Define an interface in Md5.cs – outside the class MD5 but within the namespace:
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IMd5
    {
    String ComputeHash (Object buffer);
    }
    The interface is necessary so that Visual Studio builds a DLL which provides this function with a COM interface so that it is also visible to the TD. Since the TD can only import latebound functions, the attribute InterfaceIsDispatch must be used. InterfaceIsDual is also possible (which indicates to VS to implement both interface types), but not InterfaceIUnknown.
    For the input buffer I chose type Object rather than byte []. The reason for this is that ActiveX Explorer of Team Developmer wraps type byte [] to a SafeArray. Type Object is interpreted by the TD, however, as a Variant. Both would work but the Variant is substantially easier and faster for passing strings (which may include text as well as a BLOB in TD).
  7. Now class MD5 must be modified so that it implements our interface:
    public class MD5: IMd5
    {
    }
Let’s start now with the actual programming:
To use the MD5 functions, they must be incorporated by
using System.Security.Cryptography;
The actual function looks like this:

>

public String ComputeHashAsHex(Object buffer)
{
// parameter checking
if (buffer == null)
{
return String.Empty;
}

// Cast buffer from type Object to a byte[] array
byte[] bIn = (byte[])buffer;
// Check length

if (bIn.Length == 0)
{
return String.Empty;
}

// Instantiating of MD5 is a little bit uncommon (would expect MD5 md5Hash = new MD5() )
// but MSDN-Library tells us this

MD5 md5Hash = MD5.Create();

byte[] output = md5Hash.ComputeHash(bIn);

// Cast the 16 bytes to a HEX-String with 32 chars
StringBuilder sb = new StringBuilder(32);
foreach (byte bt in output)
{
// casts a byte in a 2-digit hex
sb.Append(bt.ToString(„X2„));
}
return sb.ToString();
}

That’s it, the COM object can be built.

Incorporating the COM object in the Team Developer

An .apl wrapper must be created to use a COM object. In TD start the ActiveX Explorer (Tools menu) and select the library „DotNet2Com“. The ActiveX Explorer inspects the COM object and displays its contents. It is ok to just select the interface IMd5 and then „generate full by name“ to create the DotNet2Com.apl.
The file is found in \Gupta\AxLibs. It is now also automatically – together with the Automation.apl – part of the current TD project as a library. For example, the COM object can be used like this:

Function: HelloWorldMd5
Returns
Parameters
Static Variables
Local variables
FunctionalVar: cMd5
Class: DotNet2Com_IMd5
FunctionalVar: vBuffer
Class: Variant
String: sMd5
String: sIn
Actions
! Instantiate COM-Objekt – Default-name is: Namespace.Classname
Call cMd5.CreateObject( ‚DotNet2Com.Md5‘ )
! Set the input
Set sIn = ‚Hello World‘
! Since a String like the one above includes a terminating zero we can cut this out with
 Call SalStrSetBufferLength(sIn, SalStrLength(sIn) )
! Store String as Blob in Variant
 Call vBuffer.SetBlob( sIn )
Call cMd5.ComputeHashAsHex( vBuffer, sMd5 )
Call SalMessageBox( ‚The Md5 Hash for
„Hello World“
is
‚ || sMd5, ‚Success‘, MB_Ok)

The result:
 
That’s it for now.
Finally the downloads for the above code:

Visual Studio Project DotNet2Com.zip
TD Project Md5App.zip

I’d appreciate your feedback.
Happy coding!

.NET-Komponenten in Team Developer 3.1 einbinden

Einer der großen Vorteile des TD ist, dass man die Sal-Funktionalität durch externe Komponenten erweitern kann. Damit kann man die Vorteile der hohen Produktivität der 4GL-Sprache – geringe Lernkurve, sehr schnelle Programmierung – mit den Vorteilen des riesigen Funktionsumfangs des .NET-Frameworks kombinieren. Leider lässt sich .NET erst ab Version 6.0 des TD ohne weiteres einbinden. Für Versionen darunter gibt es jedoch die Möglichkeit, .NET-Funktionen mittels COM-Objekten im TD zu. Beispielhaft möchte ich hier die MD5-Hashfunktion aus .NET einbinden, so dass man vom TD aus den MD5-Hashwert für Strings und Blobs ermitteln kann. Ohne weiteres wäre es möglich, auch die moderneren Hash-Funktionen RIPEMD160, SHA1, SHA256, SHA384 und SHA512 zu verwenden. Siehe MSDN-Library.

 COM-Objekt in C# erstellen

Hier das Kochrezept, um die Funktion MD5.ComputeHash(..)  als COM-Objekt zur Verfügung zu stellen:

  1. In Visual Studio (z.B. mit dem kostenlosen Visual C#  2010 Express) ein neues Projekt vom Typ ClassLibrary mit dem Namen DotNet2Com erstellen.
  2. Die Assembly muss für COM registriert werden können. Dazu im Projektmappenexplorer das Kontextmenü von DotNet2Com aufrufen, dort Eigenschaften wählen. Auf dem Reiter Erstellen die Checkbox Für COM Interop registrieren anhaken.
  3. Im Projektmappenexplorer unter Properties „AssemblyInfo.cs“ aufrufen, dort den Eintrag [assembly: ComVisible(true)] ggf. von false auf true setzen.
  4.  Die Datei Class1.cs per rechtem Mausklick auswählen und via Kontextmenü in Md5.cs umbenennen (inklusive der Verweise).
  5. In der Datei Md5.cs die Using-Direktive
    using System.Runtime.InteropServices;
    eintragen.
  6. In  der Datei Md5.cs das Interface definieren – noch außerhalb der Klasse Md5 aber innerhalb des Namespaces:
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IMd5
    {
    String ComputeHash(Object buffer);
    }
    Das Interface ist notwendig, damit Visual Studio beim Erstellen der DLL diese und ggf. weitere Funktionen mit einem COM-Interface ausstattet und sie somit z.B. für den TD sichtbar sind. Da der TD offensichtlich nur Latebound-Funktionen importieren kann, muss das Attribut InterfaceIsDispatch gesetzt werden. Möglich wäre noch InterfaceIsDual (das VS anweist, beide Interfacetypen zu implementieren), nicht jedoch InterfaceIUnknown.
    Der Input-Buffer wird hier vom Typ Object gewählt und nicht etwa das näherliegende byte[]. Grund dafür ist, dass ActiveX-Explorer des TeamDeveloper den Typ byte[] als SafeArray wrappt. Der C#-Typ Object wird vom TD hingegen als Variant interpretiert. Das ist für die Übergabe von Strings (der in TD sowohl Text als auch einen BLOB aufnehmen kann) wesentlich geeigneter und schneller.
  7. Nun muss die eigentliche Klasse Md5 das Interface implementieren:
    public class Md5 : IMd5
    {
    }

Jetzt kann die eigentliche Programmierung losgehen:
Um die MD5-Funktionen nutzen zu können, müssen sie per
using System.Security.Cryptography;
eingebunden werden.

Die eigentliche Funktion sieht so aus:

public String ComputeHashAsHex(Object buffer)
{
            // Parameter auf Gültigkeit prüfen
if (buffer == null)
{
return String.Empty;
}

            // Parametervariable buffer vom Typ Object in ein byte[] Array wandeln
byte[] bIn = (byte[])buffer;
            // Länge prüfen
if (bIn.Length == 0)
{
return String.Empty;
}

            // Instantiierung von MD5 ist ungewöhnlich (an sich wäre MD5 md5Hash = new MD5() zu erwarten gewesen)
            // ist in der MSDN-Library jedoch so beschrieben:
MD5 md5Hash = MD5.Create();

byte[] output = md5Hash.ComputeHash(bIn);

// Umwandeln der 16 bytes im Output in einen HEX-String bestehend aus 32 Zeichen
StringBuilder sb = new StringBuilder(32);
foreach (byte bt in output)
{
// jedes Byte eine zweistellige Hexzahl wandeln
sb.Append(bt.ToString(„X2„));
}
return sb.ToString();
}

Das war’s, das COM-Objekt kann gebaut werden.

Einbinden des COM-Objekts in den Team Developer

Um ein COM-Objekt nutzen zu können, muss ein .apl-Wrapper erzeugt werden. Im TD muss dazu mittels des ActiveX Explorers (Menü Tools) die Library „DotNet2Com“ ausgewält werden. Dieser inspiziert das COM-Objekt und zeigt seinen Inhalt an. Es genügt, das Interface IMd5 auszuwählen und per „Generate Full by Name“ die DotNet2Com.apl zu erzeugen.

Die Datei findet sich in \Gupta\AxLibs wieder. Sie ist nun auch automatisch – zusammen mit der Automation.apl – im aktuellen TD-Projekt als Library eingebunden. Verwendet werden kann das COM-Objekt z.B. so:

Function: HelloWorldMd5
 Returns
 Parameters
 Static Variables
 Local variables
FunctionalVar: cMd5
Class: DotNet2Com_IMd5
FunctionalVar: vBuffer
Class: Variant
String: sMd5
String: sIn
 Actions
  ! COM-Objekt instanziieren – Name ist per Default: Namespace.Classname
Call cMd5.CreateObject( ‚DotNet2Com.Md5‘ )
  ! Eingabe-String setzen
Set sIn = ‚Hello World‘
  ! Besonderheit bei Text-Strings: Länge des Buffers auf Länge des Texts begrenzen – ansonsten würde die abschließende 0 in die Hash-Berechnung mit aufgenommen
Call SalStrSetBufferLength(sIn, SalStrLength(sIn) )
  ! String im Variant als Blob hinterlegen
Call vBuffer.SetBlob( sIn )
Call cMd5.ComputeHashAsHex( vBuffer, sMd5 )
Call SalMessageBox( ‚The Md5 Hash for
„Hello World“
is
‚ || sMd5, ‚Success‘, MB_Ok)

Das Ergebnis:

Das war’s fürs erste zu diesem Thema.

Zum Schluss noch die Downloads zum oben beschriebenen Code:

Visual Studio Projekt DotNet2Com.zip

TD Projekt Md5App.zip

Über Feedback würde ich mich freuen.

Happy coding!

NUnit in Visual Studio integrieren

An sich gibt’s schon etliche Artikel die beschreiben, wie man NUnit in Visual Studio integrieren kann. Aber ich habe keine Lust, jedes Mal auf’s neue die Suchmaschinen zu durchsuchen(!). Deshalb fasse ich es für mich nochmal zusammen:

  1. Download und Installation von NUnit von www.nunit.org (aktuelle Version ist 2.5.10)
  2. In VS ein „produktives“ Projekt erstellen oder öffnen
  3. Dort ein zweites Projekt (Typ Konsolenapplikation) anlegen mit Namen [Name produktives Projekt].Tests
  4. Class1.cs in [Name der zu testenden Klasse]Test umbenennen
  5. Der Klasse das Attribut [TestFixture] verpassen
  6. Im Testprojekt die Verweise zu nunit-gui-runner, nunit.framework und nunit.mocks einbinden
  7. In der Main-Methode folgende Zeile unterbringen:
    [STAThread]
    static void Main(string[] args)
    {
    NUnit.Gui.AppEntry.Main(new string[] { Assembly.GetExecutingAssembly().Location, „/run“ });
    }
  8. Unter Debug/Exceptions folgendes hinzufügen:
    NUnit.Framework.AssertionException vom Typ Managed Debugging Assistants hinzufügen.
    Sollte der Menüpunkt nicht sichtbar sein (ist bei den Express-Editions der Fall) dann voher:
    TOOLS > Settings > Expert Settings einschalten.
  9. Per rechtem Mausklick auf das Testprojekt dieses als Startup Projekt festlegen.

Sind die Tests geschrieben, mit F5 loslegen. Fertig.