Reputation: 103
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
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