In a previous post I described how to call OpenMaple from a C# application (see: Using OpenMaple with C#) Now, I'd like to tackle the reverse problem: how do I call a C# .dll from within Maple? The way to do this involves using Component Object Model (COM) interoperability in the Microsoft .NET Framework. Because Maple's kernel is written in an "unmanaged" language, a wrapper is needed in order to bridge to your managed C# code. This article will outline the steps needed to create this bridge and get Maple talking to C#.

Step 1: Create the c# class

For the purpose of this example we'll create a very simple C# class that implements a method to multiply two numbers together. This can be saved as mymult.cs

        // Interface declaration.
        public interface MyMath
        {
            int Multiply(int Number1, int Number2);
        };

        // Interface implementation.
        public class ManagedClass:MyMath
        {
            public int Multiply(int Number1,int Number2)
            {
                return Number1*Number2;
            }
        }

Step 2: create a strong name key

From your C# command prompt execute the following command:

        sn.exe -k MyKeyFile.SNK

This will generate a strong name assembly file, MyKeyFile.SNK

Step 3. compile the c# code

Compile your code making reference to the strong name you just generated. From the C# command prompt you can build this example with the following command.

        csc /t:library mymult.cs /keyfile:mykeyfile.snk

A dynamic library named mymult.dll will be created. We can't call this .dll directly from Maple yet. A wrapper library must be created to talk to this .dll through COM.

Step 4: register the assembly and generate a COM type library

The following command will generate the type library, mymult.tlb

        RegAsm.exe mymult.dll /tlb:mymult.tlb /codebase

Step 5: Create an unmanaged C++ wrapper that calls the C# Multiply method

The source for mymultwrapper.cpp implements the function mymultwrapper. This is the wrapper function that we will call directly from Maple. Note that the "mymult.tlb" file we created in step 3 is being imported. Also note the function declarations in order to export "mymultwrapper" without decorations and using the __stdcall calling convention.

        #include "stdafx.h"

        // Import the type library.
        #import "mymult.tlb" raw_interfaces_only
        using namespace mymult;

        // declare prototype in advance with dllexport and extern "C" options
        extern "C" {
           __declspec(dllexport) long __stdcall mymultwrapper( int a, int b );
        }

        long __stdcall mymultwrapper( int a, int b )
        {
            // Initialize COM.
            HRESULT hr = CoInitialize(NULL);

            // Create the interface pointer.
            MyMathPtr pMyMath(__uuidof(ManagedClass));

            long lResult = 0;

            // Call the c# method.
            pMyMath->Multiply(a, b, &lResult);

            // Uninitialize COM.
            CoUninitialize();

            return lResult;
        }

Step 6: compile the C++ code

To build mymultwrapper.dll the following command can be used. The include path here is specified to point to a directory where "stdafx.h" is located. You may need to change this path according to your personal setup.

    cl -LD -I "c:/program files/microsoft visual studio 8/vc/atlmfc/src/atl" mymultwrapper.cpp

Step 7: load the wrapper .dll from inside Maple

The last setup step is to link in the new .dll in a Maple session. The first argument to define_external specifies the name of the procedure we are calling. The middle arguments indicate the type signature of that procedure. Lastly, the LIB= parameter specifies the .dll to load. Assuming the libraries "mymultwrapper.dll" and "mymult.dll" are in the path, in the Maple bin.win directory, in c:/windows/system32, or in the current directory, this command will work as-is. Otherwise you may need to specify the full-path to "mymultwrapper.dll" in the LIB= specification.

    mymult := define_external('mymultwrapper',
	a::integer[4], b::integer[4], RETURN::integer[4],
	LIB="mymultwrapper.dll");

Step 8: Use Your C# Method

At this point we are finished all the set-up work. We can simply call the procedure just like any other Maple proc.

> mymult(33,10);
                                      330

For future Maple sessions you will need to repeat only steps 7 and 8 to load the .dll and use it. The setup step can be hidden in a ModuleLoad function as follows:

    > MyMult := module()
        local ModuleApply, ModuleLoad;

        ModuleLoad := proc() 
            ModuleApply := define_external('mymultwrapper',
	        a::integer[4], b::integer[4], RETURN::integer[4],
	        LIB="mymultwrapper.dll");
        end proc;

        ModuleLoad();
    end module:

    > savelib(MyMult);

    > MyMult(9,9);
                                      81

ModuleLoad will automatically be triggered when MyMult is first called. It will define the ModuleApply function so this module can be used just like a procedure.

 

 

Advanced Steps: manage your own Maple-to-C++ type conversions

 

Steps 5-8 rely on Maples automatic object to native type conversions. These type conversions can be done manually for more complicated types as follows. I'll use the same example just to show the structure of how this is done.

Step A5: Create an unmanaged c++ wrapper that calls the c# function

The source for mymultwrapper2.cpp implements the function mymultwrapper2. This is the wrapper function that we will take raw Maple object arguments via the args parameter and turn them into native types with explicit calls to Maple API functions. We'll leave the wrapper function, mymultwrapper alone -- this will work the same as in the previous example.

	#include "stdafx.h"

	// Maple Extrnal API
	#include "maplec.h"

	// Import the type library.
	#import "mymult.tlb" raw_interfaces_only
	using namespace mymult;

	// declare prototype in advance with dllexport and extern "C" options
	extern "C" {
	   __declspec(dllexport) long __stdcall mymultwrapper( int a, int b );
	   __declspec(dllexport) ALGEB __stdcall mymultwrapper2( MKernelVector kv, ALGEB args );
	}

	// same as before
	long __stdcall mymultwrapper( int a, int b )
	{
	    // Initialize COM.
	    HRESULT hr = CoInitialize(NULL);

	    // Create the interface pointer.
	    MyMathPtr pMyMath(__uuidof(ManagedClass));

	    long lResult = 0;

	    // Call the c# method.
	    pMyMath->Multiply(a, b, &lResult);

	    // Uninitialize COM.
	    CoUninitialize();

	    return lResult;
	}


	// New dll entry point that will be accessed by Maple
	// We'll handle all the type conversions ourselves
	ALGEB __stdcall mymultwrapper2( MKernelVector kv, ALGEB args )
	{
	    int a, b;
	    long r;

	    if( MapleNumArgs(kv, args) != 2 )
		MapleRaiseError( kv, "2 arguments rerquired for addit" );

	    a = MapleToInteger32(kv,(ALGEB)args[1]);
	    b = MapleToInteger32(kv,(ALGEB)args[2]);

	    r = mymultwrapper(a,b);

	    return ToMapleInteger(kv,(M_INT)r);
	}

Step A6: compile the C++ code

As before, we will compile this code to generate a .dll (this time mymultwrapper2.dll). We'll need to link in maplec.lib and point to Maple's include files.

       cl -LD -I "c:/program files/microsoft visual studio 8/vc/atlmfc/src/atl" -I "c:/program files/maple 12/extern/include" mymultwrapper2.cpp "c:/program files/maple 12/bin.win/maplec.lib"

Step A7: load the wrapper .dll from inside Maple

This time, instead of supplying type signatures in the middle arguments we'll just use the keyword `MAPLE`.

    mymult := define_external('mymultwrapper2',MAPLE,
	LIB="mymultwrapper2.dll");

Step 8: Use your c# method

Now call the procedure just like any other Maple proc.


        > mymult(9,12);
                                      108

References

The following links were very useful in formulating this example. These links talk generically about calling managed C# code from unmanaged C++.


Please Wait...