Create a Zemax 64 bit DLL

Zemax would normally publish such article in its Knowledge Base. But I think it should be significantly improved (outdated articles should be removed/rewritten, search should be improved, articles should be tagged and linked to each other, user comments should not get lost etc.), and until it is I’ll just publish my notes here.

One of the ways to expand Zemax functionality is by creating custom DLLs for objects, scattering, material, surface etc. Unfortunately, you’ll need something beyond basic knowledge on how to create a DLL in the IDE/compiler of your choice. Unless your DLL looks exactly the way Zemax expects it to look, the program will just ignore it, without any feedback. After some struggling, I’ve managed to make Zemax accept my DLL, and I list the required steps below, with some comments.

As the starting point, I’ve used the C:\Program Files\Zemax\DLL\GradientIndex\grin1.c file provided by Zemax. I also tested this procedure with C:\Program Files\Zemax\DLL\SurfaceScatter\TwoGaussian.c.

Here are the steps needed to make Zemax 64 accept your DLL. The IDE I used was the free MS Visual Studio Express 2013 for Desktop.

  1. Create a new solution in VS. Create a new Visual C++ > Win32 > DLL project in it (http://www.codeproject.com/Articles/9826/How-to-create-a-DLL-library-in-C-and-then-use-it-w)
  2. Since Zemax is x64, we need a 64 bit DLL. Go to configuration manager and create a new x64 platform. Set it to active.
    Note: To have it in VS 2010, see http://msdn.microsoft.com/en-us/library/9yb4317s(v=vs.100).aspx (something should be downloaded).
  3. Switch compilation mode to Release. Set Project > Properties (Alt + F7) > Configuration Properties > Linker > Debugging > Generate Debug Info for Release/x64 to NO (http://stackoverflow.com/a/2805812/674976)
  4. In Project > Properties > Configuration Properties > C/C++ > Preprocessor, add at the end of Preprocessor Definitions:
    ;_CRT_SECURE_NO_WARNINGS

    (http://stackoverflow.com/a/20753468/674976)

  5. In Project > Properties > Configuration Properties > C/C++ > Precompiled Headers, set Precompiled Header to an empty value (it can be Yes be default). Taken from http://stackoverflow.com/questions/7261707.
  6. Note the
    extern "C"

    lines I added to the code. This comes from http://www.codeproject.com/Articles/9826/How-to-create-a-DLL-library-in-C-and-then-use-it-w
    This issue is also addressed (and explained in more details) here: http://www.ni.com/white-paper/3056/en/.
    This command is only needed if the the source file has the .cpp extension. For .c it is not needed.

  7.  The DLL should be copied to C:\Program Files\Zemax\DLL. It requires Administrator rights on the computer.Note that the output DLL is in
    C:\Users\xxx\x64\Release
    rather than
    C:\Users\xxx\testDll\x64\Release
    I don’t know why.

    The following steps are optional:

  8. Note that I removed the
    int __declspec(dllexport) APIENTRY UserGrinDefinition(double *data);
    int __declspec(dllexport) APIENTRY UserParamNames(char *data);
    

    lines at the top. It also comes from the fact that they are not there in http://www.codeproject.com/Articles/9826/How-to-create-a-DLL-library-in-C-and-then-use-it-w

  9. To check why Zemax could not see the functions at first, I used the dumpbin tool.
    It should be run from VS’s Command Line, which I could find nowhere. I’ve found how to add it to Tools here: http://stackoverflow.com/a/4245337/674976.
    The required vcvarsall.bat file could be found in my case in C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC, although I am working with Visual Studio Express 2013. Not sure why.
    With this set up, I could do dumpbin /EXPORTS testDll.dll in the x64/release directory and get:

    C:\Users\xxx\x64\Release>dumpbin /EXPORTS testDll.dll
    Microsoft (R) COFF/PE Dumper Version 12.00.21005.1
    Copyright (C) Microsoft Corporation. All rights reserved.
    
    Dump of file testDll.dll
    
    File Type: DLL
    
    Section contains the following exports for testDll.dll
    
    00000000 characteristics
    536A6A96 time date stamp Wed May 07 19:17:10 2014
    0.00 version
    1 ordinal base
    2 number of functions
    2 number of names
    
    ordinal hint RVA name
    
    1 0 00001010 UserGrinDefinition
    2 1 00001270 UserParamNames
    
    Summary
    
    1000 .data
    1000 .pdata
    1000 .rdata
    1000 .reloc
    1000 .rsrc
    1000 .text
  10. Note that if the DLL is compiled with Visual Studio >2010, it cannot be used on computers with only 2010 distributables. In this case, the distributable DLL can be
  11. statically linked to the DLL, thus removing the dependence. This is done in VS 2013 in Project Properties (Alt + F7) > Configuration Properties > C/C++ > Code Generation > Runtime Library,
    which should be set to Multi-threaded (/MT) from the default Multi-threaded DLL (/MD).

With these modifications, Zemax accepted the DLL and could read the parameter names. I have not tried any calculations so far.

Full source code of the final file (testDll.cpp):

#include <windows.h>
#include <math.h>
#include <string.h>

/*
Written by Kenneth E. Moore
May 11, 2000

Modified January 24, 2005 to support rectangular periodic arrays

Modified 2014-05-07 by Kotya Karapetyan as a test Zemax 64 DLL project. Renamed to testDll.cpp.
*/

BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
{
   return TRUE;
}

extern "C" // taken from http://www.codeproject.com/Articles/9826/How-to-create-a-DLL-library-in-C-and-then-use-it-w, is needed to remove C++ symbol decoration, see http://www.ni.com/white-paper/3056/en/

{
    int __declspec(dllexport) APIENTRY UserGrinDefinition(double *data)
    {
        double r, r2, index, N, A, B, x, y, dx, dy;
        
        N = data[10];
        A = data[11];
        B = data[12];
        dx = data[13];
        dy = data[14];
        x = data[1];
        y = data[2];

        if (dx > 0 && dy > 0)
        {
            // adjust to center of array
            if (x > +0.5*dx) x -= dx*(double)ceil(+(x - 0.5*dx) / dx);
            if (x < -0.5*dx) x += dx*(double)ceil(-(x + 0.5*dx) / dx);
            if (y > +0.5*dy) y -= dy*(double)ceil(+(y - 0.5*dy) / dy);
            if (y < -0.5*dy) y += dy*(double)ceil(-(y + 0.5*dy) / dy);
        }

        r2 = x*x + y*y;
        r = sqrt(r2);

        index = N + A*r2 + B*r;
        data[6] = index;

        if (index < 1.0)
        {
            return -1;
        }

        /* now the derivatives, note they are multiplied by index */
        data[7] = 2.0*A*x*index;
        data[8] = 2.0*A*y*index;
        if (B != 0.0 && r > 0.0)
        {
            data[7] += B*x*index / r;
            data[8] += B*y*index / r;
        }
        data[9] = 0.0;
        return 0;
    }
 
    int __declspec(dllexport) APIENTRY UserParamNames(char *data)
    {
        /* this function returns the name of the parameter requested */
        int i;
        i = (int)data[0];
        strcpy(data, "");
        if (i == 1) strcpy(data, "N0");
        if (i == 2) strcpy(data, "Nr2");
        if (i == 3) strcpy(data, "Nr1");
        if (i == 4) strcpy(data, "Array dx");
        if (i == 5) strcpy(data, "Array dy");
        if (i == 6) strcpy(data, "K"); // added by Kotya for testing 2014-05-07
        return 0;
    }
}

 

See also related Knowledge Base articles: How to compile an extension using MS Visual Studio Express 2012Compiling a 64-bit Application in Microsoft Visual Studio Express 2010 (note: I haven’t checked them for correctness).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s