I have compiled a DLL from a C++ Library I have written according to this tutorial.
The dumpbin for the dll is as follows:
Section contains the following exports for HelloDLL.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
6 number of functions
6 number of names
ordinal hint RVA name
1 0 00011032 ?Add#Functions#MathLibrary##SANNN#Z = #ILT+45(?Add#Functions#MathLibrary##SANNN#Z)
2 1 00011037 ?AddMultiply#Functions#MathLibrary##SANNN#Z = #ILT+50(?AddMultiply#Functions#MathLibrary##SANNN#Z)
3 2 000112EE ?Multiply#Functions#MathLibrary##SANNN#Z = #ILT+745(?Multiply#Functions#MathLibrary##SANNN#Z)
4 3 000110F5 Add = #ILT+240(_Add)
5 4 00011073 AddMultiply = #ILT+110(_AddMultiply)
6 5 0001105F Multiply = #ILT+90(_Multiply)
Now I want to use the Functions in an Excel-VBA Project like this:
Declare Function LIB_AddMultiply Lib "C:\Users\xxxx\source\repos\HelloDLL\Debug\HelloDLL.dll" Alias "AddMultiply" (ByVal a As Double, ByVal b As Double) As Double
Public Sub test()
Dim a As Double
Dim b As Double
a = 3
b = 4
Dim c As Double
c = LIB_AddMultiply(a, b)
MsgBox ("hi " + c)
End Sub
But whenever I want to run the test() I get a Bad DLL Calling convention; Error 49.
I already looked at the following (and some other) resources, but couldn't resolve my problem:
Runtime Error 49, Bad DLL calling convention
Error 49
Declare Statement MSDN
Do you have any advice?
Thanks a lot...
UPDATE:
This is the code for the header file:
#pragma once
#ifdef MATHLIBRARY_EXPORTS
#define MATHLIBRARY_API __declspec(dllexport)
#else
#define MATHLIBRARY_API __declspec(dllexport)
#endif
namespace MathLibrary
{
class Functions
{
public:
static MATHLIBRARY_API double Add(double a, double b);
//[...]
};
extern "C" MATHLIBRARY_API double Add(double a, double b)
{
return MathLibrary::Functions::Add(a, b);
}
//[...]
}
Thank you Hans Passant for your help;
I have changed the project properties to the calling convention;
then did another dumpbin with the result of my Functions being named as
_Add#16
and then just changed the Alias in the VBA code...
Related
This is an alternative approach to this question here: Export Haskell lib as DLL
I am working with GHC version 8.6.1 [latest] and am following the documentation for compiling a DLL from a haskell library for the subsequent use in VBA.
My files look like this:
Adder.hs:
{-# LANGUAGE ForeignFunctionInterface #-}
module Adder where
adder :: Int -> Int -> IO Int
adder x y = return (x+y)
foreign export ccall adder :: Int -> Int -> IO Int
StartEnd.c:
#include <Rts.h>
void HsStart() {
int argc = 1;
char* argv[] = {"ghcDll", NULL};
char** args = argv;
hs_init(&argc, &args);
}
void HsEnd() {
hs_exit();
}
I copied the folder ghc-8.6.1\lib\include to the build location and copied the two files into that folder (because I cannot figure out how to pass the -I[PATH] parameter correctly).
Running these compilation steps, I get:
ghc -c Adder.hs
--> no error
ghc -c StartEnd.c
--> no error
ghc -shared -no-hs-main -o Adder.dll Adder.o Adder_stub.h StartEnd.o
--> no error
and the files
Adder.dll
Adder.dll.a
Adder.hi
Adder.o
Adder_stub.h
startEnd.o
I tried using the third compilation command like it is in the documentation ghc -shared -o Adder.dll Adder.o Adder_stub.h StartEnd.o and with the parameter -no-hs-main just to be sure.
My VBA script looks like this:
Private Declare Function Adder Lib "PATH\TO\Adder.dll" Alias "adder" _
(ByVal x As Long, ByVal y As Long) As Long
Private Declare Sub HsStart Lib "PATH\TO\Adder.dll" ()
Private Declare Sub HsEnd Lib "PATH\TO\Adder.dll" ()
Public Sub test()
HsStart
MsgBox "12 + 5 = " & Adder(12, 5)
HsEnd
End Sub
However, whenever I try to run the Sub test, I get the error message:
Run-Time error '48'
File not found:
PATH\TO\Adder.dll
Can you please tell me where I made a mistake and how I can fix it?
If it helps, the output of dumpbin /EXPORTS Adder.dll is this:
Microsoft (R) COFF/PE Dumper Version 14.00.24234.1
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file Adder.dll
File Type: DLL
Section contains the following exports for Adder.dll
00000000 characteristics
5BBD9533 time date stamp Wed Oct 10 07:59:15 2018
0.00 version
1 ordinal base
29737 number of functions
29737 number of names
ordinal hint RVA name
1 0 004E0F18 ALLOC_BH_adm
...
75 4A 00001681 HsEnd
76 4B 00001640 HsStart
...
472 1D7 00001540 adder
...
My Path:
C:\Users\scfa\AppData\Local\Programs\MiKTeX 2.9\miktex\bin\x64\;C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin;C:\Users\scfa\Documents\myProgramms\dev_msys2\mingw64\bin;C:\Users\scfa\Documents\myProgramms\dev_msys2\mingw32\bin;C:\Users\scfa\Documents\myProgramms\ghc-8.6.1\bin;C:\Users\scfa\Documents\myProgramms\ghc-8.6.1\mingw\bin
I have the following code in a .dll:
namespace MyNamespace
{
extern "C" __declspec(dllexport) int __stdcall GetOptionID(unsigned long num)
{
return 0;
}
}
This is compiled on Visual C++ 2010, so I also have a .def file containing GetOptionID. I can see that the function is exported, and mangled as _GetOptionID#4, using dumpbin /exports:
File Type: DLL
Section contains the following exports for MyLibrary.dll
00000000 characteristics
53D269CB time date stamp Fri Jul 25 15:29:31 2014
0.00 version
1 ordinal base
13 number of functions
13 number of names
ordinal hint RVA name
1 0 0006F030 CmdOne = _CmdOne#16
2 1 0006F510 CmdUnimpl = _CmdUnimpl#16
3 2 0006EBB0 DefineThing = _DefineThing#32
4 3 0006E0C0 GetOptionID = _GetOptionID#4
In a separate executable, I attempt to check for the presence of GetOptionID:
HINSTANCE hinst = LoadLibraryEx(file_name, NULL, DONT_RESOLVE_DLL_REFERENCES);
if(!hinst)
return FALSE;
FARPROC_IDI lp = (FARPROC_IDI) GetProcAddress(hinst, "_GetOptionID#4");
auto e = GetLastError();
Running through this code in the debugger, I can see that:
LoadLibraryEx succeeds - I have a valid-looking hinst
GetProcAddress fails - lp is 0x00000000
GetLastError returns 127
I can see the function has been exported, and I can see its name matches the entry point I'm looking for. How come GetProcAddress is failing?
Ah, solved it myself. Defining the function in the .def file causes its name to be completely unmangled, meaning the correct target for GetProcAddress was simply GetOptionID.
However, since I have other .dlls that undergo the same check and really are _GetOptionID#4, the actual solution was to remove GetOptionID from the .def file.
I came across a problem recently.
I have three files, A.h, B.cpp, C.cpp:
A.h
#ifndef __A_H__
#define __A_H__
int M()
{
return 1;
}
#endif // __A_H__
B.cpp
#include "A.h"
C.cpp
#include "A.h"
As I comile the three files by MSVC, there is a error:
C.obj : error LNK2005: "int __cdecl M(void)" (?M##YAHXZ) already defined in B.obj
It is easy understanding, as we know, B.obj has a symbol named "M", also C.obj has a "M".
Here the error comes.
However, if I change M method to a class which contain a method M like this below:
A.h
#ifndef __A_H__
#define __A_H__
class CA
{
public:
int M()
{
return 1;
}
};
#endif // __A_H__
there is no more errors!! Could somebody tell me what is happening?
If B.cpp and C.cpp include A.h, then both are compiled with your definition of M, so both object files will contain code for M. When the linker gathers all the functions, he sees that M is defined in two object files and does not know which one to use. Thus the linker raises an LNK2005.
If you put your function M into a class declaration, then the compiler marks/handles M as an inline function. This information is written into the object file. The linker sees that both object files contain a definition for an inline version of CA::M, so he assumes that both are equal and picks up randomly one of the two definitions.
If you had written
class CA {
public:
int M();
};
int CA::M()
{
return 1;
}
this would have caused the same problems (LNK2005) as your initial version, because then CA::M would not have been inline any more.
So as you might guess by now, there are two solutions for you. If you want M to be inlined, then change your code to
__inline int M()
{
return 1;
}
If you don't care about inlining, then please do it the standard way and put the function declaration into the header file:
extern int M();
And put the function definition into a cpp file (for A.h this would ideally be A.cpp):
int M()
{
return 1;
}
Please note that the extern is not really necessary in the header file.
Another user suggested that you write
static int M()
{
return 1;
}
I'd not recommend this. This would mean that the compiler puts M into both of your object files and marks M as being a function that is only visible in each object file itself. If the linker sees that a function in B.cpp calls M, it finds M in B.obj and in C.obj. Both have M marked as static, so the linker ignores M in C.obj and picks the M from B.obj. Vice versa if a function in C.cpp calls M, the linker picks the M from C.obj. You will end up with multiple definitions of M, all with the same implementation. This is a waste of space.
See http://faculty.cs.niu.edu/~mcmahon/CS241/c241man/node90.html how to do ifdef guards. You have to start with ifndef before the define.
Edit: Ah no, while your guard is wrong that's not the issue. Put static in front of your function to make it work. Classes are different because they define types.
I don't know what's under the hood, but if you don't need a class I guess that the compiler will automatically add the "extern" key to your functions, so you'll get the error including the header 2 times.
You can add the static keyword to M() method so you'll have only one copy of that function in memory and no errors at compile time.
By the way: I see you have a #endif, but not a #ifdef or #ifndef, is it a copy/paste error?
I am trying to integrate an existing C DLL (unmanaged obviously), that implements fuzzy matching, into SQL Server as a user defined function (UDF). The UDF is implemented with a CLR VB project. I have used this C code for nearly 20 years to do string matching on text files without a hitch. It has been compiled on about every platform under the sun and has never crashed or given erroneous results. Ever. Until now.
The usage of this UDF in a SQL SELECT statement looks something like this:
SELECT Field FROM Table WHERE xudf_fuzzy('doppler effect', Field) = 1;
xudf_fuzzy(Param1, Param2) = 1 is where the magic happens. Param1 is the clue word we are trying to match while Param2 is the field from the table to be tested against. If the match is successful within a certain number of errors, the UDF returns 1, if not it returns 0. So far so good.
Here is the CLR code that defines the fuzzy UDF and calls the C DLL:
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports System.Text
Imports Microsoft.SqlServer.Server
Imports System.Runtime.InteropServices
Partial Public Class fuzzy
<DllImport("C:\Users\Administrator\Desktop\fuzzy64.dll", _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function setClue(ByRef clue As String, ByVal misses As Integer) As Integer
End Function
<DllImport("C:\Users\Administrator\Desktop\fuzzy64.dll", _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function srchString(ByRef text1 As String) As Integer
End Function
<Microsoft.SqlServer.Server.SqlFunction()> Public Shared Function _
xudf_fuzzy(ByVal strSearchClue As SqlString, ByVal strStringtoSearch As SqlString) As Long
Dim intMiss As Integer = 0
Dim intRet As Integer
Static Dim sClue As String = ""
xudf_fuzzy = 0
' we only need to set the clue whenever it changes '
If (sClue <> strSearchClue.ToString) Then
sClue = strSearchClue.ToString
intMiss = (Len(sClue) \ 4) + 1
intRet = setClue(sClue, intMiss)
End If
' return the fuzzy match result (0 or 1) '
xudf_fuzzy = srchString(strStringtoSearch.ToString)
End Function
Here is the front end of the C code being called. STRCT is where all of the global storage resides.
fuzzy.h
typedef struct {
short int INVRT, AND, LOWER, COMPL, Misses;
long int num_of_matched;
int D_length;
unsigned long int endposition, D_endpos;
unsigned long int Init1, NOERRM;
unsigned long int Mask[SYMMAX];
unsigned long int Init[MaxError];
unsigned long int Bit[WORDSIZE+1];
unsigned char prevpat[MaxDelimit];
unsigned char _buffer[Max_record+Max_record+256];
unsigned char _myPatt[MAXPAT];
} SRCH_STRUCT;
fuzzy.c
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <wtypes.h>
#include <time.h>
#include "fuzzy.h"
// call exports
__declspec(dllexport) int CALLBACK setClue(char**, int*);
__declspec(dllexport) int CALLBACK srchString(char**);
SRCH_STRUCT STRCT = { 0 };
int cluePrep(unsigned char []);
int srchMin(unsigned char [], int);
BOOL APIENTRY 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;
}
int CALLBACK setClue(char **pattern, int *misses)
{
int i;
unsigned char *p;
// code to do initialization stuff, set flags etc. etc.
STRCT.Misses = (int)misses;
p = &(STRCT._myPatt[2]);
STRCT._myPatt[0] = '\n';
STRCT._myPatt[1] = SEPCHAR;
strcpy((char *)p, *pattern);
//blah blah
// end setup stuff
i = cluePrep(STRCT._myPatt);
return 0;
}
int CALLBACK srchString(char **textstr)
{
int res,i = Max_record;
unsigned char c;
char *textPtr = *textstr;
STRCT.matched = 0;
//clean out any non alphanumeric characters while we load the field to be tested
while ((c = *textPtr++)) if ( isalpha(c) || isdigit(c) || c == ' ' ) STRCT._buffer[i++] = c;
STRCT._buffer[i] = 0;
// do the search
res = srchMin(STRCT.pattern, STRCT.Misses);
if (res < 0) return res;
return STRCT.matched;
}
The runtime library it is linked against is: Multi-threaded DLL (/MD)
The calling convention is: __cdecl (/Gd)
This is where it gets weird. If I have a regular dot-net application (code not shown) that grabs the entire recordset from the test database and iterates through all of the records one at a time calling this DLL to invoke the fuzzy match, I get back the correct results every time.
If I use the CLR UDF application shown above against the test database using the SQL statement shown above while using only one thread (a single core VM) I get back correct results every time as well.
When this DLL is used in CLR UDF mode on a multi-core machine then some of the results are wrong. The results are always a little off, but not consistently.
292 records should match and do in the first two test cases.
In the CLR UDF multi-threaded case the results will come back with 273, 284, 298, 290 etc.
All of the storage in the C DLL is in character arrays. No memory allocs are being used. It is also my understanding that if SQL Server is using this CLR app in multi-threaded mode that the threads are all assigned their own data space.
Do I need to somehow "pin" the strings before I send them to the C DLL? I just can't figure out how to proceed.
Pinning is not your issue, your C code is not thread-safe. The C code uses a global variable STRCT. There is only one instance of that global variable that will be shared across all threads. Each thread will update STRCT variable with different values, which would cause incorrect result.
You will have to refactor the C code so that it does not rely on the shared state of a global variable.
You may be able to get it to work by declaring STRCT with __declspec(thread) which will use Thread Local Storage so each thread gets its own copy of the variable. However, that would be unique to each unmanaged thread and there is no guarantee that there is a one-to-one mapping between managed and un-managed threads.
The better solution would be to get rid of the shared state completely. You could do this by allocating a SRCH_STRUCT in setClue and return that pointer. Then each call to srchString would take that SRCH_STRUCT pointer as a parameter. The VB.Net code would only have to treat this struct as in IntPtr, it would not need to know anything about the definition of SRCH_STRUCT. Note you would also need to add a new function to the DLL to deallocate the allocated SRCH_STRUCT.
I'm working on a Word 2011 plugin in Mac OS. Currently, I need to write a code in VBA Macro to retrieve a String from another application (through Socket communication). So, basically in Windows, I can simply make a DLL which help me to do Socket communication with the other application and return the String value to VBA Macro.
However, in Mac, I'm able to build a .dylib (in C) and using VBA to communicate with the dylib. However, I'm having a trouble with the return String. My simple C code is something like:
char * tcpconnect(char* arguments)
{}
First, it always contains Chr(0) characters. Secondly, I suspected that this C function will not be able to handle Unicode String.
Do you guys have any experiences or have any similar example of this?
Thanks,
David
My original post was an attempt to imitate SysAllocStringByteLen() using malloc(), but this will fail when Excel tries to free the returned memory. Using Excel to allocate the memory fixes that issue, and is less code as well, e.g.:
in test.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define LPCSTR const char *
#define LPSTR char *
#define __declspec(dllexport)
#define WINAPI
char *saved_string = NULL;
int32_t saved_len = -1;
#define _CLEANUP if(saved_string) free(saved_string)
__attribute__((destructor))
static void finalizer(void) {
_CLEANUP;
}
int32_t __declspec(dllexport) WINAPI get_saved_string(LPSTR pszString, int cSize) {
int32_t old_saved_len = saved_len;
if(saved_len > 0 && cSize >= saved_len)
strncpy(pszString, saved_string, saved_len);
if(saved_string) {
free(saved_string);
saved_string = NULL;
saved_len = -1;
}
return old_saved_len;
}
int32_t __declspec(dllexport) WINAPI myfunc(LPCSTR *pszString) {
int len = (pszString && *pszString ? strlen(*pszString) : 0);
saved_string = malloc(len + 5);
saved_len = len + 5;
sprintf(saved_string, "%s%.*s", "abc:", len, *pszString);
return saved_len;
}
Compile the above with
gcc -g -arch i386 -shared -o test.dylib test.c
Then, in a new VBA module, use the below and run "test", which will prepend "abc:" to the string "hi there" and output the result the debug window:
Public Declare Function myfunc Lib "<colon-separated-path>:test.dylib" (s As String) As Long
Public Declare Function get_saved_string Lib "<colon-separated-path>:test.dylib" (ByVal s As String, ByVal csize As Long) As Long
Option Explicit
Public Function getDLLString(string_size As Long) As String
Dim s As String
If string_size > 0 Then
s = Space$(string_size + 1)
get_saved_string s, string_size + 1
End If
getDLLString = s
End Function
Public Sub test()
Debug.Print getDLLString(myfunc("hi there"))
End Sub