Eine benutzerdefinierte FPScript-Funktion

23.04.2021

Dieses Beispiel implementiert eine benutzerdefinierte FPScript-Funktion, die nach Registrierung in FlexPro wie eine eingebaute Funktion in FPScript-Formeln verwendet werden kann. Sie wird im Assistenzfenster des FPScript-Editors unterstützt und steht automatisch in allen geladenen Datenbanken zur Verfügung.

Hinweise
Das Beispiel zeigt die Implementierung einer solchen Funktion in VBA. Der vollständige VBA-Quelltext des Beispiels ist in der Projektdatenbank CustomFunction.fpd enthalten. Sie können jedoch auch jede andere Automation-kompatible Programmiersprache, z. B. C# oder C++ verwenden. Eine Variante des Beispiels, das als Add-In in C++ realisiert wurde, ist ebenfalls im Lieferumfang von FlexPro enthalten. Der Pfadname der Projektdatenbank lautet normalerweise C:\Users\Public\Documents\Weisang\FlexPro\2021\Examples\VBA\Custom Function\CustomFunction.fpd bzw. C:>Benutzer>Öffentlich>Öffentliche Dokumente>Weisang>FlexPro>2021>Examples>VBA>Custom Function>CustomFunction.fpd.
 
Sie können eine benutzerdefinierte FPScript-Funktion auch in FPScript implementieren. Siehe hierzu Tutorial benutzerdefinierte FPScript-Funktionen.

Hintergrund

Das Beispiel definiert eine Funktion mit drei Argumenten, die die ersten beiden Argumente in Abhängigkeit vom dritten Argument miteinander verrechnet. Vom Aufbau her ähnelt es damit der Struktur vieler eingebauter Funktionen. Das Beispiel deckt die wesentlichen Elemente bei der Implementierung einer benutzerdefinierten FPScript-Funktion ab:

Definition der Argumente

Beschränkung von Typ und Struktur der Argumente

Verwendung von Default-Argumenten

Registrierung und Deregistrierung der Funktion

Zugriff auf die Argumente nach dem Funktionsaufruf

Definition benutzerdefinierter FPScript-Konstanten

Registrierung einer benutzerdefinierten Funktion

Bevor die Funktion registriert werden kann, müssen ihre Argumente und sonstige Eigenschaften festgelegt werden. Die Registrierung selbst stellt dann den Abschluss der Funktionsdefinition dar.

Zunächst wird die Funktion der Auflistung UserDefinedFPScriptFunctions hinzugefügt. Anschließend sollte eine Beschreibung zugeordnet werden. Diese wird dann z. B. automatisch bei der Verwendung des Assistenzfensters angezeigt.

With UserDefinedFPScriptFunctions.Add("MyFunction")

     .Description = "Adds or subtracts two values"

     .Indeterministic = False

     …
End With

Die Eigenschaft Indeterministic legt fest, ob die Funktion bei gleicher Eingabe immer das gleiche Ergebnis liefert. Falls Indeterministic auf True gesetzt wird, müssen Formeln, die diese Funktion verwenden in jedem Aktualisierungszyklus berechnet werden. Deshalb sollte dieser Wert nur, wenn unbedingt erforderlich, ggf. bei sich ändernden externen Abhängigkeiten auf True gesetzt werden.

Nachdem die Funktion in die UserDefinedFPScriptFunctions-Auflistung aufgenommen wurde, können die erforderlichen Argumente hinzugefügt werden. Benötigt wird nur der Name des Arguments, alle anderen Einstellungen sind optional. Es empfiehlt sich jedoch eine Beschreibung anzugeben, damit im Assistenzfenster ein Hinweis zu dem Argument angezeigt werden kann. Des Weiteren sollten in der Regel die Datentypen und Datenstrukturen des Arguments auf das exakt notwendige Maß beschränkt werden. Diese Vorgehensweise hat den Vorteil, dass die Prüfung auf die Einhaltung der Datentyp- und Datenstruktureinschränkungen bereits in FlexPro erfolgt, bevor die Berechnungsroutine der benutzerdefinierten FPScript-Funktion aufgerufen wird. Auf diese Weise kann der notwendige Code für die Implementierung der Funktion reduziert werden und die Ausgabe von Fehlermeldungen hierfür wird durch FlexPro erledigt.

With .Parameters.Add("Arg1")

     .Description = "First argument"

     .AllowedTypes = fpParameterTypeNumeric

     .AllowedStructures = fpParameterStructureScalar Or _

         fpParameterStructureDataSeries Or fpParameterStructureSignal

End With

Im vorliegenden Fall wird das erste Argument auf numerische Datentypen beschränkt und als Datenstrukturen werden skalare Werte, Datenreihen und Signale zugelassen. Datentypen und Datenstrukturen können bei der Zuweisung wie gezeigt Oder-verknüpft werden. Weicht das Argument bei der Anwendung der Funktion von den gegebenen Restriktionen ab, erfolgt eine Fehlermeldung.

Beim letzten Argument der Funktion wird ein Vorgabewert verwendet. Das bedeutet, dass das Argument weggelassen werden kann. In diesem Fall wird dann intern der Vorgabewert für dieses Argument verwendet.

With .Parameters.Add("Operation")

     .Description = "Type of operation"

     .AllowedTypes = fpParameterTypeNumeric

     .AllowedStructures = fpParameterStructureScalar

     .DefaultValue = "MYFUNC_OPERATION_ADD"

End With

Im vorliegenden Fall liegt eine Besonderheit vor. Der Typ des Operation-Parameters ist numerisch, aber als Vorgabewert wird ein Text angegeben. Der Text beinhaltet dabei den Namen einer zuvor eingeführten benutzerdefinierten FPScript-Konstanten, deren Wert in diesem Fall automatisch als Vorgabewert ermittelt wird. Der Vorteil dieser Art der Definition liegt darin, dass im Assistenzfenster jeweils der verständlichere Name der Konstanten angezeigt wird.

Benutzerdefinierte FPScript-Konstanten können wie folgt eingeführt werden:

With UserDefinedFPScriptConstants

     .Add "MYFUNC_OPERATION_ADD", "Selects the add operation", 1

     .Add "MYFUNC_OPERATION_MIN", "Selects the subtract operation", 2

End With

Der UserDefinedFPScriptConstants-Auflistung wird einfach unter Angabe eines Namens, einer Beschreibung und eines skalaren Wertes ein neues Element hinzugefügt. Namenskollisionen mit bereits existierenden eingebauten oder benutzerdefinierten Konstanten führen dabei zu einer entsprechenden Fehlermeldung.

Nachdem die Parameter der Funktion deklariert wurden, kann sie für die Verwendung registriert werden. Zu diesem Zweck wird die Register-Methode aufgerufen. Als Argument erhält sie einen Verweis auf ein Objekt, das die Schnittstelle IUserDefinedFunctionCalculate implementiert.

.Register oMyFunction

Die Schnittstelle IUserDefinedFunctionCalculate beinhaltet die Calculate-Methode, die von FlexPro bei der Berechnung einer Formel, die MyFunction verwendet, aufgerufen wird, sofern die Parameterrestriktionen eingehalten sind.

Hinweise
Erst beim Registrieren einer Funktion werden die definierten Parameter auf Konsistenz geprüft. Wenn es dabei zu einem Fehler kommt, sollte der Code, der die Parameter definiert überprüft werden.
 
Typischerweise werden benutzerdefinierte FPScript-Funktionen in einer automatisch ausgeführten Funktion wie AutoOpen oder AutoExec registriert, damit sie zu einem definierten, frühen Zeitpunkt zur Verfügung stehen.

Dem Registrieren einer Funktion steht das Deregistrieren gegenüber, das in der Regel in AutoOpen oder AutoClose erfolgt:

Sub AutoClose()

    '   unregister function

    UserDefinedFPScriptFunctions.Item("MyFunction").Delete

    

    '   unregister constants

    With UserDefinedFPScriptConstants

        .Item("MYFUNC_OPERATION_ADD").Delete

        .Item("MYFUNC_OPERATION_SUB").Delete

    End With

End Sub

Mit dem Löschen der Funktion aus der UserDefinedFPScriptFunctions-Auflistung erfolgt automatisch die Deregistrierung. Das Abmelden der Konstanten erfolgt analog.

Hinweis   Das Deregistrieren der benutzerdefinierte FPScript-Elemente ist nicht zwingend erforderlich, da diese bei Programmende automatisch entfernt werden. Da bei der Registrierung einer Funktion aber ein Objektverweis mitgegeben wird, kann durch die explizite Deregistrierung eine kontrollierte Aufgabe des Objektes erfolgen, das die Funktionsberechnung ausführt. Auf diese Weise können unliebsame Seiteneffekte (FlexPro hängt) vermieden werden.

Verwenden einer benutzerdefinierten Funktion

Bei Aufruf der registrierten FPScript-Funktion MyFunction wird die Calculate-Methode der IUserDefinedFunctionCalculate-Schnittstelle aufgerufen. Die Argumente werden dabei in Form eines Variant-Feldes übergeben. Die Beispielimplementierung zeigt exemplarisch, wie auf die Argumente zugegriffen wird. Aufgrund der möglichen Vielfalt der Datenstrukturen und Datentypen wurde das Beispiel an einigen Stellen vereinfacht.

Das Feld mit den Argumenten SafeArrayOfArguments ist eins-basiert, wie in VBA üblich. Der Zugriff auf die gewünschte Operation erfolgt deshalb unter Verwendung von Index drei:

'   MYFUNC_OPERATION_ADD = 1

'   MYFUNC_OPERATION_SUB = 2

nOperation = SafeArrayOfArguments(3)

Das dritte Argument wurde auf skalare Werte numerischen Typs beschränkt. Allerdings kann immer noch ein ungültiger Wert für die auszuführende Operation angegeben werden. Deshalb erfolgt eine Prüfung:

'   check operation

If nOperation <> 1 And nOperation <> 2 Then

'   will be propagated as error 0x800a0002 to FlexPro

    Err.Raise 2, "IUserDefinedFunctionCalculate_Calculate"_

               , "Invalid operation value"

End If

Im Fehlerfall wird eine Ausnahme ausgelöst. Der Meldungstext „Invalid operation value“ erscheint dann – zusammen mit dem genannten Fehlercode – entweder in der Ereignisanzeige von FlexPro oder in einem Meldungsdialogfeld.

Der restliche Teil der Funktionsberechnung zeigt, wie man mit Hilfe der VB-Funktionen IsObject, TypeOf und IsArray Fallunterscheidungen anhand des ersten Arguments vornimmt und dann die gewünschte Operation ausführt.

 

Hinweise zur Entwicklung in VBA

Wird nach der Registrierung einer Funktion ihre Calculate-Methode geändert, muss die Funktion deregistriert und anschließend wieder neu registriert werden. Das ist erforderlich, weil der Einsprungpunkt für die Funktion ansonsten nicht mehr gültig ist und eine Fehlermeldung angezeigt wird.

Zumindest beim Testen einer Calculate-Methode sollte unter Datei > Optionen auf der Registerkarte Systemeinstellungen die Option Aktualisierung von Objekten im Hintergrund abgeschaltet werden. Ansonsten kann es bei Verwendung von Haltepunkten in der Calculate-Methode zum Absturz von FlexPro kommen. Die Ursache dafür ist, dass es in der VBA-Umgebung zu Problemen kommt, die Calculate-Methode in einem Hintergrundprogrammfaden (Thread) aufgerufen wird.

Für benutzerdefinierte FPScript-Funktionen in VBA wird vom Laufzeitsystem sichergestellt, dass zu einem bestimmten Zeitpunkt nur eine Ausführung der Calculate-Methode erfolgt. Diese Einschränkung erfolgt aus Sicherheitsgründen. Zum einen unterstützt die VBA-Laufzeitumgebung nicht wirklich parallele Ausführung, zum anderen ist die ggf. notwendige Beschränkung auf eine ausführende Instanz in VBA selbst nicht mit den Mitteln der Sprache alleine möglich.

Artikel teilen oder als Email versenden:

Diese Beiträge könnten Sie ebenfalls interessieren