ZEMAX Users' Knowledge Base - http://www.zemax.com/kb
How To Write ZEMAX Extensions in FORTRAN
http://www.zemax.com/kb/articles/111/1/How-To-Write-ZEMAX-Extensions-in-FORTRAN/Page1.html
By Anthony Richards
Published on 22 June 2006
 
This article describes how one user took advantage of the published C-language code for ZCLIENT.C, the program that provides Dynamic Data Exchange (DDE) functionality with ZEMAX, to modify it so that the main utility functions provided in that code could be called from a user-provided FORTRAN function instead of having to use the C- or C++ language.

Introduction
This article describes how one user took advantage of the published C-language code for ZCLIENT.C, the program that provides Dynamic Data Exchange (DDE) functionality with ZEMAX, to modify it so that the main utility functions provided in that code could be called from a user-provided FORTRAN function instead of having to use the C- or C++ language. It describes how a 32-bit Windows® executable can be built that combines a C-compiled ZCLIENT.OBJ object module with a FORTRAN user-function.

A useful starting place for the prospective user, as well as of course the relevant chapter in the ZEMAX manual, is the article on how to write a ZEMAX extension in C, where full details are given of the server-client relationship established by ZCLIENT and how the DDE interface works.

Modifying and Compiling the C-Code 1
Controlling the Symbol table

The main reason for modifying the ZCLIENT C-code is to control the calling convention used and the declaration of the names, or symbols, of each callable function or routine. When compiling in C, the compiler produces an .OBJ file that contains, among other things, a ‘symbol table’, a table of those functions that have been defined or referenced in the code, such as subroutines (C ‘void’ functions) and functions. Before we can successfully link this C  .OBJ file with an .OBJ file compiled from FORTRAN code that calls some of the C-functions, the FORTRAN compiler must generate identical symbols for the C-functions so that the linker will find them in the  ZCLIENT.OBJ file. The linker will search default C libraries for any symbols that it does not recognize within supplied .OBJ files.

The symbols may be either straightforward or quite complicated. For example, a function called ‘PostRequestMessage’ defined in the C-code might, if compiled as C-code (which the Microsoft C++ compiler would automatically assume it to be if it exists in a file having extension ‘.C’) appear in the symbol table with the name ‘_PostRequestMessage’, that is, it would just have a leading underscore added. However, if the same code is included in a file with ‘.CPP’ extension, then the C++ compiler would produce a symbol of the form ‘?PostRequestMessage@@YAHPAD0@Z’. The apparently superfluous additional leading and trailing characters result from a process called ‘name mangling’ which is a scheme particular to the Microsoft C++ compiler. There is a structure to this name mangling (try Googling ‘name mangling C++’ for information on this if you wish), but the main thing to be aware of is that it might change in future (Microsoft being what it is!). There is a Microsoft utility DUMPBIN (shipped with most FORTRAN and C compilers or available from the MS Windows® SDK) that can be used to extract the list of symbols in any .OBJ file (be it generated by a C-compiler or a FORTRAN one) so in theory one could extract the symbol in ZCLIENT.OBJ that we need and then give our FORTRAN equivalents the same names (with due regard being paid to case). But, because the compiled symbols may change in future (because of possible name mangling changes etc.), it is much better to control the symbol by using the C language term ‘extern’.

Here is the original ZCLIENT code for the main functions used to pass data across the DDE interface, followed by the modified code using ‘extern’:



Note that the UserFunction name has been made uppercase in the modified code. This means that all references to it in the C-code (there is only one other reference to it, inside the main window procedure WndProc) must also be made uppercase, since C preserves case. For the benefit of FORTRAN programmers, the presence of an asterisk ‘*’ in front of an argument, such as ‘char *szBuffer’ defines a pointer to the argument, the address of the memory location containing the argument’s value. This is equivalent to the FORTRAN ‘pass by reference’. Arguments without the leading ‘*’ are passed by value. The calling convention ‘__cdecl’ (the default in C++) ensures that the calling function cleans up the stack, where arguments are temporarily placed during function calls, and that the generated symbols only have a leading underscore added to the function name, with case preserved (If the stack is not properly ‘cleaned up’ the program would likely crash uncontrollably).

The ‘mangled’ naming referred to earlier is just a coded form of the function declarators given in the ‘original’ box above, so in theory, with access to the ‘name mangling’ convention, one could reconstruct (‘demangle’) the function declarator from its mangled name obtained using DUMPBIN from the .OBJ file containing the function, thus giving one knowledge of the type of function, the type and number of arguments and how the C program is passing the arguments (by value or by reference).

When the above modified code is compiled, we finish up with a ZCLIENT.OBJ file that contains, among others, the following straightforward symbols:



These are the names (preserving case) that the FORTRAN compiler must generate when wishing to call the first three C-functions and when expecting a call for the fourth function from the C function WndProc.


Modifying and Compiling the C-Code 2

Defining the DDERAYDATA  structure

The ZCLIENT C-code defines a data structure named DDERAYDATA that is passed by reference by the PostArrayTraceMessage function for faster, more efficient ray tracing. The C-code for this is shown below:

typedef struct{
double x, y, z, l, m, n, opd, intensity;
double Exr, Exi, Eyr, Eyi, Ezr, Ezi;
int wave, error, vigcode, want_opd;
}DDERAYDATA;

The structure consists of 14 double-precision words, each of length 8 bytes and 4 signed integer words, each of length 4 bytes. These memory bytes will appear in exactly the above order in memory. This structure must be matched on the FORTRAN side by a user-defined type with exactly the same sequence of memory bytes. The FORTRAN code for this will be shown later. An array of such structures is expected to be supplied as an argument to the PostArrayTraceMessage function, in number one more than the number of rays to be traced (see the ZEMAX manual for details of how the first element of this array is used to set flags).


FORTRAN Coding Requirements 1

Controlling the calling interface from FORTRAN to C


This is done using a FORTRAN MODULE, called DDEFUNCS, that contains INTERFACE blocks for all the C-routines in ZCLIENT.OBJ that are to be called from within the FORTRAN program. This module must be included in each routine or function that needs to call one or more of the three C-functions mentioned above, as it gives the compiler the vital information that it needs to generate the correct code for function calls to either receive or send data between subprograms and to generate the correct symbols. This is done by including the FORTRAN statement ‘USE DDEFUNCS’. The module is reproduced below (with added comments):




The Visual Fortran compiler recognises ‘compiler directives’ in the form

!DEC$ ATTRIBUTES C, REFERENCE, ALIAS:'_PostRequestMessage' :: PostRequestMessage
The attributes “C, REFERENCE” are required when ‘__cdecl’ is used on the C++ side.

You will notice the inclusion of extra modules, DFWINTY and DDETYPE. DFWINTY is supplied with the Compaq Visual Fortran compiler (its equivalent supplied with the Intel Fortran would be IFWINTY) and it defines parameters and types for most of the Windows® application programming interface (API) functions and routines (such as MessageBox etc.). However, it is only required in DDEFUNCS because it contains the definition of the parameter ‘SINT’ as having value ‘4’, i.e. the integer arguments are to be declared as 4-bytes long using INTEGER(4). It is quite possible to omit the reference to DFWINTY and replace all occurrences of ‘SINT’ with the value ‘4’ if you so wish. However, if you wish to generate 64-bit code, you may have to change SINT to 8. This is automatically taken care of using the above code, where DFWINTY (or IFWINTY) contains conditionally compiled code dependent on which platform the code is to be generated for.

The inclusion of the ALIAS attribute  “ALIAS:'_PostRequestMessage' :: PostRequestMessage “ ensures that the case-sensitive leading-underscored name _PostRequestMessage will be substituted for the otherwise automatically-generated all uppercase name POSTREQUESTMESSAGE used in the FORTRAN code. Thus the linker will seek for the appropriately named C-function when the executable is finally built in the FORTRAN environment.

The module DDETYPE defines a data type that exactly matches the structure of the DDERAYDATA structure defined in the C-code. The FORTRAN module code is reproduced below. The SEQUENCE statement ensures that the items are stored in memory in exactly the sequence shown, so that it will exactly match the structure defined in the C-code:




FORTRAN Coding Requirements 2
Controlling the calling interface from C to FORTRAN

There is only one function involved here, and that is the USERFUNCTION, which is called by the ZCLIENT C-code from within the main window procedure, WndProc. This function should start in the following way:



‘STRING’ has been defined as 260 characters long in the C-code (where it is given the name ‘szCommandLine’), so that value is used here. It is passed by reference, hence this attribute is applied to it. Note how ALIAS is used again, to ensure that ‘_USERFUNCTION’ is looked for by the linker.

Building the Executable Code

I found that the following works (access to an integrated development environment such as Microsoft Developer Studio is assumed here):

  1. Start a C++ compile-only project, inserting a single file, ZCLIENT.CPP into it.
  2. Compile the ZCLIENT.CPP file using the Microsoft Visual C++ compiler, creating a Release version of the ZCLIENT.OBJ file. This needs only be done once and you will then be able to add it to any FORTRAN extension project that you wish to write.
  3. Start a FORTRAN project to generate a full 32-bit windows application that will be your ZEMAX extension. Select an ‘empty’ project, then add to it the files DDEFUNCS.F90, DDETYPE.F90, GLOBALS_ENV.F90, MCDGLOBALS.F90 and ZCLIENT.OBJ.
  4. Write your USERFUNCTION code and add it to the FORTRAN project.
  5. Compile and link the FORTRAN code to produce your executable, YOUR_EXTENSION.EXE (say).
  6. When you have debugged it and have generated a release version, copy YOUR_EXTENSION.EXE over to the ZEMAXEXTEND folder where ZEMAX will automatically find it and list it under the ‘Extensions’ drop-down menu (it is a good idea to select ‘Refresh Extensions list’ if the copying was done while ZEMAX is running).
  7. Start ZEMAX and load a file. As an example try SAMPLESSEQUENTIALOBJECTIVES Double Gauss 5 degree field.ZMX.

Here is a screen shot of the way the ZCLIENT.CPP file is included in a project called ‘userfunc’ and then compiled. In this case, the Microsoft Visual C++ 6.0 compiler and the Compaq Visual Fortran are integrated into the Microsoft Developer Studio environment, so, depending on the file extension (.C, .CPP, .F90 .FOR etc), the appropriate compiler is invoked.



Here is a screen shot showing how the FORTRAN project called ‘Hellofunc’ is organised. In this case the executable file has been given the name HELLOFUNC.EXE:


Running the Extension
Just save the HELLOFUNC.EXE into the {zemaxroot}/EXTEND folder, select the menu item ‘Extensions’ in ZEMAX and select your extension from the drop-down menu that appears. The extension program will then be run and it will start talking to ZEMAX, if ZEMAX is running. Here is a screen-shot showing this being done with the extension HELLOFUNC supplied with this article.


Quirks Found During Development
C-Strings

The main workhorse function, PostRequestMessage, uses two character-string arguments, each consisting of one or more strings of characters separated by commas. All strings used on the C-side are ‘C-Strings’, that is they are and must be terminated with the ‘null’ character, which can be generated within the FORTRAN code using the intrinsic function CHAR with argument 0, CHAR(0). A C-string that is required to be supplied as an argument to one of the C-functions can be generated by adding this character to the end of any FORTRAN string using the concatenation operator ‘//’ thus:

NEWSTRING = OLDSTRING(1:LENGTH)//CHAR(0)
On the other hand, when receiving and wishing to use character strings returned by one of the C-functions defined in ZCLIENT, one must be aware of the ‘null’ character that appears at the end of them and one should strip it off before using an internal read to convert a character string containing the digits of a number into a real or integer data item.

GetString

This C-function included with ZCLIENT takes three arguments: an input C-string, an item number, and an output C-string. The input C-string is presumed to be a sequence of character strings separated by commas (or spaces as I discovered), and the function will locate the appropriate item and copy it into the output string, appending a null character. The location of an item uses the standard C ‘zero’ based counting sequence, where the first item is item 0, the second is item 1 and so on. I have found that in some cases, notably when ZEMAX surface labels are being extracted, that not only is a null character present, but that it can be preceded by both a ‘carriage return’ and a ‘line feed’ character (ASCII codes 13 and 10, decimal, respectively). I use a function called STRIP to strip off these unwanted characters before I contemplate converting the strings to internal numbers using internal READs. The code for STRIP is included with the test code.

One other quirk I discovered was that when faced with a string containing a space, such as one might find in a folder or filename (for example, in the string returned by the GetPath keyword), GetString will take the space as a delimiter, returning a truncated string and counting the remaining part of the string as the next item, thus upsetting the use of the item count to locate the next string. In this case, the delimiting comma was searched for using the intrinsic FORTRAN function INDEX and the required character string length(s) established accordingly.

Testing the DDE Interface
I have attached FORTRAN code for a test USERFUNCTION tailored to work with a ZEMAX model, “Double Gauss 5 degree field.ZMX” that should normally be found in the SAMPLES...SEQUENTIAL...OBJECTIVES folder. Included also are all of the FORTRAN modules you will need (except DFWINTY) and my version of ZCLIENT.CPP. I have also included the linked executable, HELPFUNC.EXE. You are invited to try it out by copying it to the ZEMAX...EXTEND folder and then starting ZEMAX, loading the above lens and selecting HELLOFUNC from the Extensions menu item.

User Interaction

Output to the user is given in calls to the Windows® API function MessageBox, which takes null-terminated C-strings as two of its four arguments, the others being an integer window handle (NULL in this case) and an integer flag (defined with all other flags in module DFWINTY) to show an ‘OK’ button on it. In Compaq Visual Fortran, the FORTRAN interface to this function is specified in a module called DFLIB (in Intel Fortran it will probably be called IFLIB). It is recommended that you follow the FORTRAN code for USERFUNCTION line by line to see how each displayed message box is related to the code. A ‘CANCEL’ button is offered each time a message box is displayed, allowing the user to terminate program execution at any time. Here is an example MessageBox output by the extension HELLOFUNC.



Extracting Model Data and Ray Tracing

Along the way, the program also generates two files, one called “SURFLABELS.TMP” (probably found in the EXTEND folder) containing a list of surface comments extracted from the ZEMAX model, and “TESTLOCAL.TXT” (probably found in the SAMPLES folder) containing X,Y and Z co-ordinate intercepts on five of the surfaces traced at maximum field and through the (0,1) pupil point. The latter co-ordinates are local co-ordinates generated using the PostTraceRequest function. They should then be compared with the values obtained later by tracing the same rays but this time using the PostArrayTraceMessage function, and shown to be identical. To transform from a surface’s local co-ordinates to a global frame, the transformation matrix is needed and examples of extracting this data are also demonstrated.

Generating Graphic Data and Opening, Displaying and Interacting with a ZEMAX Graphic Window

The demonstration extension also generates some points to plot in a ZEMAX graphic window, using the DDE data item ‘MakeGraphicWindow’ as you will discover. If you examine the code, you will find a routine PUPILPOINTS that generates points on a spiral inside a unit circle representing the pupil. The plot commands are generated in routine PUPILPLOT and output into the temporary file named and supplied by ZEMAX on the command line that starts the extension. (See the section on “Generating a Graphic Window” in the ZEMAX Extensions chapter of the manual for a full description of the simple script language used for the plot commands). Note that a single ‘setting’ value of ‘1’ is fed to ZEMAX at the end of the MakeGraphicWindow string. This is used by the HELLOFUNC extension as a flag when ZEMAX returns it to the extension whenever the user clicks on the ‘Update’ or  ‘Settings’ items in the menu at the top of the graphics window that is opened and controlled by the extension. Here is a picture of what should appear:



The graphic window will remain displayed, even when the extension is terminated, until it is closed by the user. The HELLOFUNC extension can be active or not, with the graphic displayed, and if you click on ‘Update’ or ‘Settings’ menu items, another instance of the extension is started, so the ‘settings’ flag, stored by ZEMAX and associated with the window, is sent back to the new instance of the extension and is used, in the present case, to sense and quit this new instance, displaying the message that ‘This window has no options’. On the left below is the message displayed by the extension when the ‘Update’ menu item is pressed. On the right is that shown when the ‘Settings’ menu item is selected and the ‘Options’ flag is set to ‘1’ by ZEMAX.


Otherwise the extension could just regenerate new data for the plot window, based on the ‘settings’ data that ZEMAX received when it got the ‘MakeGraphicWindow’ command and which it reflects back to the extension when the appropriate menu item is selected. Note that before any instance of the extension is terminated, the DDE item ‘ReleaseWindow’ is and must be sent, in order to release control of the graphics window. The following is the message sent using PostRequestMessage:



A message box (on the right above) is programmed to appear giving the return code from this command, which is just the number displayed in the top left hand corner of the graphics window shown earlier. This number would be ‘0’ if no window had been displayed before the request to ‘ReleaseWindow’ was made. Refer to the ZEMAX manual for full details on extension-generated graphic and text windows and their settings.

Note that an extension can open and maintain only one graphic window, as only a single temporary filename is supplied to the extension by ZEMAX. You can have as many extensions as you like running, and they can all have their own windows, but only one window is allowed per instance of the extension. This is just the way ZEMAX works. For example, when you open a ray fan plot you don't get two windows, just one.
 
If an extension wanted to show more than one graph or text listing, then the workaround is for the extension to make the selection of which data to present one of the settings, like ZEMAX does to choose between false color and contour maps for example. The extension could even create multiple displays, and then store them for rapid re-display when the user selects the alternate display.


Useful Links for Mixed Fortran-C Programming
I can recommend the Intel® Visual Fortran for Windows user forum as an excellent source of help for mixed language programming. This can be found at Intel® Visual Fortran Compiler for Windows - Intel® Software Network Forums.

The FORTRAN reference manuals supplied with the Compaq Visual Fortran (previously Digital Fortran and no longer marketed or supported unfortunately) and Intel Fortran compilers are also very helpful.