Solved

C/C++ DLLs for use with VB in 32-bits

Posted on 1997-08-27
9
432 Views
Last Modified: 2013-12-03
I have a bunch of DLLs with exported functions (basically it's an API) for use by other people using, for example, VB, or C/C++, or PowerBuilder, etc.  The DLLs are written in C/C++, and the exported functions are just C functions.  So, for example, I might have some functions like these:

   WORD  MyAPIInit()
   short MyAPIDoSomething(LPCSTR buff, short bufflen)
   BOOL  MyAPIShutDown(WORD handle)

etc., exported from the DLL.

In 16-bits, I had these declared using the WINAPI and EXPORT macros, which are equivalent to __far __pascal and __export respectively.  When I built my DLL, I then got these functions exported, and the __pascal bit meant that

   (a) they were callable from VB, etc., with left-to-right parameter passing, and

   (b) the names of the exported functions got made into uppercase, since the pascal calling convention is case-insensitive.

Note that I also had extern "C" in front of the declarations and definitions, and did *not* put any of the functions in the def file, since the EXPORT macro has the same effect and saves me updating both the functions and the def file all the time.

All worked fine up until I moved to 32-bits.  Now I have a bunch of 32-bit DLLs, and so instead of my previous:

   extern "C" BOOL WINAPI EXPORT MyAPIShutDown(WORD handle)

I now have

   extern "C" EXPORT BOOL WINAPI MyAPIShutDown(WORD handle)

because 32-bits needs the EXPORT in a different place.

However, WINAPI now means, not __pascal, but __stdcall.  And from reading the VB help (what little there is on this topic) I find that VB requires DLL functions to use the __stdcall calling convention.

Unfortunately, when I build my DLL, all the exported functions get name decorated, with a preceding underscore, and an "@n" tacked on the end, where n is the number of bytes the parameters take up.  Standard Windows DLLs (which can be called from VB and so presumably use the __stdcall convention) are not so decorated, so how do I get rid of this decoration so that people can use my 32-bit DLL as they did the 16-bit one?  On the odd occasions I have got the functions to work (by using the Alias keyword in VB), I get "Bad DLL Calling Convention" followed by a VB crash.  It is possible that I have not specified the function correctly in VB for this, or it is possible that my calling convention is incorrect.

What this boils down to is how should I declare and define an exported function in a DLL so that it can be used by VB and C, and is exported with a particular name (rather than a decorated one) in 32-bits compared to 16-bits.

Any help will be much appreciated.
0
Comment
Question by:rpb
  • 5
  • 3
9 Comments
 

Expert Comment

by:kfe
ID: 1404899
Hi,

I spent ages messing around with this problem, while I was writing a pair of DLLs (one 16 bit and one 32 bit) that allowed anyone calling into the 16 bit version to be 'thunked' across to the 32 bit version, all using a common code platform. All great fun, but this name mangling business had me reaaly confused for a while!

I ended up producing the following macro definitions that seem to work for VB interfaces:

// 16/32 bit macros
#ifdef _WIN32
      #define DLLEXPORT(type) type APIENTRY
#else
      #define DLLEXPORT(type) extern "C" type __export WINAPI
#endif

and then using the following prototypes in my code:

// Function prototypes
DLLEXPORT(void) Function (void);


Hope this works for you too!

Regards

kfe
0
 

Author Comment

by:rpb
ID: 1404900
Thanks, I'll have an investigate along those lines, although I just looked up APIENTRY, and it is just WINAPI (which is just __stdcall), so I'm not quite sure how yours differs from what I have at the moment, apart from the export bit.  What happened to your __declspec(dll_export) in your 32-bit version?  Don't I need one of those?

In fact, for anyone else who is interested, I currently use my own macro to export functions, but it is slightly different.  It is along the lines of:

#ifdef _WIN32
      #ifdef EXPORT
            #undef EXPORT
      #endif
      #define EXPORT __declspec( dllexport )
      #define HUGE
      #define MYAPI(ret, decl) EXPORT ret WINAPI decl
#else
      #ifdef EXPORT
            #undef EXPORT
      #endif
      #define EXPORT __export
      #define HUGE _huge
      #define MYAPI(ret, decl) ret WINAPI EXPORT decl
#endif

and I then declare my functions as follows:

// header.h
MYAPI( void, Function(void) );

and

// source.cpp
MYAPI( void, Function(void) )
{
      // do stuff here...
}

The good thing about these macros (and kfe's) is that you can just change the macro and rebuild if you want to change your strategy.  This is handy when you have several hundred API functions to manage...

0
 

Author Comment

by:rpb
ID: 1404901
Hmmm - all my tabs disappeared from the above - what a mess!
I'll have to use spaces in future...
0
 

Expert Comment

by:kfe
ID: 1404902
Hi,

It was a while ago, but I remember playing around with __declspec. I honestly can't remember what steps I went through to get to my final result! (So many of these macros end up being defined as the same thing - it was all SO confusing!) I'm  afraid I normally adopt the approach of "if it aint broke, don't fix it", and since this seemed to do everything I needed I stopped 'fixing' it! I can only assume, therefore, that __declspec is not needed.

My main source of reference was a file "vb4dll.txt" that was distributed with VB4. It is a mine of useful information about how to develop DLLs that can be interfaced by VB. I can't find it anywhere in the VB5 distribution - MS probably thought it was TOO useful :-)

Regards

kfe

0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 

Author Comment

by:rpb
ID: 1404903
<Oops - looks like my comment crossed yours in the post...>

OK, I have two problems with your solution so far.  First, since your macro doesn't export the function in Win32, it does not appear in the exported function list for the dll, which is not much use 8-).  Second, if I add __declspec(dllexport) to the macro so the function does get exported, to give...

   #ifdef _WIN32
      #define DLLEXPORT(type) __declspec( dllexport ) type APIENTRY
   #else
      #define DLLEXPORT(type) extern "C" type __export WINAPI
   #endif

then the exported function you gave an example for (renamed to "Func" because "Function" is a reserved word in VB) is exported as this (checked using the QuickView utility):

   ?Func@@YGXXZ

If I then add on an extern "C" to this, I can get rid of the C++-style name decoration to get this:

   _Func@0

If I remove the APIENTRY bit (i.e. the WINAPI, or __stdcall, bit), and also leave off the extern "C" bit, I get

   ?Func@@YAXXZ

but if I then add extern "C" back (so I now have 'extern "C" __declspec(dllexport) type' for your macro) I get what I want.  Func is exported as:

   Func

However, this is now not using __stdcall, and so I don't think a function with parameters will get called correctly from VB...

Note that in all the above cases except the last, trying to call the function from VB using:

   Private Declare Sub Func Lib "temp" ()

then

   Call Func

returns: "Run time error '453':  Can't find DLL entry point Func in temp", so the name has clearly been mangled sufficiently that you can't use it in VB easily

However, I can re-declare the function like this in VB:

   Private Declare Sub Func Lib "temp" Alias "_Func@0" ()

it will then call the function if that is how the name is decorated (similarly for the other types of decoration, without extern "C", etc., shown above).

Thus I still do not know how to declare and define a function in 32-bit code so that it uses __stdcall calling convention (if that is required for VB and other languages) and yet leaves the name how I want it (undecorated and so easily used in VB, etc.)...

------------------------------------
Let's use a specific example:

Say I want to export the following test function:

    long Test(long l, short s)
    {
        return l + s;
    }

I have created a test DLL containing two versions of this function, using the following syntax:

    // header file
    extern "C" __declspec( dllexport ) long WINAPI TestStdCall(long l, short s);
    extern "C" __declspec( dllexport ) long TestCDecl(long l, short s);

    // source file
    extern "C" __declspec( dllexport ) long WINAPI TestStdCall(long l, short s) {return l + s;}
    extern "C" __declspec( dllexport ) long TestCDecl(long l, short s) {return l + s;}

When I build this DLL, and look at the exports (using QuickView), I find I have these two:

    _TestStdCall@8
    TestCDecl

So now I write some VB in VB5 as follows:

    Private Declare Function TestStdCall Lib "temp" Alias "_TestStdCall@8" (ByVal l As Long, ByVal s As Integer) As Long
    Private Declare Function TestCDecl Lib "temp" (ByVal l As Long, ByVal s As Integer) As Long
    -----
    Dim s As Integer
    Dim l As Long
   
    l = 1000000
    s = 100
    l = TestStdCall(l, s)
   
    l = 1000000
    l = TestCDecl(l, s)

When I run this, the first one works, and returns 1000100, as expected.  The second call returns an error - "Run-time error '49':  Bad DLL calling convention".  I therefore assume that __stdcall is what we want, but the name is horribly mangled.  I am wondering if I have to maintain a def file in order to export names in the way I want, which is a shame since I would like to be able to incorporate the logic into my macro.
0
 

Author Comment

by:rpb
ID: 1404904
Hi kfe,

Sorry for rejecting your answer.  I couldn't get this to work at all.  So far I'm back to my previous situation of being able to call the functions from VB (because it is using __stdcall disguised as WINAPI or APIENTRY), but only if I supply an alias giving the decorated name.  This is a pain, as I have a lot of functions to export, and each one will need users of my API to modify all of their declarations with these ugly mangled names.

How do the Windows dlls manage to get stdcall exports with undecorated names?  Maybe I need to investigate the .def file approach...
0
 

Accepted Solution

by:
kfe earned 50 total points
ID: 1404905
Hello again!

I've hooked out my old VB4 installation and rooted around for the vb4dll.txt file that I referred to earlier. Here is what I found...

--LOTS OF STUFF DELETED--
Normally  when  compiling  32-bit  DLLs  using a Microsoft Visual C++
compiler  the  _declspec  keyword is used with the dllexport modifier
attribute to enable you to export functions, data, and objects from a
DLL. This  attribute  explicitly  defines the DLL's  interface to its
client, which  can be  an  executable  file or another DLL. Declaring
functions  as dllexport  eliminates  the need for a module-definition
(.DEF)  file, at least  with respect to the specification of exported
functions. dllexport  replaces the  _export keyword used earlier with
16-bit DLLs. However, when writing 32-bit DLLs that need to be called
from the  32-bit version of VB4, you need to export your function via
the DEF file, by  specifying  all the  function names with an EXPORTS
statement. This is  because, the  use of  dllexport  mangles function
names,  which is  the very  nature of a C++ compiler, but VB does not
understand  mangled  names. Even if you use a file with an  extension
of .C for your source code (so that the standard C compiler is used),
the same thing applies, i.e the use of dllexport will mangle function
names. In this case also you will have to using a .DEF file.

Another thing to keep in mind is that VB4 (32-bit) expects the called
function to clean up the stack before it returns. Hence  you must use
the _stdcall calling convention.

When compiling 16-bit DLLs, it not necessary to specify  the names of
the  functions  in an  EXPORTS  statement in the DEF file. The 16-bit
version  of VB4  uses the Pascal calling convention and functions are
exported by using the _export keyword.  If a C++ compiler is used for
compiling  the 16-bit DLL, it is also necessary to specify extern "C"
in front  of the function definition, to explicitly tell the compiler
not to mangle function names.
--LOTS OF STUFF DELETED--

So it looks like you HAVE to use a def file for 32 bit DLL - what a pain! (I use them anyway, more out of habit than for any other reason!)

Have earned my points now?!

Regards

kfe
0
 
LVL 4

Expert Comment

by:davmarc
ID: 1404906
kfe is correct - you simply CANNOT avoid setting aliases either in the .def file or in the VB declaration.

Davide Marcato.
0
 

Author Comment

by:rpb
ID: 1404907
Thanks,

As it turns out, I discovered most of that myself yesterday, but for sheer effort I shall hand over the points 8-).

It was interesting to see the excerpt from that help file, though.

I managed to get the functions called, as I said earlier, using the Alias keyword in VB.  Thus, my function TestStdCall, above, could be called from VB with a declaration:

   Private Declare Function TestStdCall Lib "temp" Alias "_TestStdCall@8" (ByVal l As Long, ByVal s As Integer) As Long

giving the required mangled names.  I then discovered that you can force the name to get unmangled by adding, as you stated,

   EXPORTS
      TestStdCall

to my def file.  Since 32-bit projects don't need a def file, I didn't have one to start off with.

I then discovered why my actual API function was failing, and this may be of interest to those following this saga...

I had a function roughly as follows (with names changed to protect the innocent):

   WORD MyFunc(WORD, DWORD, node)

where node was a type declared thus:

   struct node
   {
      short a;
      short b;
      short c;
   };

Now, in VB3, you can call this just fine using something along the lines of:

   Private Declare Function MyFunc Lib "mylib" (ByVal w As Integer, ByVal dw As Long, ByVal c As Integer, ByVal b As Integer, ByVal a As Integer) As Integer

Note that a, b and c are reversed, which is because the _pascal calling convention for 16-bits pops parameters onto the stack left to right, and Intel is little-endian (or big-endian - I can never remember, but the lower order byte is first - little-endian I guess).  Note also that I have 1 byte struct member packing in my code.

Now, on moving to 32-bits, this still gave me an Invalid DLL calling convention, after the fn returned, and the function received its parameters jumbled.

It turns out that in 32-bits, all parameters are packed onto the stack on 32-bit boundaries (maybe this is obvious to some), and so I can no longer use my struct in C *and* expand it out in VB.

What I have ended up with is modifying my C API functions as follows:

   WORD MyFunc(WORD, DWORD, short node_c, short node_b, short node_a)

so that the above VB declaration still works.  A pain, but then there you go.

Thanks again for your time and help.
0

Featured Post

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

This tutorial is about how to put some of your C++ program's functionality into a standard DLL, and how to make working with the EXE and the DLL simple and seamless.   We'll be using Microsoft Visual Studio 2008 and we will cut out the noise; that i…
Entering time in Microsoft Access can be difficult. An input mask often bothers users more than helping them and won't catch all typing errors. This article shows how to create a textbox for 24-hour time input with full validation politely catching …
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

8 Experts available now in Live!

Get 1:1 Help Now