Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

What's wrong wth this code, part 4

What's wrong wth this code, part 4

  • Comments 17

Ok, time for another “what’s wrong with this code” problem.

This time, I’m writing a DLL.  Nothing complicated, just a plain old DLL.  As is expected, I publish a header file for my api:

// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the NIFTY_API_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// NIFTY_API_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef NIFTY_API_EXPORTS
#define NIFTY_API_API __declspec(dllexport)
#else
#define NIFTY_API_API __declspec(dllimport)
#endif

NIFTY_API_API int MyNiftyAPI(void);

You’ll notice that this header is almost identical to the header file that Visual Studio produces when you ask it to make a DLL.  Even so, there’s a bug in this header file.

Your challenge is to figure out what the bug is.  It’s subtle, this time, but important (although Raymond and I have touched on it before).  Btw, the fact that it uses the non standard __declspec is NOT the bug.  That’s syntactic sugar that could be easily removed without removing the error.

As usual, answers and kudos tomorrow.

 

  • My *guess* is that you don't specify the calling convention, which means the caller and callee could easily use different ones, and cause some pretty nasty bugs.
  • The calling convention is missing and the default calling convention in Visual C++ is __cdecl
  • I'd agree with Grant, and add that if you plan to use the code from both C and C++ you should suppress C++ name mangling using 'extern "C"', if compiled with a C++ compiler, e.g.:

    #ifdef __cplusplus
    extern "C" {
    #endif

    NIFTY_API_API int MyNiftyApi(void);

    #ifdef __cplusplus
    }
    #endif
  • I'm no Windows guy, but I'll put a cautious eye to the generic int being subject to the signedness and size of the day.
  • My guess is as good as that of Boris. I'll second that the error is in the calling convention, which must be __stdcall.
  • How 'bout this: Using #include "apiheader.h" in >1 .CPP files in the API implementation project (where NIFTY_API_EXPORTS is defined) will result in multiply-defined-symbol (MyNiftyApi) linker errors.
  • dh: The compiler collapses identical declarations for just that reason.
  • I agree that it's a calling convention problem. You're relying on both the dll and it's users to have the same default calling convention. Explicitly specifying it would solve this.

    I don't agree with Sathyaish's comment that it must be __stdcall though, it could be any calling convention. As long as you declare it in the header, both the dll and it's caller will use the same convention, so there won't be any problem.

    Using a common header file also makes name mangling a non-issue. Since the declaration is the same in both the dll and it's caller, the symbols will match, mangled or not.
  • It seems to me that the header is viable under either stdcall or cdecl, but since the ambient option chosen in the client project can/will be different than the option chosen to compile the DLL, the calling convention should be specified somehow in the header. There could be a pair of pragmas to select a calling convention for all the functions in the header, or the macro expansions could specify the calling convention.

    In any given case, leaving out 'extern "c"' from an could be a feature. For example the DLL could be C++ only, as perhaps:

    NIFTY_API_API int MyNiftyStringParsage(const std::string& formal);
  • Ian, you're 80% right - the name mangling of C vs C++ will strike unless you use Mike Dimmicks suggestion.
  • It's rooted in and leads to closed source proprietary software?
  • Kevin, I did mention that __declspec is NOT the bug. And as long as the x86 platform supports multiple calling conventions, the problem remains.

    Even if you were using GCC, you'd STILL have the same problem, because C++ declarations are STILL decorated. GCC does only have one calling convention, but it's cdecl.

    And the cdecl calling convention is possibly the worst of the x86 calling conventions (pascal might be worse). The biggest problem with cdecl is that the caller has to clean up the stack. When the NT kernel was changed from being cdecl to stdcall, we literally shrunk the size of the kernel binary by more than 10%. Code compiled with stdcall also executes faster than cdecl because it executes fewer instructions (this may no longer be as significant but it can add up).
  • Oh, and if you're using GCC, here's the documentation where GCC describes it's calling conventions:
    http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#Function%20Attributes

    It even supports __declspec(dllexport) and stdcall.

  • Larry, yes I see what you mean. I guess I'm too used to using the same compiler for C and C++, and forgot that a proper C compiler don't know anything about name mangling... Silly me :)
  • I should clarify my comment: if the code is implemented in (or compiled as) C++, and you haven't included extern "C", the name will be mangled in the object file and hence in the DLL. If the header is consumed from C, the compiled object (in the using binary) will have a reference to the non-mangled name, and it won't link.

    The same is also true if implemented in C and consumed from C++. Only if both sides match (and share a name-mangling convention if both are C++ - GCC uses a different mangling convention to MSVC, IIRC, meaning that you can't link the program) will it work.

    I guess I was assuming that Larry wanted his DLL to have maximum reach, i.e. to C, C++ (of all vendor flavours), Visual Basic 6 and .NET languages. VB6 and .NET can use different names for linking from the names used in the program, e.g. the DllImportAttribute's EntryPoint field.

    The difference between C++ compilers causes tricky problems for platform vendors. Take, for example, GDI+. The published API uses a number of classes with inheritance. However, if you poke around in gdiplus.h, you'll quickly see that the classes are just thin wrappers over the unsupported GDI+ Flat API declared in gdiplusflat.h.
Page 1 of 2 (17 items) 12