© Robert Pickering and Kit Eason 2016

Robert Pickering and Kit Eason, Beginning F# 4.0, 10.1007/978-1-4842-1374-2_12

12. Compatibility and Advanced Interoperation

Robert Pickering and Kit Eason

(1)St. Germain-En-Laye, France

In this chapter, you will look at everything you need to make F# interoperate well with other languages, not just within the .NET Framework but also using unmanaged code from F# and using F# from unmanaged code.

Caution

Throughout this book, I have made every effort to make sure the only language you need to understand is F#. However, in this chapter, it will help if you know a little C#, C++, or .NET Common IL, although I’ve kept the code in these languages to the minimum necessary.

Calling F# Libraries from C#

You can create two kinds of libraries in F#: libraries that are just designed to be used from F# only and libraries that are designed to be used from any .NET language. This is because F# utilizes the .NET type system in a rich and powerful way, so some types can look a little unusual to other .NET languages . However, these types will always look like they should when viewed from F#.

Although you can use any library written in F# from any .NET language, you need to follow a few rules if you want to make the library as friendly as possible. Here is how I summarize these rules :

  • Avoid public functions that return tuples.

  • If you want to expose a function that takes another function as a value, expose the value as a delegate.

  • Avoid using union types in the API, but if you absolutely must use these types, consider adding members to make them easier to use.

  • Avoid returning F# lists, and use the array System.Collections.Generic.IEnumerable or better yet Collection or ReadOnlyCollection from the System.Collections.ObjectModel namespace instead.

  • When possible, place type definitions in a namespace, and place only value definitions within a module.

  • Be careful with the signatures you define on classes and interfaces; a small change in the syntax can make a big difference.

  • You may also want to consider adding a signature .fsi file or the private and internal keywords to hide implementation details and document the API expected by clients.

I will illustrate these points with examples in the following sections.

Returning Tuples

First, I’ll talk about why you should avoid tuples. If you return a tuple from your function, you will force the user to reference FSharp.Core.dll. Also, the code needed to use the tuple just doesn’t look that great from C#. Consider the following example where you define the function hourAndMinute that returns the hour and minute from a DateTime structure:

module Strangelights.DemoModule
open System


// returns the hour and minute from the given date as a tuple
let hourAndMinute (time: DateTime) = time.Hour, time.Minute


// returns the hour from the given date
let hour (time: DateTime) = time.Hour
// returns the minutes from the given date
let minute (time: DateTime) = time.Minute

To call this from C#, you need to follow the next example. You need to create a C# project alongside your F# solution. To do this, choose File ➤ Add ➤ New Project, and then choose a C# console project.

Next, you need to add a project reference from the C# project to the F# project. Then add the following C# class to the newly created project:

// !!! C# Source !!!
using System;
using Strangelights;
using FSharp.Core;


static class PrintClass {
        internal static void HourMinute() {
                // call the "hourAndMinute" function and collect the
                // tuple that's returned
                Tuple<int, int> t = DemoModule.hourAndMinute(DateTime.Now);
                // print the tuple's contents
                Console.WriteLine("Hour {0} Minute {1}", t.Item1, t.Item2);
        }
}

This example, when executed, returns the following:

Hour 16 Minute 1

Although the C# in this example isn’t too ugly, it would be better if the function had been split in two, one to return the hour and one to return the minute.

Exposing Functions That Take Functions As Parameters

If you want to expose functions that take other functions as parameters, the best way to do this is using delegates. Consider the following example that defines one function that exposes a function, and another that exposes a function as a delegate:

module Strangelights.DemoModule
open System


/// a function that provides filtering
let filterStringList f ra =
    ra |> Seq.filter f


// another function that provides filtering
let filterStringListDelegate (pred: Predicate<string>) ra =
        let f x = pred.Invoke(x)
        new ResizeArray<string>(ra |> Seq.filter f)

Although the filterStringList is considerably shorter than filterStringListDelegate, the users of your library will appreciate the extra effort you’ve put in to expose the function as a delegate. When you look at using the functions from C#, it’s pretty clear why. The following example demonstrates calling filterStringList. To call your function, you need to create a delegate and then use the FuncConvert class to convert it into a FastFunc, which is the type F# uses to represent function values. As well as being pretty annoying for the user of your library, this also requires a dependency on FSharp.Core.dll that the user probably doesn’t want.

// !!! C# Source !!!
using System;
using System.Collections.Generic;
using Strangelights;
using FSharp.Core;


class MapOneClass {
        public static void MapOne() {
                // define a list of names
                List<string> names = new List<string>(
                        new string[] { "Stefany", "Oussama",
        "Sebastien", "Frederik" });


                // define a predicate delegate/function
                Converter<string, bool> pred =
                        delegate(string s) { return s.StartsWith("S"); };


                // convert to a FastFunc
                FastFunc<string, bool> ff =
                        FuncConvert.ToFastFunc<string, bool>(pred);


                // call the F# demo function
                IEnumerable<string> results =
                         DemoModule.filterStringList(ff, names);


                // write the results to the console
                foreach (var name in results) {
                        Console.WriteLine(name);
                }
        }
}

This example, when executed, returns the following:

Stefany
Sebastien

Now, compare and contrast this to calling the filterStringListDelegate function, shown in the following example. Because you used a delegate, you can use the C# anonymous delegate feature and embed the delegate directly into the function call, reducing the amount of work the library user has to do and removing the compile-time dependency on FSharp.Core.dll.

// !!! C# Source !!!
using System;
using System.Collections.Generic;
using Strangelights;


class MapTwoClass {
        public static void MapTwo() {
                // define a list of names
                List<string> names = new List<string>(
                        new string[] { "Aurelie", "Fabrice",
            "Ibrahima", "Lionel" });


                // call the F# demo function passing in an
                // anonymous delegate
                List<string> results =
                        DemoModule.filterStringListDelegate(
                                delegate(string s) { return s.StartsWith("A"); }, names);


                // write the results to the console
                foreach (var s in results) {
                        Console.WriteLine(s);
                }
        }
}

This example, when executed, returns the following:

Aurelie

Using Union Types

You can use union types from C#, but because C# has no real concept of a union type, they do not look very pretty when used in C# code . In this section, you will examine how you can use them in C# and how you as a library designer can decide whether and how your library will expose them. Personally I recommend avoiding exposing them in cross-language scenarios.

For the first example, you define the simple union type Quantity, which consists of two constructors, one containing an integer and the other a floating-point number. You also provide the function getRandomQuantity() to initialize a new instance of Quantity.

module Strangelights.DemoModule
open System


// type that can represent a discrete or continuous quantity
type Quantity =
| Discrete of int
| Continuous of float


// initalize random number generator
let rand = new Random()
// create a random quantity
let getRandomQuantity() =
    match rand.Next(1) with
    | 0 -> Quantity.Discrete (rand.Next())
    | _ ->
        Quantity.Continuous
            (rand.NextDouble() * float (rand.Next()))

Although you provide getRandomQuantity() to create a new version of the Quantity type, the type itself provides static methods for creating new instances of the different constructors that make up the type. These static methods are available on all union types that are exposed by the assembly by default; you do not have to do anything special to get the compiler to create them. They are named after the DU case, prefixed with the word “New.” The following example shows how to use these methods from C#:

using System;
using Strangelights;


static class GetQuantityZeroClass {
        public static void GetQuantityZero() {
                // initialize both a Discrete and Continuous quantity
                DemoModule.Quantity d = DemoModule.Quantity.NewDiscrete(12);
                DemoModule.Quantity c = DemoModule.Quantity.NewContinuous(12.0);
        }
}

Now you know how to create union types from C#, so the next most important task is being able to determine the case to which a particular Quantity value belongs. You can do this in three ways: using the Tag property, using the Is Case methods, or providing explicit functionality within your F# code.

The first option is that you can switch on the value’s Tag property. This property is just an integer, but the compiled version of the union type provides constants, contained in an inner class called Tags, to help you decode the meaning of the integer. If you want to use the Tag property to find out what kind of Quantity you have, you usually write a switch statement, as shown in the following example. Notice also how you have to cast q to the appropriate DU case in order to access its value in the Item property.

// !!! C# Source !!!
using System;
using Strangelights;


static class GetQuantityOneClass
{
    public static void GetQuantityOne()
    {
        // get a random quantity
        DemoModule.Quantity q = DemoModule.getRandomQuantity();


        // use the .Tags property to switch over the quatity
        switch (q.Tag)
        {
            case DemoModule.Quantity.Tags.Discrete:
                Console.WriteLine("Discrete value: {0}", (q as DemoModule.Quantity.Discrete).Item);
                break;
            case DemoModule.Quantity.Tags.Continuous:
                Console.WriteLine("Continuous value: {0}", (q as DemoModule.Quantity.Continuous).Item);
                break;
        }
    }
}

This example, when executed, returns the following:

Discrete value: 65676

If you prefer, the compiled form of the union type also offers a series of properties , all prefixed with Is. This allows you to check whether a value belongs to a particular case within the union type. For example, on the Quantity union type, two properties, IsDiscrete and IsContinuous, allow you to check whether the Quantity is Discrete or Continuous. The following example demonstrates how to use them:

// !!! C# Source !!!
using System;
using Strangelights;


static class GetQuantityTwoClass
{
    public static void GetQuantityTwo()
    {
        // get a random quantity
        DemoModule.Quantity q = DemoModule.getRandomQuantity();
        // use if … else chain to display value
        if (q.IsDiscrete)
        {
            Console.WriteLine("Discrete value: {0}", (q as DemoModule.Quantity.Discrete).Item);
        }
        else if (q.IsContinuous)
        {
            Console.WriteLine("Continuous value: {0}", (q as DemoModule.Quantity.Continuous).Item);
        }
    }
}

This example, when executed, returns the following:

Discrete value: 2058

Neither option is particularly pleasing because the code required to perform the pattern matching is quite bulky. There is also a risk that the user could get it wrong and write something like the following example where they check whether a value is Discrete and then mistakenly cast to the Continuous case. This would lead to an InvalidCastException being thrown.

DemoModule.EasyQuantity q = DemoModule.getRandomQuantity();
if (q.IsDiscrete) {
    Console.WriteLine("Discrete value: {0}", (q as DemoModule.Quantity.Continuous).Item);
}

To provide the users of your libraries some protection against this, it is a good idea where possible to add members to union types that perform the pattern matching for them. The following example revises the Quantity type to produce EasyQuantity, adding two members to transform the type into an integer or a floating-point number:

module Strangelights.ImprovedModule
open System


// type that can represent a discrete or continuous quantity
// with members to improve interoperability
type EasyQuantity =
| Discrete of int
| Continuous of float
    // convert quantity to a float
    member x.ToFloat() =
        match x with
        | Discrete x -> float x
        | Continuous x -> x
    // convert quantity to a integer
    member x.ToInt() =
        match x with
        | Discrete x -> x
        | Continuous x -> int x


// initalize random number generator
let rand = new Random()


// create a random quantity
let getRandomEasyQuantity() =
    match rand.Next(1) with
    | 0 -> EasyQuantity.Discrete (rand.Next())
    | _ ->
        EasyQuantity.Continuous
            (rand.NextDouble() * float (rand.Next()))

This will allow the user of the library to transform the value into either an integer or a floating-point without having to worry about pattern matching , as shown in the following example:

// !!! C# Source !!!
using System;
using Strangelights;


class GetQuantityThreeClass {
        public static void GetQuantityThree() {
                // get a random quantity
                ImprovedModule.EasyQuantity q = ImprovedModule.getRandomEasyQuantity();
                // convert quantity to a float and show it
                Console.WriteLine("Value as a float: {0}", q.ToFloat());
        }
}

Obviously the applicability of this approach varies, but there is often a sensible behavior that you can define for each case of your discriminated union for the benefit of C# callers.

Using F# Lists

It is entirely possible to use F# lists from C#, but I recommend avoiding this since a little work on your part will make things seem more natural for C# programmers. For example, it is simple to convert a list to an array using the List.toArray function; to a System.Collections.Generic.List using the new ResizeArray<_>() constructor; or to a System.Collections.Generic.IEnumerable using the List.toSeq function. These types are generally a bit easier for C# programmers to work with, especially System.Array and System.Collections.Generic.List, because they provide a lot more member methods. You can do the conversion directly before the list is returned to the calling client, making it entirely feasible to use the F# list type inside your F# code. MSDN recommends using the types Collection or ReadOnlyCollection from the System.Collections.ObjectModel namespace to expose collections. Both of these classes have a constructor that accepts an IEnumerable, and so can be constructed from an F# list.

If you need to return an F# list directly, you can do so as follows:

module Strangelights.DemoModule

// gets a preconstructed list
let getList() =
    [1; 2; 3]

To use this list in C#, you typically use a foreach loop:

using System;
using Strangelights;
using FSharp.Core;
using FSharp.Collections;


class Program {
        static void Main(string[] args) {
                // get the list of integers
                List<int> ints = DemoModule.getList();


                // foreach over the list printing it
                foreach (int i in ints) {
                        Console.WriteLine(i);
                }
        }
}

This example, when executed, returns the following:

1
2
3

Defining Types in a Namespace

If you are defining types that will be used from other .NET languages, then you should place them inside a namespace rather than inside a module. This is because modules are compiled into what C# and other .NET languages consider to be a class, and any types defined within the module become inner classes of that type. Although this does not present a huge problem to C# users, the C# client code does look cleaner if a namespace is used rather than a module. This is because in C# you can open namespaces but not modules using the using statement, so if a type is inside a module, it must always be prefixed with the module name when used from C#.

Let’s take a look at an example of doing this. The following example defines TheClass, which is defined inside a namespace. You also want to provide some functions that go with this class. These can’t be placed directly inside a namespace because values cannot be defined inside a namespace. In this case, you define a module with a related name, such as TheModule, to hold the function values.

namespace Strangelights
open System.Collections.Generic


// this is a counter class
type TheClass(i) =
    let mutable theField = i
    member x.TheField
        with get() = theField
    // increments the counter
    member x.Increment() =
        theField <- theField + 1
    // decrements the count
    member x.Decrement() =
        theField <- theField - 1


// this is a module for working with the TheClass
module TheModule = begin
    // increments a list of TheClass
    let incList (theClasses: List<TheClass>) =
        theClasses |> Seq.iter (fun c -> c.Increment())
    // decrements a list of TheClass
    let decList (theClasses: List<TheClass>) =
        theClasses |> Seq.iter (fun c -> c.Decrement())
end

Using the TheClass class in C# is now straightforward because you do not have to provide a prefix , and you can also get access to the related functions in TheModule easily:

// !!! C# Source !!!
using System;
using System.Collections.Generic;
using Strangelights;


class Program {
        static void UseTheClass() {
                // create a list of classes
                List<TheClass> theClasses = new List<TheClass>() {
                        new TheClass(5),
                        new TheClass(6),
                        new TheClass(7)};


                // increment the list
                TheModule.incList(theClasses);


                // write out each value in the list
                foreach (TheClass c in theClasses) {
                        Console.WriteLine(c.TheField);
                }
        }
        static void Main(string[] args) {
                UseTheClass();
        }
}

Defining Classes and Interfaces

In F# there are two ways you can define parameters for functions and members of classes: the “curried” style where members can be partially applied and the “tuple” style where all members must be given at once. Fortunately, from the point of view of C#, both such styles appear as tuple-style calls.

Consider the following example in which you define a class in F#. Here one member has been defined in the curried style, called CurriedStyle, and the other has been defined in the tuple style, called TupleStyle.

namespace Strangelights

type DemoClass(z: int) =
    // method in the curried style
    member this.CurriedStyle x y = x + y + z
    // method in the tuple style
    member this.TupleStyle (x, y) = x + y + z

When viewed from C#, both methods appear in standard C# style (i.e. with the parameters bracketed together):

public int TupleStyle(int x, int y);
public int CurriedStyle(int x, int y);

Specifying abstract members in interfaces and classes is slightly more complicated because you have a few more options. The following example demonstrates this:

namespace Strangelights

type IDemoInterface =
    // method in the curried style
    abstract CurriedStyle: int -> int -> int
    // method in the C# style
    abstract CSharpStyle: int * int -> int
    // method in the C# style with named arguments
    abstract CSharpNamedStyle: x : int * y : int -> int
    // method in the tupled style
    abstract TupleStyle: (int * int) -> int

When you implement these members in C#, the subtle differences between the implementations become apparent:

class CSharpClass : Strangelights.IDemoInterface
{
    public int CurriedStyle(int value1, int value2)
    {
        return value1 + value2;
    }


    public int CSharpStyle(int value1, int value2)
    {
        return value1 + value2;
    }


    public int CSharpNamedStyle(int x, int y)
    {
        return x + y;
    }


    public int TupleStyle(Tuple<int, int> value)
    {
        return value.Item1 + value.Item2;
    }
}

Note that the difference between C# style and curried style is disguised by the F# compiler, so you can call from C# in the same way in these two cases. C# named style is the most caller-friendly because the IDE can know the names of the interface parameters and hence can name the arguments meaningfully when generating code. Tuple style, where you explicitly require that the caller used an F# tuple at the call site, is the least caller-friendly.

Calling Using COM Objects

Note

The need to interact with COM objects, or with unmanaged code, is now something of a rarity in the .NET world. However, we’ve decided to leave this and the following sections in this edition of Beginning F# to help get you started in those very few cases where you may find yourself having to do so.

Most programmers who work with the Windows platform will be familiar with the Component Object Model (COM). To a certain extent the .NET Framework was meant to replace COM, but you will still encounter COM-based systems from time to time.

The .NET Framework was designed to interoperate well with COM, and calling COM components is generally quite straightforward. Calling COM components is always done through a managed wrapper that takes care of calling the unmanaged code for you. You can produce these wrappers using a tool called TlbImp.exe, the Type Library Importer, that ships with the .NET SDK.

Note

You can find more information about the TlbImp.exe tool at http://msdn.microsoft.com/en-us/library/tt0cf3sx(v=vs.110).aspx .

However, despite the existence of TlbImp.exe, if you find yourself in a situation where you need to use a COM component, first check whether the vendor provides a managed wrapper for it, called primary interop assemblies. For more information on primary interop assemblies, see the next section, “Using COM-Style APIs.”

However, sometimes it is necessary to use TlbImp.exe directly. Fortunately, this is very straightforward. Normally all that is necessary is to pass TlbImp.exe the location of the .dll that contains the COM component, and the managed wrapper will be placed in the current directory. If you want to create a managed wrapper for the Microsoft Speech API, you use the following command line:

tlbimp "C:Program FilesCommon FilesMicrosoft SharedSpeechsapi.dll"
Note

There are two command-line switches that I find useful with TlbImp.exe. These are /out:, which controls the name and location of the resulting manage wrapper, and /keyfile:, which can provide a key to sign the output assembly.

The resulting .dll is a .NET assembly and can be used just like any .NET assembly, by referencing it via the fsc.exe command line switch -r. A useful side effect of this is if the API is not well documented, you can use an assembly browser, such as .NET Reflector (available via the Visual Studio Gallery), to find out more about the structure of the API.

After that, the worst thing I can say about using managed wrappers is you might find the structure of these assemblies a little unusual since the COM model dictates structure and therefore they do not share the same naming conventions as most .NET assemblies. You will notice that all classes in the assembly are postfixed with the word Class and each one is provided with a separate interface: this is just a requirement of COM objects . The following example shows the wrapper for the Microsoft Speech API that you created in the previous example being used:

open SpeechLib

let main() =
    // create an new instance of a com class
    // (these almost always end with "Class")
    let voice = new SpVoiceClass()
    // call a method Speak, ignoring the result
    voice.Speak("Hello world", SpeechVoiceSpeakFlags.SVSFDefault) |> ignore


do main()

Using COM-Style APIs

Rather than using COM libraries directly, creating your own wrappers, it’s more likely you’ll have to use COM-style API’s . This is because many vendors now distribute their applications with primary interop assemblies. These are precreated COM wrappers, so generally you won’t need to bother creating wrappers with TlbImp.exe yourself.

Note

More information about primary interop assemblies can be found on MSDN at https://msdn.microsoft.com/en-us/library/aa302338.aspx .

Although primary interop assemblies are just ordinary .NET assemblies, there are typically a few quirks you have to watch out for, such as the following:

  • Some arrays and collections often start at one rather than zero.

  • There are often methods that are composed of large numbers of optional arguments. Fortunately, F# supports optional and named arguments to make interacting with these more natural and easier to understand.

  • Many properties and methods have a return type of object. The resulting object needs to be cast to its true type.

  • COM classes contain unmanaged resources that need to be disposed of. However, these classes do not implement the standard .NET IDisposable interface, meaning they cannot be used in an F# use binding. Fortunately, you can use F# object expressions to easily implement IDisposable.

A key difference when interacting with COM in F# as opposed to C# is that you must always create instances of objects, not interfaces. This may sound strange, but in COM libraries each object typically has an interface and a class that implements it. In C#, if you try to create an instance of a COM interface using the new keyword in C#, the compiler will automatically redirect the call to the appropriate class, but this is not the case in F#.

Interacting with Microsoft Office is probably the most common reason for interacting with COM-style libraries. Here is code that reads information from an Excel spreadsheet:

open System
open Microsoft.Office.Interop.Excel


let main() =
    // initalize an excel application
    let app = new ApplicationClass()


    // load a excel work book
    let workBook = app.Workbooks.Open(@"Book1.xls", ReadOnly = true)
    // ensure work book is closed corectly
    use bookCloser = { new IDisposable with
                        member x.Dispose() = workBook.Close() }


    // open the first worksheet
    let worksheet = workBook.Worksheets.[1] :?> _Worksheet


    // get the A1 cell and all surrounding cells
    let a1Cell = worksheet.Range("A1")
    let allCells = a1Cell.CurrentRegion
    // load all cells into a list of lists
    let matrix =
        [ for row in allCells.Rows ->
            let row = row :?> Range
            [ for cell in row.Columns ->
                let cell = cell :?> Range
                cell.Value2 ] ]


    // close the workbook
    workBook.Close()


    // print the matrix
    printfn "%A" matrix


do main()

Notice how this sample deals with some of the quirks I mentioned earlier. You implement IDisposable and bind it to bookCloser to ensure the workbook is closed, even in the case of an error. The Open method actually has 15 arguments, though you only use two: .Open(@"Book1.xls", ReadOnly = true). The first worksheet is an index one: workBook.Worksheets.[1]. Finally, each row must be upcast in order to use it: let row = row :?> Range.

Using P/Invoke

P/Invoke , or platform invoke to give its full name, is used to call unmanaged flat APIs implemented in DLLs and is called using the C or C++ calling conventions. The most famous example of this is the Win32 API, a vast library that exposes all the functionality built into Windows.

To call a flat unmanaged API, you must first define the function you want to call; you can do this in two parts. First, you use the DllImport attribute from the System.Runtime.InteropServices namespace, which allows you to define which .dll contains the function you want to import, along with some other optional attributes. Then you use the keyword extern; followed by the signature of the function to be called in the C style, meaning you give the return type, the F# type, the name of the function, and finally the types and names of the parameters surrounded by parentheses. The resulting function can then be called as if it were an external .NET method.

The following example shows how to import the Windows function MessageBeep and then call it:

open System.Runtime.InteropServices

// declare a function found in an external dll
[<DllImport("User32.dll")>]
extern bool MessageBeep(uint32 beepType)


// call this method ignoring the result
MessageBeep(0ul) |> ignore
Note

The trickiest part of using P/Invoke can often be working out what signature to use to call the function. The web site http://pinvoke.net contains a list of signatures for common APIs in C# and VB .NET, which are similar to the required signature in F#. The site is a wiki, so feel free to add F# signatures as you find them.

The following code shows how to use P/Invoke when the target function expects a pointer. You need to note several points about setting up the pointer. When defining the function, you need to put an asterisk (*) after the type name to show that you are passing a pointer. You need to define a mutable identifier before the function call to represent the area of memory that is pointed to. This may not be global, in the top level, but it must be part of a function definition. This is why you define the function main, so the identifier status can be part of the definition of this. Finally, you must use the address of operator (&&) to ensure the pointer is passed to the function rather than the value itself.

Tip

This compiled code will always result in a warning because of the use of the address of operator (&&). This can be suppressed by using the compiler flag --nowarn 51 or the command #nowarn 51.

open System.Runtime.InteropServices

// declare a function found in an external dll
[<DllImport("Advapi32.dll")>]
extern bool FileEncryptionStatus(string filename, uint32* status)


let main() =
    // declare a mutable idenifier to be passed to the function
    let mutable status = 0ul
    // call the function, using the address of operator with the
    // second parameter
    FileEncryptionStatus(@"C: est.txt", && status) |> ignore
    // print the status to check it has be altered
    printfn "%d" status


main()

The result of this example, when compiled and executed (assuming you have a file at the root of your C: drive called test.txt that is encrypted), is as follows:

1ul
Note

P/Invoke also works on Mono, and in F# the syntax is exactly the same. The tricky bit is ensuring the library you are invoking is available on all the platforms you’re targeting and following the different naming conventions of libraries on all the different platforms. For a more detailed explanation, see the article at www.mono-project.com/docs/advanced/pinvoke/ .

The DllImport attribute has some useful functions that can be set to control how the unmanaged function is called. I summarize them in Table 12-1.

Table 12-1. Useful Attributes on the DllImport Attribute

Attribute Name

Description

CharSet

This defines the character set to be used when marshaling string data. It can be CharSet.Auto, CharSet.Ansi, or CharSet.Unicode.

EntryPoint

This allows you to set the name of the function to be called. If no name is given, then it defaults to the name of the function as defined after the extern keyword.

SetLastError

This is a Boolean value that allows you to specify whether any error that occurs should be marshaled and therefore available by calling the Marshal.GetLastWin32Error() method.

Note

As with COM components, the number of flat unmanaged APIs that have no .NET equivalent is decreasing all the time. Always check whether a managed equivalent of the function you are calling is available, which will generally save you lots of time.

Using F# from Native Code via COM

Although it is more likely that you will want to call native code from F# code, there may be some times when you want to call F# library functions from native code. For example, suppose you have a large application written in C++, and perhaps you are happy for the user interface to remain in C++ but want to migrate some logic that performs complicated mathematical calculations to F# for easier maintenance. In this case, you want to call F# from native code. The easiest way to do this is to use the tools provided with .NET to create a COM wrapper for your F# assembly. You can then use the COM runtime to call the F# functions from C++.

To expose functions though COM, you need to develop them in a certain way. First, you must define an interface that will specify the contract for your functions. The members of the interface must be written using named arguments (see the section on “Calling F# Libraries from C#” earlier in the chapter), and the interface itself must be marked with the System.Runtime.InteropServices.Guid attribute. Then you must provide a class that implements the interface. This too must be marked the System.Runtime.InteropServices.Guid attribute and also the System.Runtime.InteropServices.ClassInterface, and you should always pass the ClassInterfaceType.None enumeration member to the ClassInterface attribute constructor to say that no interface should be automatically generated.

Let’s look at an example of doing this. Suppose you want to expose two functions to your unmanaged client called Add and Sub. Create an interface named IMath in the namespace Strangelights, and then create a class named Math to implement this interface. Now you need to ensure that both the class and the interface are marked with the appropriate attributes. The resulting code is as follows:

namespace Strangelights
open System
open System.Runtime.InteropServices


// define an interface (since all COM classes must
// have a seperate interface)
// mark it with a freshly generated Guid
[<Guid("6180B9DF-2BA7-4a9f-8B67-AD43D4EE0563")>]
type IMath =
    abstract Add : x: int * y: int -> int
    abstract Sub : x: int * y: int -> int


// implement the interface, the class must:
// - have an empty constuctor
// - be marked with its own guid
// - be marked with the ClassInterface attribute
[<Guid("B040B134-734B-4a57-8B46-9090B41F0D62");
ClassInterface(ClassInterfaceType.None)>]
type Math() =
    interface IMath with
        member this.Add(x, y) = x + y
        member this.Sub(x, y) = x - y

The functions Add and Sub are of course simple, so there is no problem implementing them directly in the body of the Math class. If you need to break them down into other helper functions outside of the class, this is not a problem. It is fine to implement your class members any way you see fit. You simply need to provide the interface and the class so the COM runtime has an entry point into your code.

Now comes arguably the most complicated part of the process: registering the assembly so the COM runtime can find it. To do this, you need to use a tool called RegAsm.exe. Suppose you compiled the previous sample code into a .NET .dll called ComLibrary.dll. Now you need to call RegAsm.exe twice using the following command lines:

regasm comlibrary.dll /tlb:comlibrary.tlb
regasm comlibrary.dll

The first time is to create a type library file, a .tlb file, which you can use in your C++ project to develop against. The second registers the assembly itself so the COM runtime can find it. You will also need to perform these two steps on any machine to which you deploy your assembly.

The C++ to call the Add function is as follows . The development environment and how you set up the C++ compiler will also play a large part in getting this code to compile. In this case, I created a Visual Studio project, choosing a console application template, and activated ATL. Notice the following about this source code :

  • The #import command tells the compiler to import your type library. You may need to use the full path to its location. The compiler will also automatically generate a header file, in this case comlibrary.tlh, located in the debug or release directory. This is useful because it lets you know the functions and identifiers that are available as a result of your type library.

  • You then need to initialize the COM runtime. You do this by calling the CoInitialize function.

  • You then need to declare a pointer to the IMath interface you created. You do this via the code comlibrary::IMathPtr pDotNetCOMPtr;. Note how the namespace comes from the library name rather than the .NET namespace.

  • Next, you need to create an instance of your Math class. You achieve this by calling the CreateInstance method, passing it the GUID of the Math class. Fortunately, there is a constant defined for this purpose.

  • If this was successful, you can call the Add function. Note how the result of the function is actually an HRESULT, a value that will tell you whether the call was successful. The actual result of the function is passed out via an out parameter.

// !!! C++ Source !!!
#include "stdafx.h"
// import the meta data about out .NET/COM library
#import "..ComLibraryComLibrary.tlb" named_guids raw_interfaces_only


// the applications main entry point
int _tmain(int argc, _TCHAR* argv[])
{
        // initialize the COM runtime
        CoInitialize(NULL);
        // a pointer to our COM class
    comlibrary::IMathPtr pDotNetCOMPtr;


        // create a new instance of the Math class
        HRESULT hRes = pDotNetCOMPtr.CreateInstance(comlibrary::CLSID_Math);
        // check it was created okay
        if (hRes == S_OK)
        {
                // define a local to hold the result
        long res = 0L;
                // call the Add function
                hRes = pDotNetCOMPtr->Add(1, 2, &res);
                // check Add was called okay
            if (hRes == S_OK)
            {
                        // print the result
            printf("The result was: %ld", res);
        }


                // release the pointer to the math COM class
        pDotNetCOMPtr.Release();
        }


        // uninitialise the COM runtime
        CoUninitialize ();
}

This example, when executed, returns the following:

The result was: 3

When you execute the resulting executable, you must ensure that ComLibrary.dll is in the same directory as the executable or the COM runtime will not be able to find it. If you intend that the library be used by several clients, then I strongly recommend that you sign the assembly and place it in the GAC. This will allow all clients to be able to find it without having to keep a copy in the directory with them.

Summary

In this chapter, you saw some advanced techniques in F# for compatibility and interoperation. Although these techniques are definitely some of the most difficult to master, they also add a huge degree of flexibility to your F# programming.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.227.82