Creating Mathcad-Readable Function Libraries |
To get you started writing DLLs for Mathcad, a number of code samples are included. The first example, discussed here, is the file MULTIPLY.C located in the UserEFI\Microsft\sources\simple subdirectory in your Mathcad install directory. MULTIPLY.C contains a two-argument function that multiplies an array by a scalar. When compiled and correctly linked, the new function multiply(a, M) will be available in Mathcad.
In the following sections the code is broken down into parts accompanied by an explanation. To see the code in its entirety, open the sample file in Visual Studio or Notepad.
All UserEFIs must include the mcadincl.h header file, located in the UserEFI\Microsft\include subdirectory in your Mathcad install directory. This file contains the data structures that will allow you to define functions that Mathcad can read, allocate and free scalars and arrays in a Mathcad-compatible way, and create error messages that can be returned to Mathcad using the graphical user interface.
#include "mcadincl.h"
Typically, the next part of the program defines error codes for the types of errors you might expect to encounter. Be sure to trap errors for inappropriate data types, as these are among the most common misuses of Mathcad functions. These are defined together so it is easy to count the total number of errors. This total will be required by the CreateUserErrorMessageTable function.
Mathcad traps the following floating point exceptions: overflow, divide by zero, and invalid operation. In the case of these exceptions, Mathcad will display a floating point error message under the function; you do not have to handle these errors specifically. Mathcad will also free all the memory allocated when one of these errors occurs.
#define INTERRUPTED 1
#define INSUFFICIENT_MEMORY 2
#define MUST_BE_REAL 3
#define NUMBER_OF_ERRORS 3
// tool tip error messages
// if your function never returns an error, you do not need to create this table
char * myErrorMessageTable[NUMBER_OF_ERRORS] =
{
"interrupted",.
"insufficient memory",
"must be real"
};
Next comes the code that executes your algorithm. If you are converting code from an existing library, you will have to recast it with the COMPLEXARRAY, COMPLEXSCALAR, and MCSTRING types passed by and expected by Mathcad. Note that all values passed into and out of Mathcad are complex, having both a real and an imaginary part to their structure (see mcadincl.h). You will have to split off the real and imaginary parts if you need to do independent manipulations on them. Also note that arrays are indexed by column, then row, as opposed to the order of indices inside Mathcad, which is by row, then column. Finally, all arrays are assumed to have two dimensions. If you wish to reference a vector, you will still need the first array index (column), always set to 0.
The first argument to the algorithm is a pointer to the return value, in this case, Product. The remaining arguments are pointers to the input values coming from Mathcad.
// this code executes the multiplication
LRESULT MultiplyRealArrayByRealScalar(
COMPLEXARRAY * const Product,
const COMPLEXSCALAR * const Scalar,
const COMPLEXARRAY * const Array )
{
unsigned int row, col;
// check that the scalar argument is real
if ( Scalar->imag != 0.0)
// if not, display "must be real" under scalar argument
return MAKELRESULT( MUST_BE_REAL, 1);
// check that the array argument is real
if ( Array->hImag != NULL )
// if not, display "must be real" under array argument
return MAKELRESULT( MUST_BE_REAL, 2);
// allocate memory for the product
if( !MathcadArrayAllocate( Product, Array-rows,Array-cols,
// allocate memory for the real part
TRUE,
// do not allocate memory for the imaginary part
FALSE ))	
// if allocation is not successful, return with the appropriate error code
return INSUFFICIENT_MEMORY;
// if all is well so far, perform the multiplication
for ( col = 0; col < Product-> cols; col++ )
{
// check that a user has not tried to interrupt the calculation
if (isUserInterrupted())
{
// if user has interrupted, free the allocated memory
MathcadArrayFree( Product );
// and return with an appropriate error code
return INTERRUPTED;
}
for ( row = 0; row < Product-> rows; row++ )
Product->hReal[col][row] =
Scalar-> real*Array-> hReal[col][row];
}
// normal return
return 0;
}
Fill out a FunctionInfo structure with the information needed for registering the function with Mathcad. This structure will define the name of the function within Mathcad, not the name of the algorithm above, which is MultiplyRealArrayByRealScalar. It will also define the parameters, and a description, as well as a pointer to the algorithm used. In versions previous to Mathcad 8, this description used to populate the Insert Function dialog. The code has since changed, so you will have to update the User_EN.xml document separately to make your function definition show up in this dialog box.
FUNCTIONINFO multiply =
{
// name by which Mathcad will recognize the function
"multiply",
// description of "multiply" parameters
"a,M",
// description of the function
"returns the product of real scalar a and real array M",
// pointer to the executable code
(LPCFUNCTION)MultiplyRealArrayByRealScalar;
// multiply(a, M) returns a complex array
COMPLEX_ARRAY,
// multiply takes on two arguments
2,
// the first is a complex scalar, the second a complex array
{ COMPLEX_SCALAR, COMPLEX_ARRAY}
};
DLL stands for Dynamically Linked Library. The following code will make this library available to other Windows functions, in particular, Mathcad, through Windows' DLL interface. Note that you will also have to specify a reference to the entry point through your programming environment. The DLL entry point is called by the operating system when the DLL is loaded. Mathcad requires that you register your user functions and your error message table while the DLL is being loaded.
// all Mathcad DLLs must have a DLL entry point code
// the _CRT_INIT function is needed if you are using Microsoft's 32-bit compiler
BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved);
BOOL WINAPI DllEntryPoint (HINSTANCE hDLL, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
if (!_CRT_INIT(hDLL, dwReason, lpReserved))
return FALSE;
Finally, register the error message table, and, if that succeeds, register the user function. If your function never returns an error, an error message table is not required. You can register only one error message table per DLL, but you can register more than one function per DLL. You'll also need to clean up any remaining processes and definitions.
if ( CreateUserErrorMessageTable( hDLL, NUMBER_OF_ERRORS, myErrorMessageTable ) )
// and if the errors register properly, register the user function
CreateUserFunction( hDLL, &multiply );
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
if (!_CRT_INIT(hDLL, dwReason, lpReserved))
return FALSE;
break;
}
return TRUE;
}
#undef INTERRUPTED
#undef INSUFFICIENT_MEMORY
#undef MUST_BE_REAL
#undef NUMBER_OF_ERRORS