L. Young
L. Young

Reputation: 103

Using MinGW gcc to create DLLs for VBA 64 bit

I want to do what the title says. This has been discussed before (How to create dll using gcc compiler/Mingw for visual basic?), but these posts are 10 years old. I was writing Excel/VBA macros at that time without difficulty, but now I want to update them to 64 bit Windows. I've also switched compilers since then. I have had no trouble creating DLLs with gcc or gfortran that are called by gcc or Python. It is difficult to understand the compatibility issues, because you don't know when an article was written and whether it is still valid in the 64 bit world. You may ding me for rehashing this old subject, but maybe others have a similar problem. I have also found an excellent article (https://www.transmissionzero.co.uk/computing/building-dlls-with-mingw/). I have tried to follow both of these, but can't get anything working in an Excel spreadsheet. Maybe I've made a mistake that will be obvious to someone. Here is the function, code.c:

__declspec(dllexport) int __stdcall sqint(int x){
   return x * x;
}

I build the DLL with:

gcc code.c -shared -o code.dll -s -Wl,--subsystem,windows,--kill-at,-Map=code.map

The code.map file and the objdump utility, both show the function, e.g. here are a few lines from objdump -p code.dll:

[Ordinal/Name Pointer] Table
    [   0] sqfloat
    [   1] sqint

where sqfloat is a similar code with doubles. The map file also shows references to libraries, like:

/usr/lib/gcc/x86_64-pc-cygwin/7.4.0/../../../../lib/libcygwin.a

Next, I declare it in VBA with:

Declare PtrSafe Function sqint Lib "C:\Cwin\mix\code.dll" (ByVal x As Long) As Long

A visual basic long and gcc int should both be 4 bytes. I should be able to call the function directly, but I tried putting it inside another function during debugging. With the VBA debugger, I can see the value being passed to the function, but I get nothing other than the dreaded #VALUE!. If you see what I've done wrong, I'd appreciate hearing from you.

Update 7/2/2020: I've made another stab at this problem with limited success. I believe the DLL I was creating was not 64 bit. I thought this would be the default. The build is now like:

gcc code.c -Wa,-march=generic64 -shared -o code.dll -s -Wl,--subsystem,windows,--kill-at,-Map=code.map

Following the posts at how to test DLL, using file code.dll gives:

code.dll: PE32+ executable (DLL) (GUI) x86-64 (stripped to external PDB), for MS Windows

The sqfloat function now returns a correct result, but then eventually Excel craps out and closes. The sqint function returns 2*2 = 1. I tried using Integer instead of long, but no change. Did MSVBA change the definition of long?

So the current status is I occasionally get a result, but the macro kills Excel.

Upvotes: 2

Views: 1382

Answers (1)

alain R
alain R

Reputation: 13

I am trying to create a C wrapper DLL to interface a commercial DLL written with the CDECL calling convention, so that it can be interfaced with existing programs written in Excel VBA (hence using STDCALL calling convention).

I have exactly the same issue, probably after trying very similar things. I have tried using mingw (gcc & g++, 32 & 64 bits), and cygwin gcc flavors (gcc & g++ 32 & 64 bits). No luck.

I suspect an issue with the compilation or linker, as some combinations of compiler produce DLL that are just not recognized by VBA (although they can be called by a stub exe) throwing an error 53-DLL not found. Other combination give another error (48-error loading DLL), of course with the very same code.

I am using Windows 10 64 bits with Office 64 bits.

This is wrapper.c:

    #include "wrapper.h"
    #include <windows.h>
    #include <stdio.h>

    #ifdef cplusplus
    extern "C"
    {
    #endif

    PCNC_EXPORT void __stdcall WrFreeString(char* str)
    {
        //Memory
        return FreeString(str);
    }
    PCNC_EXPORT int __stdcall WrGetVer()
    {
        return GetVer();
    }
    PCNC_EXPORT char* __stdcall WrGetLibPath()
    {
        return GetLibPath();
    }

    // snip ___


    PCNC_EXPORT BOOL __stdcall DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
    {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }

    #ifdef cplusplus
    }
    #endif

wrapper.h:

    #include "PlanetCNCLib.h"
    #include "PlanetCNCLibTypeDef.h"

    #ifdef cplusplus
    extern "C"
    {
    #endif

    PCNC_EXPORT void __stdcall WrFreeString(char* str);
    PCNC_EXPORT int __stdcall WrGetVer();
    PCNC_EXPORT char* __stdcall WrGetLibPath();

    #ifdef cplusplus
    }
    #endif

from PlanetCNCLib.h:

    #ifndef __PlanetCNCLib_H__
    #define __PlanetCNCLib_H__


    #include "PlanetCNCLibTypeDef.h"


    #ifdef __cplusplus
    extern "C" {
    #endif


    #ifndef PCNC_EXPORT
    #   ifdef _WIN32
    #       define PCNC_EXPORT __declspec(dllexport)
    #   else
    #       define PCNC_EXPORT __attribute__((visibility("default"))) 
    #   endif
    #endif

    // etc

And the Makefile:

    #CPP=/usr/bin/i686-w64-mingw32-g++.exe   # doesnt work (err 48 err loading DLL)
    #CPP=/usr/bin/x86_64-w64-mingw32-g++.exe # doesnt work (err 53 DLL not found)
    #CPP=/usr/bin/i686-pc-cygwin-g++         # doesnt work (err 48 err loading DLL) 
    #CPP=/usr/bin/x86_64-pc-cygwin-g++.exe   # doesnt work (err 53 DLL not found)
    #CPP=gcc                                 # doesnt even compile

    #LD=/usr/bin/i686-w64-mingw32-ld.exe
    #LD=/usr/bin/x86_64-w64-mingw32-ld.exe
    LD=$(CPP)

    LDFLAGS=-shared -L/usr/lib -L./lib/ -Wl,--add-stdcall-alias #--kill-at
    CFLAGS=-c

    all: PlanetCNCWrapper.dll

    wrapper.o:PlanetCNCLib.h PlanetCNCLibTypeDef.h wrapper.c
        $(CPP) -o $@ wrapper.c  $(CFLAGS)

    PlanetCNCWrapper.dll:wrapper.o
        $(LD) $(LDFLAGS) -o $@ $? -L./lib/ lib/PlanetCNCLib64.lib -lPlanetCNCLib64 

    clean:
        rm -f *.o *.e *~ .*~ *stackdump PlanetCNCWrapper.dll

Other useful data:

    Windows Version 1909 (OS Build 18363.778)
    System type: 64-bit operating system, x64-based processor
    Microsoft Excel for Office 365 MSO (16.0.11929.20708) 64 bits
    i686-w64-mingw32-g++ (GCC) 9.2.0
    x86_64-w64-mingw32-g++ (GCC) 9.2.0
    i686-pc-cygwin-g++ (GCC) 6.4.0
    x86_64-pc-cygwin-g++ (GCC) 9.3.0

Forgot to add that the --add-stdlib-alias doesn't seem to do anything: symbols get decorated in the DLL regardless of the presence of this flag.

Upvotes: 1

Related Questions