c++excelvbadllexportstdcall

Correct calling convention for exporting windows DLL functions for Excel VBA without mangled names


I am writing a DLL to export functions to be used in Excel VBA - I have found a way to be able to pass parameters in but with mangled names. If I set up without name mangling then I can not pass parameters and get a calling convention error

I use the standard declaration for calling DLL exported functions from VBA:

VBA

Public Declare Function foo Lib "C:\ ... \helloworld.dll" (ByVal bar As Long) As Long

My function is set up as so:

helloworld.cpp


extern "C" __declspec(dllexport) long foo(long bar){
    return bar * 2;
}

I compile with cl.exe /LD helloworld.cpp using cl.exe (Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30145 for x86)

and dumplib/exports helloworld.dll yields

Dump of file helloworld.dll

File Type: DLL

  Section contains the following exports for helloworld.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001000 foo

  Summary

        2000 .data
        6000 .rdata
        1000 .reloc
        A000 .text

If I call the function from VBA

VBA

dim x as long
x = foo(2)

I get the VBA error Bad DLL calling convention (Error 49)

If I add __stdcall to the function signature,

extern "C" __declspec(dllexport) long __stdcall foo(long bar){
    return bar * 2;
}

I get the following DLL export

Dump of file helloworld.dll

File Type: DLL

  Section contains the following exports for helloworld.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001000 _foo@4

  Summary

        2000 .data
        6000 .rdata
        1000 .reloc
        A000 .text

And the function now works if I use the alias in the VBA declaration

Public Declare Function foo Lib "C:\ ... \helloworld.dll" Alias "_foo@4" (ByVal bar As Long) As Long
VBA

dim x as long
x = foo(2)
'foo sets x = 4

Is it possible to pass parameters to functions but not have a mangled/ordinal name?


Solution

  • Per Microsoft's documentation:

    https://learn.microsoft.com/en-us/office/client-developer/excel/developing-dlls

    When compilers compile source code, in general, they change the names of the functions from their appearance in the source code. They usually do this by adding to the beginning and/or end of the name, in a process known as name decoration. You need to make sure that the function is exported with a name that is recognizable to the application loading the DLL. This can mean telling the linker to associate the decorated name with a simpler export name. The export name can be the name as it originally appeared in the source code, or something else.

    The way the name is decorated depends on the language and how the compiler is instructed to make the function available, that is, the calling convention. The standard inter-process calling convention for Windows used by DLLs is known as the WinAPI convention. It is defined in Windows header files as WINAPI, which is in turn defined using the Win32 declarator __stdcall.

    A DLL-export function for use with Excel (whether it is a worksheet function, macro-sheet equivalent function, or user-defined command) should always use the WINAPI / __stdcall calling convention. It is necessary to include the WINAPI specifier explicitly in the function's definition as the default in Win32 compilers is to use the __cdecl calling convention, also defined as WINAPIV, if none is specified.

    You can tell the linker that a function is to be exported, and the name it is to be known by externally in one of several ways:

    • Place the function in a DEF file after the EXPORTS keyword, and set your DLL project setting to reference this file when linking.
    • Use the __declspec(dllexport) declarator in the function's definition.
    • Use a #pragma preprocessor directive to send a message to the linker.

    Although your project can use all three methods and your compiler and linker support them, you should not try to export one function in more than one of these ways. For example, suppose that a DLL contains two source code modules, one C and one C++, which contain two functions to be exported, my_C_export and my_Cpp_export respectively. For simplicity, suppose that each function takes a single double-precision numerical argument and returns the same data type. The alternatives for exporting each function by using each of these methods are outlined in the following sections.

    ...

    The article then goes on to provides examples of each method.

    In your case, since you are already doing the 2nd method and not getting the result you want, you will have to employ the 1st or 3rd method as well.