Converting C-API Add-Ins to 32-bit Microsoft Excel

The files included in this article will help you to build compiled add-ins for the 32-bit (Intel x86 family) versions of Microsoft Excel (Microsoft Excel 7.0 for Windows 95 and Microsoft Excel 5.0 for Windows NT). The article includes the C-API files Xlcall32.lib and Xlcall.h. Xlcall32.dll is also required, and is installed in your Windows system folder when you install Microsoft Excel.

Click to open or copy the CAPI project files

Conversion Notes

1. The 32-bit versions of the Visual C++ compiler (versions 2.0, 2.1, 2.2, and 4.0) changed the __export keyword to __declspec(dllexport). Use this keyword on your function declarations that are called from Microsoft Excel. For example:


__declspec(dllexport) int TestFunction(int iArg)
{
    ...
}

There are some issues related to using __declspec(dllexport) in 32-bit versions of Visual C++. For more information, see "Exporting PASCAL-Like Symbols in 32-bit DLLs" later in this article.

2. Ensure that Xlcall32.dll is in your Windows system folder.

3. Xlcall.h is the same file that you used in the older (16-bit) C-API.

4. Closely examine parameters, variables, and return values declared as type int in your C/C++ code. In C/C++ code the size of an int depends on the system and/or compiler. Win16 defined an int to be 16-bits, or the same as a short int or short. In Microsoft Excel the short int code used with REGISTER() is "H" or "I". Win32 defines an int to be 32-bits, or the same as a long int or long, and the Microsoft Excel registration code is "J".

System-dependent int sizing may cause a problem with internal structure sizes and numeric overflows, but in most cases it doesn't affect the program. Because, however, Microsoft Excel does not support a system-dependent int parameter or return value type, Microsoft Excel will have a problem with carelessly written Win16 code that is recompiled for Win32. Plain int parameters and return values are expanded to 32-bits, which effectively changes the registration type code from "H" to "J". Calling an incorrectly declared procedure may result in incorrect results or a protection fault. There are two solutions to this problem:

5. The C-API xlGetHwnd call that retrieve the main window handle of Microsoft Excel is problematic because sizeof(HWND) on Win32 is a long but the field used to receive the HWND parameter in the XLOPER structure is a short. Consequently the high word of the actual Microsoft Excel HWND is truncated. To work around this problem call xlGetHwnd to get the low word of the actual hwnd, then iterate the list of top-level windows and look for a match with the returned low word. This code illustrates the technique:


typedef struct _EnumStruct {
    HWND        hwnd;
    unsigned short    wLoword;
} EnumStruct;

#define CLASS_NAME_BUFFER    50

BOOL CALLBACK EnumProc(HWND hwnd, EnumStruct * pEnum)
{
    // first check the class of the window. Must be "XLMAIN"
    char rgsz[CLASS_NAME_BUFFER];
    GetClassName(hwnd, rgsz, CLASS_NAME_BUFFER);
    if (!lstrcmpi(rgsz, "XLMAIN")) {
        // if that hits, check the loword of the window handle
        if (LOWORD((DWORD) hwnd) == pEnum->wLoword) {
            pEnum->hwnd = hwnd;
            return FALSE;
        }
    }

    // no luck - continue the enumeration
    return TRUE;
}

BOOL GetHwnd(HWND * pHwnd)
{
    XLOPER x;

    if (Excel4(xlGetHwnd, &x, 0) == xlretSuccess) {
        EnumStruct enm;
        
        enm.hwnd = NULL;
        enm.wLoword = x.val.w;

        EnumWindows((WNDENUMPROC) EnumProc, (LPARAM) &enm);
        
        if (enm.hwnd != NULL) {
            *pHwnd = enm.hwnd;
            return TRUE;
        }
    }
    return FALSE;
}

Exporting PASCAL-Like Symbols in 32-bit DLLs

This section is taken from Microsoft Product Support Services Knowledgebase article Q140485.

Microsoft Visual C++, 32-bit Edition, versions 2.0, 2.1, 2.2, 4.0 There is no _pascal keyword in the 32-bit editions of Visual C++. Instead the Windef.h header file has PASCAL defined as __stdcall. This creates the correct style calling convention for the function (the called function cleans up the stack) but decorates the function name differently. So, when __declspec(dllexport) is used (in a DLL, for example), the decorated name is exported instead of the desired PASCAL style name, which is undecorated and all uppercase.

PASCAL name decoration is simply the undecorated symbol name in uppercase letters. __stdcall name decoration prefixes the symbol name with an underscore (_) and appends the symbol with an at sign (@) character followed by the number of bytes in the argument list (the required stack space). Therefore, when you declare the following function:


   int  __stdcall func (int a, double b)

it is decorated as:


   _func@12

The C calling convention (__cdecl) decorates the name as _func, whereas the desired PASCAL style name is FUNC.

To get the decorated name set the Generate Mapfile option in the Linker General category setting.

Use of __declspec(dllexport) does the following:

If the function is exported with C calling convention (_cdecl), it strips the leading underscore (_) when the name is exported.

If the function being exported does not use the C calling convention (for example, __stdcall ), it exports the decorated name.

So to simulate PASCAL name decoration and calling conventions, you must have the "Called Function stack clean-up" provided by using __stdcall and the undecorated uppercase name.

Because there is no way to override who does the stack clean up, you must use __stdcall. To undecorate names with __stdcall, you must specify them by using aliases in the EXPORTS section of the .def file. This is shown below for the following function declaration:


   int  __stdcall MyFunc (int a, double b);
   void __stdcall InitCode (void);

In the .def file:


   EXPORTS
      MYFUNC=_MyFunc@12
      INITCODE=_InitCode@0