Calling Native C from VBA - vba

I am unable to create a minimal working example of a native dll in combination with VBA
There are three Problems:
I can not resolve error 453 (can't find dll entry point)
I do not know how to marshall variac
VBA (ExCel)
Option Explicit
Public Declare Sub KERNEL32_SLEEP _
Lib "kernel32" _
Alias "Sleep" (ByVal dwMilliseconds As Long)
Public Declare Sub CALLADAPTER_SIMPLE _
Lib "D:\Stackoverflow\Release\CallAdapter.dll" _
Alias "simple" ()
Public Declare Function CALLADAPTER_ADD _
Lib "D:\Stackoverflow\Release\CallAdapter.dll" _
Alias "add" (ByVal A As Integer, ByVal B As Integer) As Integer
Public Declare Sub CALLADAPTER_PRINT _
Lib "D:\Stackoverflow\Release\CallAdapter.dll" _
Alias "print" (ByVal FormatSpecifier As String)
Sub TEST_KERNEL32_SLEEP()
Call KERNEL32_SLEEP(2000) 'works
End Sub
Sub TEST_CALLADAPTER_SIMPLE()
Call CALLADAPTER_SIMPLE 'error 453 can't find dll entry point
End Sub
Sub TEST_CALLADAPTER_ADD()
Dim A, B, C As Integer
A = 30
B = 12
C = CALLADAPTER_ADD(A, B) 'error 453 can't find dll entry point
MsgBox "A + B = " & C
End Sub
Sub TEST_CALLADAPTER_PRINT()
Call CALLADAPTER_PRINT("Hello World") 'error 453 can't find dll entry point
End Sub
Sub TEST_CALLADAPTER_PRINTF()
'I do not know how to marshall variadic
End Sub
Ansi C ( Visual Studio 2010 )
// Header
#ifdef CALLADAPTER_EXPORTS
#define CALLADAPTER_API __declspec(dllexport)
#else
#define CALLADAPTER_API __declspec(dllimport)
#endif
CALLADAPTER_API void _stdcall simple( void );
CALLADAPTER_API int _stdcall add( int a, int b );
CALLADAPTER_API void _stdcall print( const char * msg );
CALLADAPTER_API int _stdcall printf( const char * format, ... );
// code
#include "CallAdapter.h"
#include <stdio.h>
#include <stdarg.h>
CALLADAPTER_API void _stdcall simple( void )
{
printf("simple was called\n");
}
CALLADAPTER_API int _stdcall add( int a, int b )
{
return a + b;
}
CALLADAPTER_API void _stdcall print( const char * msg )
{
printf( "%s", msg );
}
CALLADAPTER_API int _stdcall printf( const char * format, ... )
{
int ret;
va_list args;
va_start( args, format );
ret = vprintf( format, args );
va_end( args );
return ret;
}
Edit #1: I completely reworked the examples to better illustrate my problems.
Edit #2: Progress with variadic.
There is a very useful site on the web.
I made some progress but i stll can not compule the call...
VBA
Option Explicit
Public Declare Function CallAdapter_sprintf _
Lib "D:\Stackoverflow\Release\CallAdapter.dll" _
Alias "sprintf" (ByRef DST As String, ByRef FORMAT As String, ParamArray args()) As Integer
Sub TEST_CALLADAPTER_sprintf()
Dim DESTINATION, FORMAT As String
Dim OTHER() As Variant
Dim C As Integer
FORMAT = "%s"
OTHER = Array("Hello World")
DESTINATION = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
C = CallAdapter_sprintf(DESTINATION, FORMAT, OTHER)
MsgBox "RET " & C & " -> " & DESTINATION
End Sub
Ansi C
CALLADAPTER_API int _stdcall sprintf( char * dest, const char * format, ... )
{
int ret;
va_list args;
va_start( args, format );
ret = vsprintf( dest, format, args );
va_end( args );
return ret;
}

varocarbas gave a good hint.
I used Dependency Walker to get the correct names.
This solves half of my problem, i still dont know how to marshall variadic
Public Declare Sub CALLADAPTER_SIMPLE _
Lib "D:\Stackoverflow\Release\CallAdapter.dll" _
Alias "_simple#0" ()
Public Declare Function CALLADAPTER_ADD _
Lib "D:\Stackoverflow\Release\CallAdapter.dll" _
Alias "_add#8" (ByVal A As Integer, ByVal B As Integer) As Integer
Public Declare Sub CALLADAPTER_PRINT _
Lib "D:\Stackoverflow\Release\CallAdapter.dll" _
Alias "_print#4" (ByVal FormatSpecifier As String)
Only the ADD example is a good example since stdout does not seem to exist.

Related

Runtime Error 49, Bad DLL calling convention in a D language DLL for Excel/VBA - What am I missing?

I'm trying to get the simplest possible D-language DLL working from VBA in Excel. I am using Visual D. First, I copied the boilerplate example from https://wiki.dlang.org/Win32_DLLs_in_D, DLLs with a C interface, using the following code:
module DDLL;
import core.sys.windows.windows;
import core.sys.windows.dll;
__gshared HINSTANCE g_hInst;
extern (Windows)
BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
switch (ulReason)
{
case DLL_PROCESS_ATTACH:
g_hInst = hInstance;
dll_process_attach( hInstance, true );
break;
case DLL_PROCESS_DETACH:
dll_process_detach( hInstance, true );
break;
case DLL_THREAD_ATTACH:
dll_thread_attach( true, true );
break;
case DLL_THREAD_DETACH:
dll_thread_detach( true, true );
break;
default:
}
return true;
}
The D application code I am trying to call from VBA is this:
module myfns;
export double testdd(double a, double b) { return a + b + 0; }
export int testi(int x) {return 42 + x ; }
export int testii(int a, int b) { return a + b + 0; }
The VBA code is:
Option Explicit
Declare Function testdd Lib "C:\Users\vvkozlov\sources\DDLL\Win32\Debug\DDLL.dll" _
Alias "_D5myfns6testddFddZd" (ByVal x As Double, ByVal y As Double) As Double
Declare Function testi Lib "C:\Users\vvkozlov\sources\DDLL\Win32\Debug\DDLL.dll" _
Alias "_D5myfns5testiFiZi" (ByVal x As Long) As Long
Declare Function testii Lib "C:\Users\vvkozlov\sources\DDLL\Win32\Debug\DDLL.dll" _
Alias "_D5myfns6testiiFiiZi" (ByVal x As Long, ByVal y As Long) As Long
Public Sub test()
Dim x As Long: x = 42000
Debug.Print "two doubles: " & testdd(84#, -42#)
Debug.Print "one long: " & testi(x)
Debug.Print "two longs: " & testii(x, -x)
End Sub
Note I went in and fished out the mangled names from the generated DLL.
The testdd (two doubles) example works as expected, but both of the int examples result in a
Runtime Error 49, Bad DLL calling convention
in VBA. At first, second, and third glance the argument declarations seem to be fine - Long on the VBA side, int in D.
Why does the double example work while the int fails?
Adam D Ruppe's advice was spot on. The D application code now looks like
module myfns;
import std.conv;
extern(Windows) export double testdd(double a, double b) { return a + b + 0; }
extern(Windows) export int testi(int x) {return 42 + x ; }
extern(Windows) export int testii(int a, int b) { return a + b + 0; }
which also has the pleasant side effect of at least simplfying the name mangling (no, removing the Alias from the VBA code does not work).
The VBA now looks like
Option Explicit
Declare Function testdd Lib "C:\Users\vvkozlov\sources\DDLL\Win32\Debug\DDLL.dll" _
Alias "_testdd#16" (ByVal x As Double, ByVal y As Double) As Double
Declare Function testi Lib "C:\Users\vvkozlov\sources\DDLL\Win32\Debug\DDLL.dll" _
Alias "_testi#4" (ByVal x As Long) As Long
Declare Function testii Lib "C:\Users\vvkozlov\sources\DDLL\Win32\Debug\DDLL.dll" _
Alias "_testii#8" (ByVal x As Long, ByVal y As Long) As Long
And now on to BSTR, then maybe SAFEARRAY. No Variant.

Calling GetProcAddress from VBA always returns null

I have 64 bit windows 10 with MS Office 64 bit.
I am trying to get the VBA for Powerpoint to load and execute a function in a self-written 64 bit windows DLL.
To prevent export name mangling I have used extern C:
extern "C" {
__declspec(dllexport) long jaadd(long a, long b)
{
return a + b;
}
}
This simple function can be called by a C++ module with no problems:
hinstDLL = LoadLibrary(L"D:\\Visual Studio 2017\\Projects\\PopUpDLL\\x64\\Debug\\PopUpDLL.dll");
if (hinstDLL != NULL)
{
jaadd = (AddFunc)GetProcAddress(hinstDLL, "jaadd");
if (jaadd != NULL) {
result = jaadd(13, 40);
}
fFreeDLL = FreeLibrary(hinstDLL);
}
The problem arises when trying to call the DLL from VBA in Powerpoint. GetProcAddress always returns zero and so does FreeLibrary
Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As LongLong
Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private hLib As Long
Sub LLib()
hLib = LoadLibrary("D:\\Visual Studio 2017\\Projects\\PopUpDLL\\x64\\Debug\\PopUpDLL.dll")
MsgBox hLib
Dim pprocaddress As Long
pprocaddress = GetProcAddress(hLib, "jaadd") ***** always returns 0
MsgBox pprocaddress
xx = FreeLibrary(hLib) ***** always returns 0
MsgBox xx
End Sub
Any help gratefully received.
Did you every try
Private Declare PtrSafe Function jaadd Lib "......\x64\Debug\PopUpDLL.dll"
Alias "_jaadd#8" (ByVal arg1 As Long, ByVal arg2 as Long) As Long
Note the Alias mangling "algorithm": _ + your original name + # + sum of argument bytes.
You have two VBA Longs, or two C# ints, 4 + 4 = 8.
You can also dispense with the twin \ thing here in VBA-land.
See also https://learn.microsoft.com/en-us/office/client-developer/excel/how-to-access-dlls-in-excel
Lastly, make sure you use a 32-bit DLL for 32-bit VBA, and a 64-bit DLL for 64-bit VBA.
So many websites in their DECLARE statements have the handle for LoadLibrary and return of GetProcAddress as Long instead of LongPtr.
Problem is that the information is stale - the code was never updated to reflect the post-Excel 2009 state of VBA.

how to pass struct from vb to dll?

I'm trying to pass a struct from vb to dll im facinf isues on that.
//Datalink layer.cpp
#include"DataLinkLayer.h"
#include<stdio.h>
#include <windows.h>
int __stdcall DataLink_TellTale_Encode (struct telltalelib *st_telltale, LPSTR * rtnFramePassedPtr)
{
sprintf(*rtnFramePassedPtr,"<%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d>",NORMAL,STREAM,VEHICLEOVERALL,NA,PERCENTAGE,TELLTALEID,COMPIDNA,TELLTALELENGTH,st_telltale.Telltaleid,st_telltale.color,st_telltale.glowLevel,st_telltale.onOff);
return 0;
}
//Datalink Layer.h
#define MAXFRAMELEN 20
#define MAXPOSSOPTFIELDS 50
#define RESERVED 0
#define COMPIDNA 0
#define NA 1
#define SPEEDLENGTH 1
#define RPMLENGTH 1
#define FUELLENGTH 1
#define TELLTALELENGTH 3
struct telltalelib
{
int Telltaleid;
int color;
int glowLevel;
int onOff;
};
int __stdcall DataLink_TellTale_Encode (struct telltalelib *st_telltale, char* rtnFramePassedPtr);
enum priority{ LOW=1 , NORMAL, HIGH, CRITICAL,FATAL };
enum frameType{ REQUEST=1, RESPONSE, ERROR, STREAM, NOTIFICATION, STATEINFORMATIONCHANGE, PERIODIC };
enum domain{MODULEPOWER=1,MODULESTATEMACHINE,VEHICLEOVERALL,ENGINE,TRANSMISSION,BRAKE,SAFETYSYSTEMS,LOCK,DRIVER,AC,DOOR,TPMS,LIGHTS,WINDOW, ANTENNA,SEATS,VEHICLEATTITUDE,WIPER,ORVM,DISPLAY,VEHICLECONSTANTS,TELEPHONY,INFOTAINMENT,FOTA };
enum Parameter{ SPEED=2 ,RPM ,FUEL, ODO ,TEMPERATURE, PRESSURE, HUMIDITY,BATTERYLEVEL, ONOFF , GLOWLEVEL, COLOR, OPENPOSITION, RAMPSTYLE, CMDSHUTDOWN, CMDREBOOT, NOTIFICATIONTEXT, CURRENT, DRIVEMODE, GEARLEVEL, MASS, VOLUME, DATAPACKET ,SLIDELEVEL ,RECLINELEVEL, DRIVERID, DRIVINGSCORE, MINLEVELSETTING, MAXLEVELSETTING, TIME, ROLL, PITCH, YAW,MODE};
enum Unit{ RAW=2, SECONDS, CM ,M ,KM, MILE, KMPERHOUR, MILESPERHOUR, X100RPM, X1000RPM, PERCENTAGE, RGB, PSI, BAR, KPA, DEGCELCIUS, DEGFARANHEIT ,FULLYOPENORFULYLCLOSED, ENUMERATEDCODES ,ENUMERATEDFIELDID, V ,MA, A, DEGREE , KG ,CM3, CC, PLAINTEXT ,DB ,EPOCHTIME ,OKNOTOK };
enum Component{TYREID=2 ,ACVENTID,DOORID, WINDOWID, LIGHTID, ANTENNAID, TELLTALEID, ACTUATORID,GENERICDATAFIELDID ,DATAFORMATID, WIPERID } ;
VB Code:
Private Type struct_telltalelib
Telltaleid As Integer
color As Integer
glowLevel As Integer
onOff As Integer
End Type
Private Declare Function DataLink_TellTale_Encode Lib "C:\MinGW\bin\DataLinkLayer.dll" (ByRef st_telltale As struct_telltalelib, ByRef rtnFramePassedPtr As String) As Integer
Dim vb_telltale As struct_telltalelib
Dim str_data As String * 20
Dim s2 As String
Dim stringToBeTrasmitted As String
Private Sub Check1_Click()
vb_telltale.Telltaleid = 8
vb_telltale.color = -1
vb_telltale.glowLevel = -2
vb_telltale.onOff = 1
dummy = DataLink_TellTale_Encode(str(vb_telltale), str_data)
stringToBeTrasmitted = Trim(Mid(Replace(str_data, Chr(0), " "), 1, InStr(str_data, Chr(0))))
Timer1.Enabled = True
End Sub
Private Sub Form_Load()
UART1.PortOpen = True
End Sub
Private Sub Timer1_Timer()
Timer1.Enabled = False
UART1.Output = stringToBeTrasmitted
End Sub
I'm getting error message like only user defined in public object modules can be corrected to or from varient or passed to late -bound functions.
I see the following problems:
The struct should be passed as vb_telltale rather than str(vb_telltale).
The string should be declared as ByVal.
The string buffer is likely too small and will be overrun.
The error is telling you the VB Type variable must be declared as Public in a BAS module. Also, in C an int is equivalent to a VB6 Long, not an Integer, so you'll likely have size and alignment mismatches. There may be other issues but those stand out.

How can I get the current filename in a FreeBasic DLL?

How can make a FreeBASIC DLL find its own filename and path?
I've tried this so far: (ran it with rundll32 filename,DllMain)
Code:
#include "windows.bi"
Extern "Windows-MS"
Sub DllMain() Export
dim This as String
This = dir(command$(0))
MessageBox( null, "Hello World", This, MB_OK )
End Sub
End Extern
. . . but it doesn't work.
When I compile it as an EXE though, it works fine.
Any suggestions? Thanks!
I tried to adapt the approach of this C++ snippet to FreeBASIC. At first glance, it seems to work. But: Snippet provided "as is", without warranty of any kind. Use at your own risk.
#include "windows.bi"
Const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = &H2
Const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = &H4
Extern "Windows-MS"
Function getMyPath() As String
' See https://stackoverflow.com/a/6924332/
Dim path As ZString * 255
Dim hm As HMODULE = NULL
If GetModuleHandleEx( _
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS Or _
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, _
Cast(LPCSTR, #getMyPath), _
#hm _
) Then
GetModuleFileName( hm, path, SizeOf( path ) )
End If
Return path
End Function
Sub DllMain() Export
dim dllPath as String
dllPath = getMyPath()
MessageBox( null, dllPath, "Hello World", MB_OK )
End Sub
End Extern

VBA - generating unique numbers in code

Is there any way in which I can generate a unique number in code ?
I had an idea of using system time for that, but eventually could not implement it.
You can use the Now() then format the output to a number:
Sub unique()
Dim t As Date
t = Now()
Range("A1").NumberFormat = "#"
Range("A1") = CStr(Format(t, "yyyymmddhhMMss"))
End Sub
This would be unique.
As #Vasily pointed out, without formatting the cell as string and placing the number as a sting the value gets truncated to scientific notation.
especially for such cases the GUID (Global Unique IDentifyer) was invented. It may be a little bit oversized ... but just that you have seen it:
Option Explicit
Public Type TYP_GUID
bytes(15) As Byte
End Type
Public Declare Function CoCreateGuid Lib "OLE32.dll" _
(guid As TYP_GUID) As Long
Public Function newGUID() As TYP_GUID
Dim uGUID As TYP_GUID
CoCreateGuid uGUID
newGUID = uGUID
End Function
whenever you call newGUID() you will become a value that should be really unique in world. You can try and call it as often as you want ... you will never get the same value a second time.
it's also possible to convert such GUID's to string:
Option Explicit
Public Type TYP_GUID
bytes(15) As Byte
End Type
Public Declare Function CoCreateGuid Lib "OLE32.dll" _
(guid As TYP_GUID) As Long
Public Declare Function StringFromGUID2 Lib "OLE32.dll" _
(guid As TYP_GUID, _
ByVal lpszString As String, _
ByVal iMax As Long) As Long
Public Function newGUID() As TYP_GUID
Dim uGUID As TYP_GUID
CoCreateGuid uGUID
newGUID = uGUID
End Function
Public Function newGUID_String() As String
Dim sBuffer As String
Dim lResult As Long
sBuffer = VBA.Space(78)
lResult = StringFromGUID2(newGUID, sBuffer, Len(sBuffer))
newGUID_String = Left$(StrConv(sBuffer, vbFromUnicode), lResult - 1)
End Function