Workflow for calling user-written subroutines in C
-
Include the header files in your source file.
#include <string.h> #include <stdio.h>
-
Define the "MAYA_EXPORT" macro as __declspec(dllexport) to export data and functions from a DLL.
#define MAYA_EXPORT __declspec(dllexport)
-
Include the USER1 function inside the wrapper
extern "C" {...}
.The wrapper communicates to the C++ compiler that the USER1 subroutine written in C language. You apply the wrapper by defining the __cplusplus macro, using the C++ compiler.#ifdef __cplusplus extern "C" { #endif
The C++ compiler does not alter the function names as a C compiler does.
-
Write the USER1 function.
The USER1 function retrieves many raw pointers to arrays and scalar data. The arrays and scalar data are consistent with data that you pass to the Fortran-based user-written subroutine.
MAYA_EXPORT void USER1(float* GG, double* T, float* C, float* Q, float* QD, float* R, double* TIME, double* DT, int* IT, int* KODE, int* NOCON, int* MAXNO, int* ICONV, float* DUM1, float* DUM2, float* DTP, float* TF) {
-
Define the user-written subroutine.
The following is an example of the THERMOSTAT subroutine that removes 25W from each heater element when the thermostat is off.
{ // "command" is either "SET" or "GET" (input to "THERMOSTAT"): char command[] = "GET"; size_t commandLength = strlen(command); // "elemNum" is an external element number (input to "THERMOSTAT"): int elemNum = 10; // "state" is the thermostat state either 0 = OFF or 1 = ON (input to "THERMOSTAT", if "command" = "SET" // output from "THERMOSTAT", if "command" = "GET"): int state = 0; // If *KODE is 1, we call "THERMOSTAT". if (*KODE == 1) { // You do not call the Fortran subroutine "THERMOSTAT" directly, instead you call the C wrapper, which is built around this native Fortran subroutine. // // 1) The name of any C wrapper used in C supported "USER1" or "USERF" functions is Native Fortran subroutine name with 'c' in the end. // For example, instead of "THERMOSTAT", you use the name "thermostatc". // // 2) You pass the arguments to a C wrapper by address. // // 3) If there are one or a few char* arguments which are passed to a C wrapper, // you also have to pass the C wrapper the lengths of each char* argument at the end of the argument list. // // For example, the name of a C wrapper = "namec". // // You define and initialize the arguments, which you passed to the C wrapper as follows: // // char arg1[] = "Hello, "; // 7 chars + null terminating symbol '\0' added automatically // int arg2 = 17; // float arg3[] = {1.0, 2.0, 3.0, 4.0, 5.0}; // char arg4[] = "World!"; // 6 chars + null terminating symbol '\0' added automatically // float arg5 = 3.14; // // Now, you call the C wrapper as: // // namec(&arg1, &arg2, &arg3, &arg4, &arg5, 7, 6); // // Note that instead of &cArray, you can simply pass cArray: // // namec(arg1, &arg2, arg3, arg4, &arg5, 7, 6); // // The name of a cArray usually evaluates to the address of the first element of the cArray, so cArray and &cArray have the same value. thermostatc(&command, &elemNum, &state, commandLength); // The integer "state" is returned from "thermostatc". if (state == 0) // If the thermostat is OFF, we remove 25W from each heater element. { int i = 0; for (i = 0; i < 10; i++) // Remember, the first index of a cArray = 0, // that's why we start the loop from 0. { Q[i] = Q[i] - 2.50E+07; // -25W } } }
The following example shows how to declare a 2D-array in a C program and pass it to Fortran consistently. Both C and Fortran programming languages store the arrays in memory differently: raw-major order in Fortran and column-major order in C language.
// Declare the C functions which you use in this program. // All these functions delegate execution to the appropriate analyz functions to extend the user-code interaction. void tcnamec (char*, char*, int*, int*, int, int); int nocondc (int* , int* , int*); void namarc (char*, int* , int*, int); float hydpropc(int* , int*); int kconvc (int*); void fanc (int* , float*); // USER1: MAYA_EXPORT void USER1(float* GG, double* T, float* C, float* Q, float* QD, float* R, double* TIME, double* DT, int* IT, int* KODE, int* NOCON, int* MAXNO, int* ICONV, float* DUM1, float* DUM2, float* DTP, float* TF) { // All variables persistent between calls are declared as static. // Flag of the first call to this function: static int ISTART = 0; // Conductance number that is returned by the nocondc function: static int J; // Arrays of chars: char STR1[] = "N3008"; size_t LEN1 = strlen(STR1); char STR2[] = "N3009"; size_t LEN2 = strlen(STR2); char STR3[] = "FANPUMP"; size_t LEN3 = strlen(STR3); // Integers: int int3006 = 3006; int int3007 = 3007; int int3008 = 3008; int int5 = 5; int int8 = 8; int NC; int IQ; static int IC = 0; int N; int temp; // Floats: float RMASS; // Arrays: static int IF[2]; // Note: // // Fortran uses the column-major order and C programm uses the row-major order to store a multi-dimensional array. // Fortran arrays are simple - an array of any dimension is just a list of locations in memory stored in the column-major order, like a(1,1), a(2,1), a(3,1), ... // When a Fortran function has an array argument, it receives is a pointer to the most-left element. // When you call Fortran from C, you have to be aware of the switch on order and then pass &a[0][0]. // // For example, ICONDNO is defined as the [4][10] array in the Fortran USER1 subroutine. // Therefore, you define the same array as the [10][4] array. int ICONDNO[10][4]; // ---+++---+++---+++---+++---+++---+++--- if (*KODE == 4) { if (ISTART == 0) { tcnamec(&STR1, &STR2, &ICONDNO[0][0], &NC, LEN1, LEN2); // When you compare how the elements of a 2D array are accessed in Fortran and C, // you notice that apart from changing the order of indices, you also subtract 1 from each index, // because the arrays in C are numbered started from 0: // // Fortran: a(5,7) // C: a[7-1][5-1] for (IQ = 1; IQ <= NC; IQ++) { if (ICONDNO[IQ - 1][4 - 1] == 5) { IC = ICONDNO[IQ - 1][1 - 1]; } } J = nocondc(&int3007, &int3008, &int5); namarc(&STR3, &IF[0], &N, LEN3); ISTART = 1; } // End of first call temp = IF[1-1]; RMASS = hydpropc(&int8, &temp) * 0.002; temp = kconvc(&int3006); fanc(&temp, &RMASS); GG[J - 1] = 2 * GG[J - 1]; GG[IC - 1] = 3 * GG[IC - 1]; } // End of KODE 4 } // ORIGINAL F77 USER1-SUBROUTINE: // // SUBROUTINE USER1 (RR, T, C, Q, QDEL, R, T2ME, DELTAT, ITTOT1, KODE, MAXNOR, MAXNOQ, ICONV, RI, TR, CO1, CO44) // C COMMON /HYDPROP/H(25,1) // DIMENSION ICONV(*), Q(*), RR(*) ! - inputs // DIMENSION IF(2), ICONDNO(4,10) ! - locals // SAVE // DATA ISTART/0/ // // IF(KODE.EQ.4)THEN // IF(ISTART.EQ.0)THEN // CALL TCNAME('N3008','N3009',ICONDNO,NC) // IC=0 // DO 10 IQ=1,NC // IF(ICONDNO(4,IQ).EQ.5)THEN // IC=ICONDNO(1,IQ) // END IF // 10 CONTINUE // J=NOCOND(3007,3008,5) // CALL NAMAR('FANPUMP',IF,N) // ISTART=1 // END IF // RMASS=HYDPROP(8,IF(1))*.002 // CALL FAN(KCONV(3006),RMASS) // RR(J)=2*RR(J) // RR(IC)=3*RR(IC) // END IF // RETURN // END #ifdef __cplusplus } #endif