Contents Previous Next Index
|
14 Advanced Connectivity
|
|
This chapter describes how to connect Maple to other applications. Maple can be connected as the main interface (for example, to a database), as a hidden engine (for example, as part of a Microsoft® Excel® plug-in), or side-by-side with another application (for example, a CAD application). You can also use Maple to generate code.
|
14.1 In This Chapter
|
|
•
|
Connecting to the Maple engine
|
•
|
Embedding external libraries in Maple
|
•
|
Connecting Maple to another program
|
|
Connecting to the Maple Engine
|
|
There are several ways to use the Maple computation engine in other applications. For example, you can create a financial application that runs calculations using the MapleNet web service API; create a plug-in for Microsoft Excel in both Visual Basic and C++ to perform Maple computations in a spreadsheet; and create an engineering process to generate and batch process scripts using the Maple command-line interface. These are examples of real situations where you can use Maple as a calculation engine embedded in an external interface.
For more information, see MapleNet, OpenMaple, and The Maple Command-line Interface.
|
|
Using External Libraries in Maple
|
|
Most dynamic link-libraries (.dlls) that contain mathematical functions written in another programming language can be linked directly in Maple. Using the commands in these libraries usually requires you to translate the Maple data into a format that the external library can interpret. Maple provides an extensive API for data conversions, evaluation, and process control. You can, therefore, use a custom library of functions in Maple as if it were a regular Maple package. You can also use the Maple external API to connect Maple to a hardware device for data acquisition, link with an open source suite of utilities, or to avoid rewriting legacy code.
For more information, see External Calling: Using Compiled Code in Maple.
|
|
Code Generation
|
|
The Maple programming language and the worksheet environment are ideal for creating prototypes. They are also ideal for error-free algebraic manipulations and long calculations. When you create a prototype that includes various formulas, you can easily write that program in the native language that is used by your application. The generated code can then be compiled and embedded directly in your application.
The CodeGeneration package provides commands for translating Maple code into other programming languages such as C, Visual C#, Fortran, Java, MATLAB®, and Visual Basic. The resulting code can be compiled and used in applications other than Maple.
For more information, see Code Generation.
|
|
|
14.2 MapleNet
|
|
You can use MapleNet to publish your Maple applications on the web. Two key features are provided by MapleNet: in-browser interfaces and the ability to connect to a Maple computation engine over the Internet or an intranet.
MapleNet is a Maple add-on product. For more information, visit http://www.maplesoft.com/products/maplenet/index.aspx.
|
Computation on Demand
|
|
A web service is software on a web server that listens for requests and waits to perform a specific job. The MapleNet web service, in particular, waits to run Maple commands.
A desktop application that has a network connection can communicate with the MapleNet web service to perform computations. The client computer that runs the desktop application does not require Maple, a Maple license, or specific software to communicate with the web service.
The server-side infrastructure for Maple web services is provided by the MapleNet API. You can build client applications to use the MapleNet web service by using tools such as the Microsoft C# toolkit, Apache™ Axis plug-in for Eclipse, IBM® WebSphere®, and the NetBeans IDE.
Web services are defined by using a file containing the Web Services Description Language (WSDL). Before creating a client application, download the definition of the MapleService by using the WSDL file located at the following URL.
http://yourserver.com:port/maplenet/services/MapleService?wsdl
This URL points to a link on your server where MapleNet is installed. yourserver.com is the name or IP address of your MapleNet server and port is the HTTP port on which your server listens for requests (for example, 80 or 8080).
This URL can be used in your web services toolkit so that you can create the code to connect to the MapleService web service.
The following language-independent APIs are the most commonly used methods in the web service.
String result = evaluate(String expression);
String[] results = callMaple(String[] expressions);
|
|
|
The evaluate method accepts a string that is a valid Maple expression and returns a string corresponding to the results generated by Maple after evaluating that expression.
The callMaple method accepts an array of strings of valid Maple expressions and returns an equivalent number of strings corresponding to the results generated by Maple after each expression is evaluated.
When a call to the web service is complete, the Maple kernel is released. In other words, the state (for example, variable assignments) is not preserved.
Results are returned from the web service call as text. The text can either be a 1-D Maple output expression or a Base64-encoded plot. Plot results take the following form.
"_Base64GIF(width,height,data)"
|
|
|
In this example, _Base64GIF is a literal text string, which indicates that the result is an encoded plot. Following this string are two integers indicating the width and height of the output plot. Finally, data is the binary plot data, which is a Base64-encoded .gif image. A Base64 decoder must be used to translate the data into binary data that can either be rendered by your client application or written out to form the bytes of a .gif file.
For more information and examples, refer to the MapleNet Publisher's Guide.
|
|
Embedding a Maple Application in a Web Application
|
|
Embedding a Maple application is easy: save your Maple document on a MapleNet server, open a web browser, and point to the saved .mw file. Your worksheet is not only visible, but also interactive if you included buttons, plots, or other embedded components in the .mw file.
Maplet applications are also easy to publish over the web.
Maple can also be embedded in larger web applications and used as the underlying computation engine for your application. For example, by using MapleNet functionality, you can use Maple as the graphics engine for scientific or business plots; you can also create an equation editor to read formula input or display formatted equations.
You can also use two other technologies to include Maple as part of your web applications: Java applets and JavaServer Pages (JSP).
Java applets, in conjunction with the MapleNet Java API, extend the browser capabilities beyond those provided by HTML alone. For example, the Maple equation editor is an applet that you can use to capture input using natural math notation. Another applet allows you to embed 3-D plots with controls for rotation, zooming, and changing properties such as constrained access and color shading. By using the Java API, you can create custom applets, for example, an applet that plots points in-place where you click, and uses Maple to compute a smooth curve that interpolates the points. To run applets, you require a Java-enabled browser.
JSP provides an alternate technology. Unlike applets, JSP pages are resolved on the server; the client computer only processes HTML code. Therefore, no special requirements are needed for the browser or computer running the browser to display the JSP results.
JSP is an extension to HTML. An HTML file can be extended by adding <maple> tags. When the web server displays one of these pages, it replaces the <maple> tags with the result of the specified computation. The result is an embedded image, text, or any other element that you want to include.
MapleNet comes with complete documentation and detailed examples. For more information on the Java applet and JSP APIs, refer to the MapleNet Publisher's Guide.
|
|
|
14.3 OpenMaple
|
|
OpenMaple is an interface that lets you access the Maple computation engine by referencing its dynamic-link library (.dll) file.
Note: The OpenMaple interface is available on all platforms supported by Maple. The convention in this guide is to use the terminology related to .dll files, in place of .so or .dylib on other systems.
You can use this interface to embed calls to Maple in other applications.
Interfaces to access the OpenMaple API are provided for use with C, C++, Java, Fortran, C#, and Visual Basic. All of these interfaces are built on the C API, so they all reference the primary library, maplec.dll, which is located in your Maple binary directory. This library can be accessed from other languages by following the protocol established in the maplec.dll file.
Complete example programs are available in the samples/OpenMaple subdirectory of your Maple installation. In conjunction with reading this section, you may want to try extending one or more of those examples before creating your own programs.
Application-specific header files can be found in the extern/include subdirectory of your Maple installation. If you are developing a Java application, you can find the jopenmaple.jar file in the java subdirectory of your Maple installation.
|
Runtime Environment Prerequisites
|
|
To run your application, two paths must be set up in your local environment.
•
|
the path to the maplec.dll file
|
•
|
the path to the top-level directory in which Maple is installed
|
In Windows, depending on the source programming language, calls to initialize the OpenMaple interface will locate these paths automatically so that the Maple commands will work without additional configuration steps.
Note: If your application does not initialize, set your Windows %PATH% environment variable to include the Maple bin.win or bin.X86_64_WINDOWS directory. To find out which path to use, run the kernelopts(bindir) command in Maple.
In UNIX, Linux, Solaris, and Macintosh, the MAPLE, and LD_LIBRARY_PATH or DYLD_LIBRARY_PATH environment variables must be set before starting your application. To set these environment variables, add the following lines to the start-up script of your calling application, where $MAPLE is your Maple installation directory.
#!/bin/sh
export MAPLE="/usr/local/maple"
. $MAPLE/bin/maple -norun
myapp $*
|
|
|
These commands run the maple launch script to configure your environment without starting Maple. The period (.) prefix in a Bourne shell causes the commands to be sourced, thus, applying the settings to future sessions. Starting the application would be done via the above script rather than calling the executable directly.
|
|
Interface Overview
|
|
Each OpenMaple application contains the following components:
•
|
a text callback to display or hide the output generated by Maple when an expression is evaluated
|
•
|
commands to initialize the Maple engine
|
•
|
calls to API commands to compute with Maple
|
The examples in this section show how to run a Maple computation using the OpenMaple API. Each example evaluates an expression and then exits. The examples in this section are intended to help you get started using the API. For detailed examples and descriptions, refer to the OpenMaple help page.
|
Text Callbacks
|
|
In each example, a text callback is defined. Output that would normally be displayed after an expression is evaluated in a Maple session is routed through callbacks. This output includes the following:
•
|
results from evaluated commands that are terminated with a semicolon
|
•
|
output from the print command, the printf command, and other display-related commands
|
•
|
userinfo and warning messages
|
•
|
diagnostic output from the debugger, high settings from printlevel, and trace output
|
•
|
error messages if no error callback is defined
|
•
|
resource status messages if no status callback is defined
|
The text callback is not the only way to control output in your OpenMaple application. For more control, individual results can be converted to strings, which can be displayed in text boxes or directed in any way you want. When using this method of controlling output, the text callback can be defined as a procedure that does not perform any operations (that is, does not direct the output anywhere). Note that the Java example below uses the predefined EngineCallBacksDefault class, which configures a method to print output using the System.out method. In general, if the text callback is left undefined by setting it to 0 or null, the output is directed to the console.
|
|
Initializing the Maple Engine
|
|
You can initialize the Maple engine by calling the StartMaple function in most versions of the API, or by creating an Engine class in the Java version of the API. In all cases, the initialization process loads the maplec.dll file and sets up the initial state so that the OpenMaple interface can evaluate commands. Despite the name StartMaple, this is only an initialization step; no separate Maple process is started.
The initialization process follows standard Maple start-up steps, including reading and running initialization files, setting library paths, and setting default security options. The startup state can be controlled by using the first parameter passed to the StartMaple function. This parameter is an array of strings that specify options accepted by the command-line interface. For more information about these options, refer to the maple help page.
|
|
Calling API Commands to Compute with Maple
|
|
When the OpenMaple interface is initialized, a kernel vector handle (or engine class), can be used to access all of the other methods in the API. The most common method lets you parse and evaluate arbitrary Maple commands. If a command is terminated with a semicolon, the display output is directed to your defined callbacks. This command also returns the result as a Maple data structure. The return value can be passed to other API commands for further analysis.
The OpenMaple interface manages Maple internal data structures and performs garbage collection. The data structures that are returned by an API function are automatically protected from garbage collection. The Maple command unprotect:-gc must be called to clean the memory reserved for these tasks. The OpenMaple Java interface is the only exception to this rule. Because the OpenMaple Java interface implements Maple structures as native objects, it manages object references by using a weak hash map, and therefore Maple data does not need to be unprotected.
Maple data structures are all declared as a single black box ALGEB, IntPtr, or similar type. Methods for inspecting and manipulating these data structures are provided. The API methods should be used, rather than dereferencing them directly in the data.
|
|
|
C/C++ Example
|
|
#include <stdio.h>
#include <stdlib.h>
#include "maplec.h"
/* callback used for directing result output */
static void M_DECL textCallBack( void *data, int tag, char *output )
{
printf("%s\n",output);
}
int main( int argc, char *argv[] )
{
char err[2048]; /* command input and error string buffers */
MKernelVector kv; /* Maple kernel handle */
MCallBackVectorDesc cb = { textCallBack,
0, /* errorCallBack not used */
0, /* statusCallBack not used */
0, /* readLineCallBack not used */
0, /* redirectCallBack not used */
0, /* streamCallBack not used */
0, /* queryInterrupt not used */
0 /* callBackCallBack not used */
};
ALGEB r; /* Maple data-structures */
/* initialize Maple */
if( (kv=StartMaple(argc,argv,&cb,NULL,NULL,err)) == NULL ) {
printf("Fatal error, %s\n",err);
return( 1 );
}
r = EvalMapleStatement(kv,"int(x,x);");
StopMaple(kv);
return( 0 );
}
|
|
|
Additional examples are available in the samples/OpenMaple directory of your Maple installation.
The method used to build this program depends on which compiler you are using. The following command is specific to the GCC compiler on a 64-bit version of Linux; it is useful as a reference for other platforms.
gcc -I $MAPLE/extern/include test.c -L $MAPLE/bin.X86_64_LINUX -lmaplec -lmaple -lhf -lprocessor64
|
|
|
In this example, $MAPLE is your Maple installation directory. Note that the C header files can be found in the $MAPLE/extern/include directory and the library files can be found in the $MAPLE/bin.$SYS directory. In this case,$SYS is X86_64_LINUX; check the library path you need to specify by running the kernelopts(bindir) command in Maple. The remaining -l options specify which libraries need to be linked. In Windows, you only need to link to the maplec.lib library. Other platforms may require several libraries to be linked, including libmaplec.so, libmaple.so, and libhf.so. If you do not specify a library as required, the compiler returns a message indicating that undefined references to functions exist, or a dependent library cannot be found.
When this example is built, a file called test.exe is created. Note: The file might be called a.out or another name, depending on your compiler. Before this executable file can be run, you must specify the path of the Maple dynamic libraries. For more information, see Runtime Environment Prerequisites.
After setting up your environment, run the binary file as you would run any other executable file. For example, create a shortcut icon and double-click it, or enter the file name at a command prompt.
The following output is displayed.
|
|
C# Example
|
|
using System;
using System.Text;
using System.Runtime.InteropServices;
class MainApp {
// When evaluating an expression, Maple sends all of the displayed
// output through this function.
public static void cbText( IntPtr data, int tag, String output )
{
Console.WriteLine(output);
}
public static void Main(string[] args) {
MapleEngine.MapleCallbacks cb;
byte[] err = new byte[2048];
IntPtr kv;
// pass -A2 which sets kernelopts(assertlevel=2) just to show
// how in this example. The corresponding argc parameter
// (the first argument to StartMaple) should then be 2
// argv[0] should always be filled in with a value.
String[] argv = new String[2];
argv[0] = "maple";
argv[1] = "-A2";
// assign callbacks
cb.textCallBack = cbText;
cb.errorCallBack = null;
cb.statusCallBack = null;
cb.readlineCallBack = null;
cb.redirectCallBack = null;
cb.streamCallBack = null;
cb.queryInterrupt = null;
cb.callbackCallBack = null;
try {
kv = MapleEngine.StartMaple(2,argv,ref cb,IntPtr.Zero,IntPtr.Zero,err);
}
catch(DllNotFoundException e) {
Console.WriteLine(e.ToString());
return;
}
catch(EntryPointNotFoundException e) {
Console.WriteLine(e.ToString());
return;
}
// make sure we have a good kernel vector handle back
if( kv.ToInt64() == 0 ) {
// If Maple does not start properly, the "err" parameter will be filled
// in with the reason why (usually a license error).
// Note that since we passed in a byte[] array, we need to remove
// the characters past \0 during conversion to string
Console.WriteLine("Fatal Error, could not start Maple: "
+ System.Text.Encoding.ASCII.GetString(err,0,Array.IndexOf(err,(byte)0))
);
return;
}
MapleEngine.EvalMapleStatement(kv,"int(x,x);");
MapleEngine.StopMaple(kv);
}
}
|
|
|
To build this example, you can open a Microsoft .NET Framework SDK Command Prompt. Browse to the directory that contains the test.cs file and enter the following command.
csc test.cs $MAPLE\\extern\\include\\maple.cs
|
|
|
$MAPLE is the directory in which Maple is installed. The maple.cs file contains the MapleEngine class definition. and defines an interface to the maplec.dll file.
When this example is built, a file called test.exe is created. This file can usually be run without additional environment settings. For more information, see Runtime Environment Prerequisites.
Run the binary file as you would run any other executable file. For example, create a shortcut icon and double-click it, or enter the file name at a command prompt.
The following output is displayed.
|
|
Java Example
|
|
import com.maplesoft.openmaple.*;
import com.maplesoft.externalcall.MapleException;
class test
{
public static void main( String args[] )
{
String a[];
Engine t;
int i;
a = new String[1];
a[0] = "java";
try
{
t = new Engine( a, new EngineCallBacksDefault(), null, null );
t.evaluate( "int( x,x );" );
}
catch ( MapleException e )
{
System.out.println( "An exception occurred" );
return;
}
System.out.println( "Done" );
}
}
|
|
|
This example and others are available in the samples/OpenMaple/Java/simple subdirectory of your Maple installation.
To build this program, enter the following at a command prompt, where $JDKBINDIR is the directory in which your Java development tools are installed, and $MAPLE is the directory in which Maple is installed.
$JDKBINDIR/javac -classpath "$MAPLE/java/externalcall.jar;$MAPLE/java/jopenmaple.jar" test.java
|
|
|
Note: The same command can be used to build the example in UNIX and Macintosh; however, use a colon (:) to separate the directories in the classpath instead of a semicolon.
When this example is built, a test.class file is created in the current directory. Before this file can be run, the path of the Java OpenMaple native library must be specified for your Java Virtual Machine. For more information, see Runtime Environment Prerequisites. In Windows, Java OpenMaple applications also require the %PATH% environment variable to be set.
You can use the Java Virtual Machine to run the generated class file by entering the following command. Note that the third entry in the classpath is a period (.) indicating the current directory.
$JDKBINDIR/java -classpath "$MAPLE/java/externalcall.jar;$MAPLE/java/jopenmaple.jar;." test
|
|
|
Note: The same command can be used to build the example in UNIX and Macintosh; however, use a colon (:) to separate the directories in the classpath instead of a semicolon.
The following output is displayed.
|
|
Visual Basic 6 Example
|
|
Public kv As Long
Public cb As MapleCallBack
Public Sub TextCallBack(data As Long, ByVal tag As Integer, ByVal output As Long)
Dim OutputString As String
OutputString = MaplePointerToString(output)
MainForm.OutputText.Text = MainForm.OutputText.Text + vbCrLf + OutputString
End Sub
Private Sub Form_Load()
Dim error As String
Dim args(0 To 1) As String
'init callbacks
cb.lpTextCallBack = GetProc(AddressOf TextCallBack)
cb.lpErrorCallBack = 0
cb.lpStatusCallBack = 0
cb.lpReadLineCallBack = 0
cb.lpRedirectCallBack = 0
cb.lpQueryInterrupt = 0
cb.lpCallBackCallBack = 0
' start Maple
kv = StartMaple(0, args, cb, 0, error)
If kv = 0 Then
MsgBox "Error starting Maple: " + StrConv(error, vbUnicode), vbCritical, ""
Unload Me
End
End If
dim result as Long = EvalMapleStatement(kv, "int(x,x);" )
End Sub
|
|
|
Other examples are available in the samples/OpenMaple/msvb directory of your Maple installation.
To build this example, create a new project, and add both the test.bas and $MAPLE/extern/include/maple.bas files to your project. $MAPLE is the directory in which Maple is installed. Create a form called "MainForm" and add a text box named "OutputText" to the form.
Build and run this example by pressing F5. When this example is built, a form that shows a text box filled with the value 1/2*x^2 is displayed.
|
|
Visual Basic .NET Example
|
|
Friend Class MainForm
Inherits System.Windows.Forms.Form
Public kv As IntPtr
Public cb As MapleCallBack
Public Sub MyTextCallBack(ByRef data As Integer, ByVal tag As Short, ByVal output As String)
tbOutput.Text = tbOutput.Text & vbCrLf & " (" & tag & ") " & output
End Sub
Public Sub MyErrorCallBack(ByRef data As Integer, ByVal Offset As Short, ByVal output As String)
MsgBox(" at offset " & Str(Offset) & " " & output, MsgBoxStyle.Information, "")
End Sub
Private Sub MainForm_Load(ByVal eventSender As System.Object, ByVal eventArgs As System.EventArgs) Handles MyBase.Load
Dim args(1) As String
'init callbacks
cb.lpTextCallBack = AddressOf MyTextCallBack
cb.lpErrorCallBack = AddressOf MyErrorCallBack
cb.lpStatusCallBack = 0
cb.lpReadLineCallBack = 0
cb.lpRedirectCallBack = 0
cb.lpQueryInterrupt = 0
cb.lpCallBackCallBack = 0
' start Maple
Try
args(1) = "-A2"
kv = StartMaple(1, args, cb, 0)
Catch e As StartMapleException
MsgBox("Error starting Maple: " & e.Message, "")
Me.Close()
End Try
Dim result As IntPtr
result = EvalMapleStatement(kv, "int( x,x );" )
If result = 0 Then
tbOutput.Text = "invalid expression"
Else
tbOutput.Text = MapleToString(kv, MapleALGEB_SPrintf1(kv, "%a", result))
End If
End Sub
End Class
|
|
|
To build this example, create a new project, and add both the test.bas and $MAPLE/extern/include/maple.vb files to your project. $MAPLE is the directory in which Maple is installed. Create a form called "MainForm" and add a text box named "tbOutput" to the form.
Build and run the example by pressing F5. When this example is built, a form that shows a text box filled with the value 1/2*x^2 is displayed.
|
|
Memory Usage
|
|
Maple allocates memory from the operating system in large portions. On most platforms, this process is performed by the C malloc function; 64KB of memory is allocated during a Maple session. This memory is not returned to the operating system (that is, by free) until the Maple session ends.
When Maple no longer requires a block of memory, that memory block is added to an internal list of free storage. Maple has several storage lists for different sizes of memory blocks so that it can quickly find blocks of the required size. For example, Maple uses three-word blocks, so it maintains a list of blocks of that size that are free. Maple allocates additional memory from the operating system only when it cannot respond to a request using its storage lists.
The more memory Maple is allocated by the operating system, the less it is allocated in the future because it reuses memory. For most applications, 4MB of memory must be available for allocation.
|
|
|
14.4 The Maple Command-line Interface
|
|
When considering how to use the Maple engine as part of another application, interface, or automatic process, several options are available. One of the simplest options is to use the Maple command-line interface.
The command-line version of Maple is a simple interface that, when used interactively, displays an input prompt (>), runs commands, and displays the output as text-based results. You can use this interface in batch mode to direct input to an application, specify a text file to run, or evaluate a command using the -c option.
In Windows, the command-line interface is called cmaple.exe. You can run this file from either the bin.win or bin.X86_64_WINDOW directory of your Maple installation, depending on your platform. On other platforms, you can start the command-line interface by running the maple script located in the bin directory of your Maple installation.
Starting the Maple command-line interface, automatically executing a command file, and stopping the Maple session can take about one tenth of a second, depending on which commands are run and the speed of your system. The quick start-up time and the minimal amount of processing required make the Maple command-line interface suitable to be called from other applications, even for quick calculations.
|
Batch Files
|
|
A batch file is a Maple script that can run in the Maple command-line interface, run statements, and exit. The results can be displayed or directed to a file.
One method of using the command-line interface to solve a problem is to create an .mpl script, which includes the input data. If this script is called solve.mpl, you could run the script as follows.
cmaple solve.mpl > solve.output
|
|
|
In this example, the output is redirected to a file named solve.output. You can configure an application to read this output file to capture the result.
Note: .mpl is the standard file extension for a Maple language file, which is a text file that can contain Maple statements. For more information, refer to the file help page.
You can use the -q option to hide extra output that interferes with parsing results automatically. For more options on the Maple command-line interface, refer to the maple help page.
|
|
Directing Input to a Pipeline
|
|
To avoid using the file system, input to the command-line interface can be directed to a pipeline. The following example shows how to perform this task at a command prompt.
echo "int(x,x);" | cmaple
|
|
|
|
|
Specifying Start-up Commands
|
|
You can use the -c option to specify start-up commands to be run by the Maple command-line interface. Although the -c option can be followed by any valid Maple statement, the syntax must be carefully quoted to avoid being interpreted by the shell of the calling system. For example, the following syntax can be entered at a Windows command prompt.
cmaple -c "datafile := `c:/temp/12345.data`" -c N:=5;
|
|
|
The equivalent command in a UNIX shell requires different quoting.
/usr/local/maple/bin/maple -c 'datafile:="/tmp/12345.data";' -c N:=1;
|
|
|
Statements that do not use characters that are special to the system interpreter can be left unquoted, as with the case of -c N:=1; above. This statement does not use spaces, pipe characters, redirect symbols, quotes, or other characters that have meaning to the interpreter.
|
|
|
14.5 External Calling: Using Compiled Code in Maple
|
|
In Maple, you can load a dynamic-link library (.dll) file that contains functions written in a programming language other than Maple, and then use those functions in Maple as you would use any other commands.
External functions that accept and return basic types (for example, integers and floats) can be called directly in Maple after defining the calling sequence of the external function. Alternatively, if you want to call functions that use complicated types, or if you require more control over the conversion of data structures you want to access, you can use the Maple external function interface to create and compile a wrapper file.
|
Calling a Function in a Dynamic-link Library
|
|
Most external functions that are compiled in a .dll file use standard hardware types such as integers, floating-point numbers, strings, pointers (to strings, integers, and floating-point numbers), matrices, and vectors. Maple can translate the hardware representation of these external functions so that the external functions are recognized in Maple. This method is efficient and easy to use because it does not require the use of a compiler. This method of directly calling the external code allows you to use an external library without modifying the Maple library.
To understand the Maple external calling facility, consider the following C code, which adds two numbers and returns the result.
int add( int num1, int num2 )
{
return num1+num2;
}
|
|
|
Three basic steps are required to call a function in a .dll library.
•
|
Create or obtain a .dll file
|
•
|
Create a function specification in Maple
|
•
|
Call the external function from within Maple
|
|
Create or Obtain a .dll file
|
|
The external functions that you want to call from Maple must be compiled in a .dll file. You can either create the code and compile the .dll file yourself or obtain an existing .dll file that contains the functions you want to use in Maple.
The external library functions in a .dll file must have been compiled using the _stdcall calling convention, which is the default convention used by most compilers in UNIX, Macintosh, and 64-bit Windows, but must be specified when using most 32-bit Windows compilers. Options are also available for calling .dll files created by Fortran compilers and classes created in Java.
|
|
Create a Function Specification
|
|
Before using an external function in Maple, you must provide a description (or function specification), which includes the following information.
•
|
Name of the function in the .dll file. In the example above, the name is add.
|
•
|
Type of parameters the function passes and returns. In the example above, all of the parameters are of type int.
|
•
|
Name of the .dll file that contains the function. In the example above, assume that the C code has been compiled into a .dll file called mylib.dll.
|
A function specification translates the external function into a form that can be recognized and interpreted by Maple.
At a Maple prompt, you can define the function specification by calling define_external as follows.
>
|
myAdd := define_external(
'add',
'num1'::integer[4],
'num2'::integer[4],
'RETURN'::integer[4],
'LIB'="mylib.dll"
);
|
Examine this function and note the following characteristics.
•
|
The first argument of the define_external function (in this example, add) is the name of the external function as exported by the .dll file. In the C code, the function is called add. However, because the define_external function is assigned to the name myAdd above, the Maple procedure that will be generated after you define the function specification will be called myAdd. This is the command that you will use to call the external function within Maple.
|
•
|
If Java or Fortran was used to create the external function, you must specify JAVA or FORTRAN as the second argument to indicate the programming language. The default language is C, so this parameter does not need to be specified in the example above since the add function was written in C.
|
•
|
The parameters num1 and num2, which are both of type int, describe the arguments of the function to be called. These values should be specified in the order in which they appear in your documentation or source code for the external function, regardless of issues such as the passing order (left to right versus right to left). By doing so, the Maple procedure returned by the define_external function has the same calling sequence as the external function when it is used in the language for which it was written. The only exception is that one argument can be assigned the name RETURN. This argument specifies the type returned by the function rather than a parameter passed to the function. In the example above, the return type does not have a name, so the keyword RETURN is used.
|
For information on specifying parameter types, see Specifying Parameter Types for Function Specifications.
•
|
Specifying the parameter types is independent of the compiler. The specification is always defined in the same way, regardless of the method used to compile the .dll file. The example above uses the C type int, which is specified as integer[4] in Maple. The 4 in the square brackets indicates the number of bytes used to represent the integer. Some C compilers use 4-byte (32-bit) long data types, but other compilers use 8-bytes (64-bit) for the same data structure. If you are using the long data type, the specification in Maple will need to be either integer[4] or integer[8], depending on the way your .dll file was built. For more information about common type relations, see Table 14.2.
|
•
|
The name of the .dll file containing the external function is specified by defining the LIB parameter. In the example above, mylib.dll specifies the file name of the library in which the function is located. The format of this name is system-dependent and certain systems require a full path to the file. In general, the name should be in the same format as you would specify for a compiler on the same system. If you are calling a Java method, dllName is the name of the class containing the method.
|
Important: Specify the function exactly and make sure that the arguments are in the correct order. Failure to do this will not cause any problems when you are defining the specification; however, unexpected results may be produced or your program may stop responding when the external function is called within Maple.
|
|
Calling the External Function
|
|
Calling the define_external function for myAdd returns a Maple procedure that translates the Maple types to hardware types that can work with an external function. This procedure can be used in the same way as other Maple commands.
| (1) |
| (2) |
| (3) |
|
|
|
Specifying Parameter Types for Function Specifications
|
|
Maple uses its own notation to provide a generic well-defined interface for calling compiled code in any language. The format of each type descriptor parameter is as follows.
argumentIdentifier :: dataDescriptor
|
|
|
The return value description is also defined by using a data descriptor, with the name RETURN as the argumentIdentifier. If the function returns no value, no RETURN parameter is specified. Also, if no parameters are passed, no argument identifiers are required.
|
|
Scalar Data Formats
|
|
External libraries generally handle scalar data formats that are supported by your platform. All array, string, and structured formats are created from these. The data descriptors used to represent scalar formats usually contain a type name and size. The size represents the number of bytes needed to represent the given hardware type. Table 14.1 lists the basic type translations for standard C, Fortran, and Java compilers.
Table 14.1: Basic Data Types |
Maple Data Descriptor
|
C Type
|
Fortran Type
|
Java Type
|
integer[1]
|
char
|
BYTE
|
byte
|
integer[2]
|
short
|
INTEGER2
|
short
|
integer[4]
|
int or long^1
|
INTEGER or INTEGER4
|
int
|
integer[8]
|
long^1 or long long
|
INTEGER8
|
long
|
float[4]
|
float
|
REAL or REAL4
|
float
|
float[8]
|
double
|
DOUBLE PRECISION or REAL8
|
double
|
char[1]
|
char
|
CHARACTER
|
char
|
boolean[1]
|
char
|
LOGICAL1
|
boolean
|
boolean[2]
|
short
|
LOGICAL2
|
|
boolean[4]
|
int or long^1
|
LOGICAL or LOGICAL4
|
|
boolean[8]
|
long^1 or long long
|
LOGICAL8
|
|
|
Note: The C type long is typically 4 bytes on 32-bit systems and 4 or 8 bytes on 64-bit systems. Use the sizeof operator or consult your compiler documentation to verify sizeof(long).
|
|
Structured Data Formats
|
|
In addition to the basic types listed in Table 14.1, Maple also recognizes certain compound types that can be derived from the basic types, such as arrays and pointers. These compound types are listed in Table 14.2. For a complete list and a detailed specification, refer to the define_external,types help page.
Table 14.2: Compound Data Types |
Maple Data Descriptor
|
C Type
|
Fortran Type
|
Java Type
|
ARRAY( datatype = float[8], ... )
|
type A
|
type A
|
type[] A
|
string[n]
|
char x[n]
|
CHARACTER2
|
string
|
complex[4]
|
struct{ float re, im; }
|
COMPLEX or COMPLEX8
|
NA
|
complex[8]
|
struct{ double re, im; }
|
DOUBLE COMPLEX or COMPLEX16
|
NA
|
REF(typename)
|
TYPENAME
|
NA
|
NA
|
|
|
|
External Function Interface
|
|
Alternatively, you may want to call call a .dll file that directly manipulates Maple data structures, rather than converting them automatically to standard data types. By doing so, you can either write custom applications that are integrated with Maple or provide custom conversions for data passed to prebuilt .dll files. Maple provides an API for directly managing Maple data structures and operations performed on them.
This API, or external function interface, is a subset of the API provided by the OpenMaple interface. Unlike the OpenMaple interface, you do not need to define stream callbacks because Maple is the primary interface. Also, the kernel-vector handle returned from a call to the StartMaple function in the OpenMaple API is, instead, passed as an argument to the external function defined in your .dll file.
Currently, the API is defined for C/C++ and Fortran, and certain portions of the API can be used for external functions written in Java. Other languages such as Visual C# and Visual Basic can interface through a small C++ layer.
The API function prototypes for manipulating Maple data structures are located in the $MAPLE/extern/include directory where $MAPLE is the directory in which Maple is installed. The header file maplec.h must be included when writing custom C wrappers. One of the header files, maplefortran.hf or maplefortran64bit.hf, must be included when writing custom Fortran wrappers. Other header files, mplshlib.h, and mpltable.h contain macros, types, and data structures that are needed for direct manipulation of Maple data structures.
In your C code, Maple uses the following lines as an entry point to call the external function directly with no argument conversion.
ALGEB myExternalFunction(
MKernelVector kv,
ALGEB args
);
|
|
|
Two parameters are in the external function declaration. The first is a handle that will be required to call any Maple API function. The second is a Maple expression sequence of all the arguments passed when the external function is called. The API function MapleNumArgs can be used to determine the number of elements in the expression sequence. This variable can be treated as an array of DAGs starting at index 1 (not 0). Therefore, args[1] is the first parameter passed to the external function.
>
|
myFunc := define_external('myExternalFunction', 'MAPLE', 'LIB'= "myStuff.dll"):
|
When using the define_external function to declare an interface to an external function that directly manipulates Maple structures, you do not need to provide a description of the arguments and their types. Instead, add the keyword option, MAPLE.
Again, consider the simple example that adds two numbers passed by Maple. This time, with explicit data type conversions using the API, and defining the external function prototype, as described above, the C function appears as follows.
/* Program to add two numbers from Maple */
#include <stdio.h>
#include <stdlib.h>
#include <maplec.h>
ALGEB myAdd( MKernelVector kv, ALGEB args )
{
int a1, a2, r;
if( MapleNumArgs(kv,args) != 2 )
MapleRaiseError(kv,"Incorrect number of arguments");
a1 = MapleToInteger32(kv,((ALGEB*)args)[1]);
a2 = MapleToInteger32(kv,((ALGEB*)args)[2]);
r = a1 + a2;
return( ToMapleInteger(kv,(M_INT) r) );
}
|
|
|
This program first checks if the Maple function call passes exactly two arguments. It then converts the two arguments to hardware integers and adds them. The result is converted to a Maple integer and returned.
This program can be compiled into a .dll file using a C compiler of your choice. Ensure that you link with the Maple API .dll file. The .dll file can be placed in the Maple binary directory, as given by kernelopts(bindir), or a subdirectory within the directory specified by the PATH environment variable. If you are using .dll files outside of the Maple binary directory, you may need to specify the full path to the .dll file in the LIB argument to the define_external function. UNIX developers may also need to set their load-library path.
To complete the example, the myAdd function can be linked in Maple and used as any other Maple procedure.
>
|
myAdd := define_external('myAdd', 'MAPLE', 'LIB'= "myAdd.dll"):
|
| (4) |
The equivalent Fortran wrapper is as follows.
Program to add two numbers from Maple
INTEGER FUNCTION myAdd(kv, args)
INCLUDE "maplefortran.hf"
INTEGER kv
INTEGER args
INTEGER arg
INTEGER a1, a2, r
CHARACTER ERRMSG*20
INTEGER ERRMSGLEN
ERRMSGLEN = 20
IF ( maple_num_args(kv, args) .NE. 2 ) THEN
ERRMSG = 'Incorrect number of arguments'
CALL maple_raise_error( kv, ERRMSG, ERRMSGLEN )
myAdd = to_maple_null( kv )
RETURN
ENDIF
arg = maple_extract_arg( kv, args, 1 )
a1 = maple_to_integer32(kv, arg)
arg = maple_extract_arg( kv, args, 2 )
a2 = maple_to_integer32(kv, arg)
r = a1 + a2
myAdd = to_maple_integer( kv, r )
END
|
|
|
Once compiled into a .dll file, the same syntax can be used in Maple to access the function. The only difference is the additional keyword 'FORTRAN' in the define_external call.
>
|
myAdd := define_external('myAdd','MAPLE','FORTRAN','LIB'=
"myAdd.dll"):
|
| (5) |
For more examples, refer to the define_external,CustomWrapper help page.
|
|
Specifying Parameter Passing Conventions
|
|
Each programming language uses a specific convention for parameter passing. For example, C uses the pass-by-value convention; passing parameters by reference must be performed explicitly by passing an address. Fortran uses the pass-by-reference convention. Pascal uses either, depending on how the parameter was declared.
The Maple external calling mechanism supports C, Fortran, and Java calling conventions. There is an external API for writing custom wrappers for C and Fortran, but not for Java. The default convention used is C. To use Fortran calling conventions, specify the name FORTRAN as a parameter to the define_external function.
>
|
f := define_external(`my_func`,`FORTRAN`, ...);
|
To use Java calling conventions, specify the name JAVA as a parameter to the define_external command. Also, specify the CLASSPATH= option to point to the classes used.
>
|
f := define_external(`my_func`,`JAVA`, CLASSPATH="...", ...);
|
Some other compiler implementations, such as Pascal and C++, can work with C external calling by using the correct definitions and order of passed parameters.
|
|
Generating Wrappers Automatically
|
|
When you specify the keyword WRAPPER in the call to the define_external function, Maple generates code for data translations. Maple compiles this code into a .dll file and dynamically links to the new library. Subsequently invoking the procedure that is returned by the define_external function calls the newly generated conversion command before calling the external function in the library you provided.
The C code generated by Maple wraps the Maple data structures by translating them to hardware equivalent types. Therefore, the code file is called the wrapper, and the library generated by this code is called the wrapper library.
Generating wrappers can provide an easy way to start writing custom code that references the Maple external function interface. The term wrapper also refers to the code you write to communicate with existing .dll files, as it does for the code Maple generates for the same reason. Your code is sometimes called a custom wrapper.
Consider the original add function that was introduced at the beginning of this chapter. In the following example, the WRAPPER option is used to generate a wrapper using the define_external function. As shown, you can also use the NO_COMPILE option to prevent the generated wrapper from compiling. The name of the generated file is returned instead.
>
|
myAdd := define_external(
'add',
'WRAPPER',
'NO_COMPILE',
'num1'::integer[4],
'num2'::integer[4],
'RETURN'::integer[4]
);
|
The file mwrap_add.c resembles the following.
/* MWRAP_add Wrapper
Generated automatically by Maple
Do not edit this file. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mplshlib.h>
#include <maplec.h>
MKernelVector mapleKernelVec;
typedef void *MaplePointer;
ALGEB *args;
/* main - MWRAP_add */
ALGEB MWRAP_add( MKernelVector kv,
INTEGER32 (*fn) ( INTEGER32 a1, INTEGER32 a2 ),
ALGEB fn_args )
{
INTEGER32 a1;
INTEGER32 a2;
INTEGER32 r;
ALGEB mr;
int i;
mapleKernelVec = kv;
args = (ALGEB*) fn_args;
if( MapleNumArgs(mapleKernelVec,(ALGEB)args) != 2 )
MapleRaiseError(mapleKernelVec,"Incorrect number
of arguments");
/* integer[4] */
a1 = MapleToInteger32(mapleKernelVec,args[1]);
/* integer[4] */
a2 = MapleToInteger32(mapleKernelVec,args[2]);
r = (*fn)(a1, a2);
mr = ToMapleInteger(mapleKernelVec,(long) r);
return( mr );
}
|
|
|
The generated wrapper is a good starting point for writing your own code. Some extra variables and declarations may be used because the wrapper generation is generic. For example, the use of args rather than fn_args avoids the need for a cast with args[1], but it is also a static global variable which is useful when working with callbacks that need access to the argument sequence outside the main entry point.
|
|
Passing Arguments by Reference
|
|
External functions follow normal Maple evaluation rules in that the arguments are evaluated during a function call. It therefore may be necessary to enclose assigned names in right single quotes (unevaluation quotes) when passing arguments by reference. For example, consider the following function that multiplies a number by two in-place.
void double\_it( int *i )
{
if( i == NULL ) return;
*i *= 2;
}
|
|
|
In Maple, the definition of this function could appear as follows.
>
|
double_it := define_external('double_it', i::REF(integer[4]),
LIB="libtest.dll");
|
When running this function, the argument 'i' is converted from the Maple internal representation of an integer to a 4-byte hardware integer. A pointer to the hardware integer is then passed to the external function, 'double_it'. Although 'i' is declared as a pointer to an integer, you can call 'double_it' with non-pointer input.
In this case, a pointer to the hardware integer 3 is sent to 'double_it'. The modified value is not accessible from Maple. To access the modified value, the parameter must be assigned to a name. The name must be enclosed in unevaluation quotes to prevent evaluation.
>
|
double_it(n); # n is evaluated first, so 3 is passed
|
| (6) |
>
|
double_it('n'); # use unevaluation quotes to pass 'n'
|
| (7) |
For numeric data, the string "NULL" can be passed as a parameter to represent the address 0 (the C NULL). For strings, because "NULL" is a valid string, the integer 0 represents the address 0.
>
|
double_it("NULL");
concat := define_external('concat',
RETURN::string, a::string, b::string,
LIB="libtest.dll"):
concat("NULL","x");
|
| (8) |
| (9) |
In the concat example, the C code might look like the following. Note that this function does not clean the memory as it should.
char * concat( char* a, char *b )
{
char *r;
if( !a \\ !b ) return( NULL );
r = (char*)malloc((strlen(a)+strlen(b)+1)*sizeof(char));
strcpy(r,a);
strcat(r,b);
return( r );
}
|
|
|
|
|
External API
|
|
An external API is provided if you want to expand existing wrappers or write custom wrappers. Because this API is the same as that of OpenMaple, most of the internal documentation is referenced in the OpenMaple help pages. In particular, refer to the OpenMaple,C,API and OpenMaple,VB,API help pages and related pages.
Additional examples can be found in the examples,ExternalCalling page. Sample code is provided in the samples/ExternalCall directory of your Maple installation. In particular, all of the external calling sample code provided in the individual help pages in OpenMaple,C,API can be found in the samples/ExternalCall/HelpExamples directory. This code is precompiled in the HelpExamples.dll file provided with Maple so that you can run the examples in your Maple session.
|
|
System Integrity
|
|
The Maple kernel cannot control the quality or reliability of external functions. If an external function performs an illegal operation, such as accessing memory outside of its address space, that operation may result in a segmentation fault or system error. The external function may stop responding and cause Maple to stop responding as well.
If an external function accesses memory outside of its address space but within the Maple address space, the external function will likely not stop responding, but certain parts of Maple may not function correctly, resulting in unexpected behavior or a crash later in the Maple session. Similarly, an external function that directly manipulates Maple data structures can produce unexpected results by misusing the data structure manipulation facilities.
Therefore, use external calling at your own risk. Whether an external function is one that you have written, or supplied by a third party to which you have declared an interface (that is, by using the define_external function), Maple must rely on the integrity of the external function when it is called.
|
|
|
14.6 Accessing Data over a Network with TCP/IP Sockets
|
|
The Sockets package allows Maple to communicate with data sources over the Internet, such as web sites and remote Maple sessions running on a network.
You can create a Maple server in a Maple session and configure the server to send a message to that session.
|
Socket Server
|
|
A socket server can be a public web service such as a stock quote service. The following example shows how to create a Maple procedure that acts as a service that listens for a socket connection and sends a message when a connection is found.
The server action is defined in the following procedure.
>
|
myServer := proc( sid )
uses Sockets;
Write( sid, sprintf( "Hello %s on port %d, from %s\r\n",
GetPeerHost( sid ), GetPeerPort( sid ), GetHostName() ) )
end proc:
|
The following commands cause the Maple session in which they are called to start the servicing requests. This call is not returned. To run the code, enter the procedure definition above and the Serve command below in a Maple worksheet. (Remove the comment character (#) before running the code.)
>
|
#Sockets:-Serve( 2525, myServer );
|
|
|
Socket Client
|
|
To write a simple client, the socket must first be opened. To do so, specify the name of the host and the port number of the host to which you want to connect.
>
|
sid := Sockets:- Open ( "localhost", 2525 );
|
To get information from the server, the socket must be read.
When you are finished, close the socket.
|
|
|
14.7 Code Generation
|
|
In Maple, code generation is one of several powerful tools for deploying results to other systems. Maple can translate formulas, numerical procedures, data sets, and matrices to compiled languages. Maple supports translation to C, Visual C#, MATLAB, Java, Visual Basic, and Fortran.
>
|
with( CodeGeneration );
|
| (10) |
|
Calling CodeGeneration Commands
|
|
You can call the CodeGeneration commands using the following syntax, where L is one of the supported languages, for example, C.
CodeGeneration[*L*]( *expression*, *options* )
|
|
|
The expression can take one of the following forms.
•
|
A single algebraic expression: Maple generates a statement in the target language assigning this expression to a variable.
|
•
|
A list of equations of the form *name*=*expression*: Maple interprets this list as a sequence of assignment statements and generates the equivalent sequence of assignment statements in the target language.
|
•
|
A list, array, or rtable: Maple generates a statement or sequence of statements assigning the elements to an array in the target language.
|
•
|
A Maple procedure or module: Maple generates an equivalent structure in the target language. For example, to translate a procedure to C, Maple generates a function along with any necessary directives for library inclusion. To translate a module to Java, Maple generates a Java class declaration with exports translated to public static methods and module locals translated to private static methods. For more information on translating code to a specific language, refer to the CodeGeneration help page and browse to the help page for the target programming language that you want to use.
|
You can use many options with the CodeGeneration commands. For more information, refer to the CodeGenerationOptions help page. Some of the commonly used options are listed below.
•
|
optimize=value: This option specifies whether the Maple code is optimized before it is translated. The default value is false. When this option is set to true, the codegen[optimize] function is used to optimize the Maple code before it is translated.
|
•
|
output=value: This option specifies the form of the output. By default, the formatted output is printed to the console. If a name (different from the name string) or a string is specified as the value, the result is appended to a file of that name. If the value is the name string, a string containing the result is returned. This string can then be assigned and manipulated.
|
•
|
declare=[declaration(s)]: This option specifies the types of variables. Each declaration has the form varname::vartype where varname is a variable name and vartype is one of the Maple type names recognized by the CodeGeneration package, as described in the TranslationDetails help page. Declarations specified using this option override any other type declarations in the input code.
|
|
|
Notes on Code Translation
|
|
Because the Maple programming language differs from the target languages supported by the CodeGeneration package, the generated output may not be completely equivalent to the input code. The CodeGeneration/Details help page provides more information on the translation process and hints on how to take full advantage of the facilities. In addition, some help pages contain notes that are relevant to specific languages. For more information, refer to the help pages for the corresponding target language, for example, CodeGeneration/General/CDetails.
|
|
Translation Process
|
|
The CodeGeneration commands recognize a subset of the Maple types, which are listed in the CodeGeneration/Details help page. The Maple types are translated to appropriate types in the target language. Compatibility of types is checked before operations are translated, and type coercions are performed if necessary. The CodeGeneration commands attempt to determine the type of any untyped variables. You can control type analysis and deduction by using the coercetypes, declare, deducetypes, and defaulttype options. For more information, refer to the CodeGenerationOptions help page.
The CodeGeneration commands can translate a subset of the Maple commands, which are listed on the CodeGeneration/Details help page. Some commands are translated only to certain target languages. For more information about a specific language, refer to its detailed help page, for example, CodeGeneration/General/CDetails.
The return type of a procedure is determined automatically if you do not declare it. If more than one return statement is specified, the types of all objects returned must be compatible in the target language. If a return statement contains a sequence of objects, the sequence is translated into an array. Implicit returns are recognized in some cases, but translations to explicit returns can be suppressed by using the deducereturn=false option. When necessary, an automatically generated return variable is used to store a return value.
Lists, Maple data structures of the type array, and rtables are translated to arrays in the target language. It is recommended that you declare the type and ranges for all arrays. In some target languages, arrays are reindexed to begin at index 0.
|
|
Example 1: Translating a Procedure to Java
|
|
>
|
f := proc(x)
local y;
y := ln(x)*exp(-x);
printf("The result is %f", y);
end proc:
|
>
|
CodeGeneration[Java](f);
|
import java.lang.Math;
class CodeGenerationClass {
public static void f (double x)
{
double y;
y = Math.log(x) * Math.exp(-x);
System.out.print("The result is " + y);
}
}
| |
|
|
Example 2: Translating a Procedure to C
|
|
In this example, the defaulttype option sets the default type to integer and the output option specifies that a string is returned. In this case, the output is assigned to the variable s.
>
|
g := proc(x, y, z)
return x*y-y*z+x*z;
end proc:
|
>
|
s := CodeGeneration[`C`](g, defaulttype=integer, output=string);
|
| (11) |
|
|
Example 3: Translating a Procedure to Fortran
|
|
In this example, because Fortran 77 is not case-sensitive, the variable X is renamed to avoid a conflict with the variable x.
>
|
h := proc(X::numeric, x::Array(numeric, 5..7))
return X+x[5]+x[6]+x[7];
end proc:
|
>
|
CodeGeneration[Fortran](h);
|
doubleprecision function h (X, cg)
doubleprecision X
doubleprecision cg(5:7)
h = X + cg(5) + cg(6) + cg(7)
return
end
| |
|
|
Example 4: Translating an Expression to MATLAB
|
|
In this example, the optimize option is used to minimize the number of arithmetic operations called in the exported code. The default exported code would have recomputed a value for (3 - c * b + a * b), which appears many times. To avoid recomputing the value, common subexpressions are evaluated once and stored in variables so that other expressions can refer to the value.
>
|
M := 1 / < a,3,c; 1,b,2; -1,0,-1 >;
|
| (12) |
>
|
CodeGeneration:-Matlab(M, optimize = true);
|
t1 = -729 + 22 * c;
t1 = 1 / t1;
t2 = 3 * t1;
t3 = 22 * t1;
cg = [-t3 t2 (6 - 22 * c) * t1; -t1 (-33 + c) * t1 (-66 + c) * t1; t3 -t2 723 * t1;];
| |
|
|
Example 5: Translating a Procedure to Visual Basic
|
|
In the following example, all of the parameters are assigned a floating-point type by default. The defaulttype option determines how to interpret variables that do not have a type.
>
|
f := proc(x, y, z) return x*y-y*z+x*z; end proc:
|
>
|
CodeGeneration:-VisualBasic(f,defaulttype=integer);
|
Public Module CodeGenerationModule
Public Function f( _
ByVal x As Integer, _
ByVal y As Integer, _
ByVal z As Integer) As Integer
Return x * y - y * z + x * z
End Function
End Module
| |
|
|
Example 6: Using the defaulttype and deducetypes Options
|
|
Maple attempts to determine the types of variables that do not have a type automatically. The default type is assigned only to those variables that do not have a type after the automatic type deduction process. In the following example, the parameters y and z are assigned a floating-point type because they are in an expression involving the float variable x. Therefore, the default type, integer, is not assigned.
>
|
f := proc(x::float, y, z) x*y-y*z+x*z; end proc:
|
>
|
CodeGeneration:-C( f, defaulttype=integer );
|
double f (double x, double y, double z)
{
return(x * y - y * z + x * z);
}
| |
You can turn off the automatic type deduction process by setting the deducetypes option to false. In the following example, the parameters y and z are now assigned the default type.
>
|
CodeGeneration:-C(f, defaulttype=integer, deducetypes=false);
|
double f (double x, int y, int z)
{
return(x * (double) y - (double) (y * z) + x * (double) z);
}
| |
You can turn off the explicit type coercion process by setting the coercetypes option to false.
>
|
CodeGeneration:-C(f, defaulttype=integer, deducetypes=false, coercetypes=false);
|
double f (double x, int y, int z)
{
return(x * y - y * z + x * z);
}
| |
|
|
Example 7: Using the declare Option
|
|
You can control how types are assigned by specifying the parameter, local variable, and return types explicitly in procedures or by using the declare option with expressions.
>
|
CodeGeneration:-C(1+x+y, declare=[x::float, y::integer]);
|
cg0 = 0.1e1 + x + (double) y;
| |
|
|
The Intermediate Code
|
|
All Maple input to the CodeGeneration translators is processed and converted to an inert intermediate form called intermediate code. The intermediate code is the basic object on which all CodeGeneration translators operate. For information about the intermediate code, refer to the CodeGeneration/General/IntermediateCodeStructure help page.
The names that appear in intermediate code expressions are part of the CodeGeneration:-Names subpackage.
Error and warning messages displayed from CodeGeneration package commands sometimes refer to the intermediate form of the Maple expression that triggered the message.
When determining the cause of an error message or writing and debugging custom language definitions, it is recommended that you determine the intermediate form of a Maple expression input. In general, you can determine the intermediate form by using the CodeGeneration:-IntermediateCode translator. However, because some aspects of the intermediate code are specific to the language to which you are translating, it may help to see the intermediate code for a specific translator. This can be done by setting the command infolevel[CodeGeneration] to a value greater than 3 and performing a translation.
The following example shows the intermediate code for the expression 2x^2-1. The first argument of the Scope structure is the name of a type table used internally during the translation process.
>
|
CodeGeneration[IntermediateCode](2*x^2-1);
|
Scope( nametab,
StatementSequence(
Assignment(GeneratedName("cg1"), Sum(Product(Integer(2), Power(Name("x"), Integer(2))), Negation(Integer(1))))
)
)
| |
|
|
Extending the CodeGeneration Translation Facilities
|
|
The CodeGeneration package is distributed with translators for several programming languages. In addition, you can define new translators to enable the CodeGeneration package to generate code for other languages. Tools for this task are available in the LanguageDefinition subpackage of CodeGeneration.
Custom translators can define a complete language, extend existing language definitions, overriding and extending only those language components that need to be changed.
To view a list of languages that are currently supported by the CodeGeneration package, and thus available for extending, use the CodeGeneration:-LanguageDefinition:-ListLanguages command.
|
|
The Printing Phase
|
|
As described previously, the CodeGeneration package first processes the Maple input and translates it to an intermediate form. This step is followed by the printing phase, which translates the intermediate form to a Maple string according to transformation rules specific to the target language.
For each name used in the intermediate form, there is a print handler procedure. During the printing phase, Maple traverses the intermediate form recursively. For each subexpression of the intermediate form, Maple invokes the print handler associated with that class of expressions.
|
|
Defining a Custom Translator
|
|
This section explains the process of defining a translator for a target language.
|
|
Using a Printer Module
|
|
For each CodeGeneration language definition, there is an associated Maple module called a Printer module, which contains language-specific print handlers and data. A Printer module has several functions, which set and reference language-specific printing data.
There are two ways to obtain a Printer module: the LanguageDefinition:-GenericPrinter() returns a generic Printer module containing no language-specific data, and the LanguageDefinition:-Get(language_name):-Printer command returns a copy of the Printer module used for a previously defined language language_name.
The most frequently used Printer package command is Print. When it is given a string, the Print command prints the string to a buffer. When given an intermediate-form expression, the Print command invokes the print handler appropriate for the expression. In this manner, Print recurses through the intermediate form until it is printed in its entirety to the buffer. At this point, the translation process is complete.
Table 14.3 lists the important Printer commands. For a complete list and more detailed information, refer to the CodeGeneration/LanguageDefinition/Printer help page.
Table 14.3: Printer Commands |
AddFunction
|
Define a translation for a command name and type signature
|
AddOperator
|
Define a translation for a unary or binary operator
|
AddPrintHandler
|
Set a procedure to be the print handler for an intermediate form name
|
GetFunction
|
Get a translation for a command name and type signature
|
GetOperator
|
Get a translation for a unary or binary operator
|
GetPrintHandler
|
Get the current print handler' procedure for an intermediate form name
|
Indent
|
Indent a printed line when supplied as an argument to Print
|
Print
|
Print arguments to buffer
|
PrintTarget
|
Initiate printing of an intermediate form
|
|
The following commands illustrate how data is stored and retrieved from a Printer module.
>
|
with(CodeGeneration:-LanguageDefinition):
|
>
|
Printer := GenericPrinter():
|
>
|
Printer:-AddOperator( Addition = "+" );
|
| (13) |
>
|
Printer:-AddFunction( "sin", [numeric]::numeric, "sine" );
|
| (14) |
>
|
Printer:-GetOperator( Addition );
|
| (15) |
>
|
Printer:-GetFunction( "sin", [numeric]::numeric );
|
| (16) |
Within a language definition, the Printer module associated with the language definition can be referenced by the name Printer. Note: This applies to both of the language definition methods described in the next section.
|
|
Language Translator Definition
|
|
There are two distinct methods of defining a language translator for use by the CodeGeneration package: using the LanguageDefinition:-Define command and creating a language definition module.
For simple languages or small extensions of existing languages, use the LanguageDefinition:-Define command. To create a translator that preprocesses or postprocesses the generated output, or makes frequent use of a utility function in translations, create a language definition module. The language definition module approach is used for all translators supplied with the CodeGeneration package.
|
|
Using the Define Command
|
|
The Define command takes a series of function call arguments f1, f2, ... where the command names are, for example, AddFunction, AddFunctions, AddOperator, AddPrintHandler, AddType, and SetLanguageAttribute.
These function calls accept identical syntax and perform the same actions as the Printer commands of the same name. That is, they define print handlers and other data specific to the language translation you are defining. For more information on these commands, refer to the CodeGeneration/LanguageDefinition/Printer help page.
The Define command automatically creates a Printer module for the language. You do not need to create one using the LanguageDefinition:-GenericPrinter or LanguageDefinition:-Get commands.
This example illustrates a C translator, in which the translated code uses a specialized library function mymult for multiplication instead of the built-in * operator.
>
|
CodeGeneration:-LanguageDefinition:-Define("MyNewLanguage",
extend="C",
AddPrintHandler(
CodeGeneration:-Names:-Product = proc(x,y)
Printer:-Print("mymult(", args[1], ", ", args[2],
")");
end proc
)
):
|
Note that in the previous example, one of the arguments of the LanguageDefinition:-Define command is the function call AddPrintHandler, which takes a name and a procedure as arguments. The supplied procedure therefore prints any Product subexpression of the intermediate form. The call to Printer:-Print specifies that the translator uses the automatically generated Printer module.
|
|
Creating a Language Definition Module
|
|
A language definition module is a Maple module with the exports PrintTarget and Printer. The module exports must satisfy the following criteria.
•
|
Printer: A Printer module, that is, either a generic Printer module returned by the CodeGeneration:-LanguageDefinition:-GenericPrinter command or a Printer module obtained from another language definition module using the LanguageDefinition:-Get("language_name"):-Printer command.
|
•
|
PrintTarget: Returns the translated output as a string. In most cases, the PrintTarget command calls the Printer:-PrintTarget command.
|
The body of the module definition must contain a sequence of calls to Printer commands that define language-specific data and utility procedures.
Once defined, a language definition module can be added to to the set of languages recognized by the CodeGeneration package by using the CodeGeneration:-LanguageDefinition:-Add command.
When creating your language definition module, you must delay the evaluation of the module by using unevaluation quotes before adding it using the LanguageDefinition:-Add command. That is, the language definition module must be added as a module definition and not as a module.
The following example adds a definition module. Note the use of unevaluation quotes around the module definition to delay its evaluation.
>
|
UppercaseFortran77 := 'module()
export Printer, PrintTarget;
Printer := eval(CodeGeneration:-LanguageDefinition:-Get(
"Fortran")):-Printer;
PrintTarget := proc(ic, digits, prec, func_prec, namelist)
Printer:-SetLanguageAttribute("Precision" = prec);
StringTools:-UpperCase(Printer:-PrintTarget(args));
end proc:
end module':
|
>
|
CodeGeneration:-LanguageDefinition:-Add("UppercaseFortran",
UppercaseFortran77);
|
|
|
Using a New Translator
|
|
After creating the language definition using either the LanguageDefinition:-Define or LanguageDefinition:-Add commands, translate your code to the new language using the CodeGeneration:-Translate command.
The following example demonstrates the use of a new translator. Compare the output of the Fortran command with that of the new translator.
>
|
p1 := proc() sin(x+y*z)+trunc(x); end proc:
|
>
|
CodeGeneration:-Fortran(p1);
|
doubleprecision function p1 ()
p1 = sin(x + y * z) + dble(int(aint(x)))
return
end
| |
| | | |