saxonica.com

Writing extension functions for .NET

On the .NET platform extension functions may be implemented in any .NET language (the examples here assume C#).

Queries and stylesheets running on the .NET platform may also call Java extension functions, provided the Java class is part of the standard library implemented in the OpenJDK DLL, or in Saxon itself. For calling conventions, see Writing extension functions in Java. If you want to write your own extensions in Java, you will need to compile them to .NET assemblies using IKVMC.

There are two ways of using extension functions. You can either load the assembly containing the extension dynamically from within the stylesheet or query, or you can pass a parameter to the stylesheet or query whose value is an "external object", and then make calls on methods available on this external object. Dynamic loading of assemblies can be tricky from a configuration point of view, and is also expensive at run-time, so passing in a reference to an external object may often be the better approach. This is possible only when you invoke the transformation from a .NET application, not when you use the command line. To create a value representing an external object, use the static factory method XdmValue.WrapExternalObject().

An extension function is invoked using a name such as prefix:localname(). The prefix must be the prefix associated with a namespace declaration that is in scope. The namespace URI is used to identify a .NET class, and the local name is used to identify a method, property, or constructor within the class.

The command line option -TJ is useful for debugging the loading of .NET extensions. It gives detailed information about the methods that are examined for a possible match.

The basic form of the namespace URI is "clitype:" followed by the fully-qualified type name (for example xmlns:env="clitype:System.Environment"). This form works for system classes and classes in a loaded assembly. If an assembly needs to be loaded, extra information can be given in the form of URI query parameters. For example xmlns:env="clitype:Acme.Payroll.Employee?asm=payroll;version=4.12.0.0".

The parameters that are recognized are:

Keyword

Value

asm

The simple name of the assembly

ver

The version number, up to four integers separated by periods

loc

The culture (locale), for example "en-US"

sn

The public key token of the assembly's strong name, as 16 hex digits

from

The location of the assembly, as a URI

partialname

The partial name of the assembly (as supplied to Assembly.LoadWithPartialName()).

If the from keyword is present, the other parameters are ignored. The value of from must be the URI of the DLL to be loaded: if it is relative, it is relative to the base URI of the expression containing the call to the extension function (regardless of where the namespace is actually declared).

If the partialName keyword is present, the assembly is loaded (if possible) using Assembly.LoadWithPartialName() and the other parameters are ignored.

If the assembly is a library DLL in the global assembly cache, use the gacutil /l command to list the assemblies present in the GAC, and to extract the required version, culture, and strong name attributes. For example, suppose you want to call a static method disappear() in class Conjurer in namespace Magic.Circle, and this class is contained in an assembly Magic listed as:

Magic, Version=7.2.2200.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a4b, Custom=null

Then the URI you would use to identify this class is clitype:Magic.Circle.Conjurer?asm=Magic;ver=7.2.2200.0;sn=b03f5f7f11d50a4b, and an actual call of the function might take the form:


<xsl:value-of select="m:disappear()"
     xmlns:m="clitype:Magic.Circle.Conjurer?asm=Magic;ver=7.2.2200.0;sn=b03f5f7f11d50a4b"/> 

Tips for Dynamic Loading in .NET"

Here are some hints and tips that might help you to get dynamic loading working under .NET.

First decide whether you want to load the assembly (DLL) containing the extension functions from local filestore or from the Global Assembly Cache.

If you want to load it from the GAC you must compile the assembly with a strong name, and move it to the GAC for example by using the gacutil command (alternatively, use the interactive .NET configuration tool accessible from the Control Panel via Administrative Tools). Note that this needs Administrator privilege. Conversely, it you want to load the assembly locally then you should NOT give it a strong name.

For local loading, the following techniques work:

For production running it is probably more appropriate to place the assembly holding extension functions in the global assembly cache. It can then generally be referenced in one of two ways:

The following example shows how to call system methods in the .NET framework:

<out xmlns:Environment="clitype:System.Environment" 
     xmlns:OS="clitype:System.OperatingSystem">
    <xsl:variable name="os" select="Environment:OSVersion()"/>
    <v platform="{OS:Platform($os)}" version="{OS:Version($os)}"/>
</out>

Remember that you can avoid dynamic loading entirely by passing an external object as a parameter to the query or stylesheet, and then calling its methods as extension functions. See Calling .NET Instance-Level Methods for details.

Identifying and Calling Specific Methods

The rest of this section considers how a .NET method, property, or constructor is identified. This decision (called binding) is always made at the time the XPath expression is compiled.

There are three cases to consider: static methods, constructors, and instance-level methods. In addition, a public property in a class is treated as if it were a zero-argument method, so static properties can be accessed in the same way as static methods, and instance-level properties in the same way as instance-level methods. (Note that the property name is used directly: it is not prefixed by "get".)

Next