Binary-Compatible C ++ Interfaces

Source: Internet
Author: User
Tags configuration settings win32 window
Document directory
  • Chad Austin, 2002.02.15

 

Http://chadaustin.me/cppinterface.html

 

Chad Austin, 2002.02.15 Updates2003.02.21

Clarified some of my comments thanks to feedback and suggestions from
Razvan surdulescu. For actual com compatibility, I added _ stdcall
The method declarations as well. (apparently, if you follow
Guidelines of this article, it's easy to add bindings from your DLL
Delphi and VB !)

2002.04.03

Somebody (unfortunately, I lost his e-mail address and name before I
Cocould write it down. If you're re reading this or you know who sent it,
Please drop me a line) sent me another Guideline. The gist of it is
That you shouldn't use overloaded methods in your interfaces.
Different compilers will order them in the vtable differently.

2002.03.27

Ben Scott (bscott at iastate dot EDU) submitted some excellent classes
That simplify usage of the concepts presented in this article. Simply
Derive your interface classes from dllinterface and your
Implementations from dllimpl! Download the source file here
.

Overview

This article explains how to create C ++ DLL APIs that will work within SS several
Compilers and configuration settings (release, debug, etc .).

Background

Many platforms have
Abi
For their preferred
Programming language. For example, BEOs's primary language is C ++, so the C ++
Compiler must be able to generate code that remains Binary compatible with
Operating System's c ++ system CILS (and classes, etc .).

The Windows API
And
Abi were defined for C, so c ++ compiler writers had free reign to implement
C ++ Abi however they felt. Eventually, however, Microsoft created
Object-oriented abi for Windows called com. To simplify com usage, they made
The vtables of their C ++ Abi match the vtables required in COM interface.
Since a Windows compiler that can't use Com is pretty limited, other Compiler
Vendors enforced the mapping between COM vtables and C ++ vtables.

There are several aspects to an ABI. This article only discusses the issues
With using C ++ in windows. Other platforms have different requirements.
(Fortunately, since most other platforms aren't as popular as windows, they
Have only one or two compilers, and thus there isn't much of a problem .)

Concepts
  • Abi
    -
    Application binary interface.
    The binary interface between systems. If a binary interface changes, both
    Sides of the interface (the user and the Implementation) must be recompiled.
  • API
    -
    Application program interface.
    The source interface between systems. If a source interface changes, code
    That uses that interface must be modified. API changes usually imply Abi
    Changes.
  • Interface
    -
    A class where every method is pure virtual, and thus has no inherent
    Implementation. An interface is merely a protocol for communication
    Objects.
  • Factory
    -
    Something that creates objects. In this article, we'll use a single global
    Function as our factory.
  • DLL Boundary
    -The line between code instantiated
    In a DLL and code in a calling process is called the DLL boundary.
    In some cases, code can be on both sides of the boundary: Consider
    An inline function in a header file that gets used in the DLL and
    The executable. The function is actually instantiated on both sides
    Of the boundary. Therefore, if the inline function has a static
    Variable, two variables will be created, one in the executable and
    One in the DLL, and which is used depends on whether the code in
    DLL or the executable is calling the function.
Initial Attempt

Let's say you want to create a portable indexing wing API and you want to stick
The implementation in a DLL. I'm going to create a class called Window
Which can represent a window in several different processing wing systems: Win32,
MFC, wxwindows, QT, GTK, Aqua, X11, swing (* gasp *), Etc... we'll walk through
Several attempts at creating an interface until it works extends SS different
Implementations, compilers, and compiler settings.

// Window.h

#include <string>

#ifdef WIN32
#ifdef EXPORTING
#define DLLIMPORT __declspec(dllexport)
#else
#define DLLIMPORT __declspec(dllimport)
#endif
#define CALL __stdcall
#else
#define DLLIMPORT
#define CALL
#endif

class DLLIMPORT Window {
public:
Window(std::string title);
~Window();

void setTitle(std::string title);
std::string getTitle();

// ...

private:
HWND m_window;
};

I'm not going to show the implementation, as I'm assuming you already know how
To do that. There is one glaring problem with this interface: it assumes
You're using the basic Win32 API. That is, it holds an hwnd as
Private member, which introduces a dependency between our window class
And the Win32 SDK. One possible solution is to use the pimpl idiom
Remove the class's private members from the class definition.
You can read more about that elsewhere
[1]
,
[2]
,
[3]
, And
[4]
.
Also, you cannot add new members to
Class without breaking binary compatibility, as the size of the class
Changes.

Perhaps the most important problem with this approach is
That the methods are non-Virtual. Thus, they are implemented
Specially named functions that take the 'This' pointer as their first
Argument. Unfortunately, I don't know of any two compilers that mangle
Method names in the same way. So don't think your DLL work with
Executable compiled with another compiler!

Attempt #2

For those of you experienced in Object Oriented Programming, you know that
Every class can be broken into two concepts: an interface and a factory.
A factory is a mechanic for creating objects, and an interface allows you
Communicate with them. The next version of window. H will separate these
Concepts. notice that you no longer need to export the class (you
Have to export the factory function though !), As it is
Abstract: All method callgo through the object's vtable, not
Through a direct linking to the DLL. Only the call to the factory
Function CILS directly into the DLL.

// Window.h

#include <string>

class Window {
public:
virtual ~Window() { }
virtual void setTitle(std::string title) = 0;
virtual std::string getTitle() = 0;
};

Window* DLLIMPORT CreateWindow(std::string title);

This is much better. The code that uses window objects doesn't care
What actual type the window object is, just that it implements
Window Interface. However, there is still a problem: Different
Compilers mangle Symbol names differently, so
CreateWindow
Function in DLLs generated by different
Compilers will have a different names. This means that if you compile
The specified wing DLL with Visual C ++ 6, you can't be able to use it in
Borland C ++, and vice versa. Fortunately, the C ++ standard lets us
Disable symbol mangling on specified names,extern
"C"

.

Some of you may have noticed another problem with this code.
Different compilers implement the Standard C ++ library differently.
In the less obvious case, some people replace their Compiler's
Implementation of the library with another (such as stlport
). Since you can't depend on STL
Objects being Binary compatible extends SS compilers, you cannot safely use them
In your DLL interfaces.

If a C ++ Abi is ever created for Windows, it will need to specify
Exactly how to interface with every class in the standard library,
I don't see this happening anytime soon.

The final problem here is a minor one. By convention, com methods and
DLL functions use the _ stdcall calling convention. We can fix this
With the call macro I defined above. (You'll want to rename it in
Your project .)

Revision 3
// Window.h

class Window {
public:
virtual ~Window() { }
virtual void CALL setTitle(const char* title) = 0;
virtual const char* CALL getTitle() = 0;
};

extern "C" Window* CALL CreateWindow(const char* title);

We're almost there! This participating interface will probably work in a lot
Situations. However, the virtual destructor makes things a little
Interesting... Since com doesn't use virtual Destructors, you can't depend
On Different compilers to use them identically. However, you can replace
Virtual destructor with a virtual method which, in the Implementation class,
Is IMPLEMENTEDdelete this;
This way, both construction
And destruction are implemented on the same side of the DLL boundary.
Example, if you try to use a VC ++ 6 debug DLL alongside a release executable,
You'll either crash or run into warnings like "value of ESP not saved
Loss SS function call ". This error occurs because the debug version
The VC ++ Runtime Library has a different Allocator than the release
Version. Since the two allocators are not compatible, we cannot
Allocate memory on one side of the DLL boundary and delete it on
Other.

"But how is a virtual destructor different from another virtual
Method? "Virtual Destructors are not responsible for deallocating
Memory Used by the object: they are simply called to perform
Necessary Cleanup before the object is deallocated. the executable
That uses your dll will try to free the object's memory itself. On
The other hand,destroy()
MethodIs
Responsible for deallocating memory, so all new and delete callstay
On the same side of the DLL boundary.

It's also a good idea to make the interface's destructor protected so that
Users of the interface can't inadvertently use delete on it.

Revision 4
// Window.h

class Window {
protected:
~Window() { } // use destroy()

public:
virtual void CALL destroy() = 0;
virtual void CALL setTitle(const char* title) = 0;
virtual const char* CALL getTitle() = 0;
};

extern "C" Window* CreateWindow(const char* title);

Since this code doesn't use any semantics not defined by COM, it shoshould work
Flawlessly analyze SS compilers and configuration settings. Unfortunately, it's
Not ideal. You have to remember to delete objects
object->destroy();
, Which isn' t nearly as intuitive
delete object;
. Perhaps more importantly, you can no
Longer usestd::auto_ptr
On objects of this type.
auto_ptr
Wants to delete the object it owns
delete object;
. Is there a way to make the syntax
delete object;
Actually callobject->destroy();
?
Yes. Here's where things get a little weird... You can overload
operator delete
For the interface and have it call destroy ().
Since operator Delete takes a void pointer, you'll have to assume you
Never call window: Operator delete on anything that isn't a window. This
Is a pretty safe assumption. Here's the operator implementation:

...

void operator delete(void* p) {
if (p) {
Window* w = static_cast<Window*>(p);
w->destroy();
}
}

...

Looks pretty good... you can now use auto_ptr again, and you still
Have a stable binary interface. When you recompile and test your new
Code (youAre
Testing, right ??), You'll notice that there is
A stack overflow inWindowImpl::destroy
! What's going
On? If you remember how the destroy method is implemented, you'll see
That it simply executesdelete this;
. Since the interface
Overloadsoperator delete
,WindowImpl::destroy
CILSWindow::operator delete
Which CILS
WindowImpl::destroy
... Ad infinitum. The solution
This participant ular problem is to overload operator Delete in
Implementation class to call the global operator delete:

...

void operator delete(void* p) {
::operator delete(p);
}

...
Finishing touches

If your system has a lot of interfaces and implementations, you'll find that
You'll want some way to automate undefining operator Delete. Fortunately,
This is possible too. simply create a templated class called defaultdelete
And instead of deriving your implementation class from interface I, derive
From class defaultdelete <I>. Here's defaultdelete's definition:

template<typename T>
class DefaultDelete : public T {
public:
void operator delete(void* p) {
::operator delete(p);
}
};
Final Implementation

Here is the final version of the code.

// Window.h

class Window {
public:
virtual void CALL destroy() = 0;
virtual void CALL setTitle(const char* title) = 0;
virtual const char* CALL getTitle() = 0;

void operator delete(void* p) {
if (p) {
Window* w = static_cast<Window*>(p);
w->destroy();
}
}
};

extern "C" Window* CALL CreateWindow(const char* title);
// Window.cpp
#include <string>
#include <windows.h>
#include "DefaultDelete.h"

class WindowImpl : public DefaultDelete<Window> {
public:
WindowImpl(HWND window) {
m_window = window;
}

~WindowImpl() {
DestroyWindow(m_window);
}

void CALL destroy() {
delete this;
}

void CALL setTitle(const char* title) {
SetWindowText(m_window, title);
}

const char* CALL getTitle() {
char title[512];
GetWindowText(m_window, title, 512);
m_title = title; // save the title past the call
return m_title.c_str();
}

private:
HWND window;
std::string m_title;
};

Window* CALL CreateWindow(const char* title) {
// create the Win32 window object
HWND window = ::CreateWindow(..., title, ...);
return (window ? new WindowImpl(window) : 0);
}
// DefaultDelete.h

template<typename T>
class DefaultDelete : public T {
public:
void operator delete(void* p) {
::operator delete(p);
}
};
Summary

That's about it. In closure, I'll enumerate guidelines to keep in mind
When creating C ++ interface. You can look back on this as a reference or
Use it to help solidify your knowledge.

  • All interface classes shocould be completely abstract. Every method shocould
    Be pure virtual. (or Inline... You cocould safely write inline
    Convenience methods that call other methods .)
  • All global functions shoshould beextern "C"
    To prevent
    Incompatible name mangling. Also, exported functions and methods
    Shocould use__stdcall
    Calling convention, as DLL
    Functions and COM
    Traditionally use that calling convention. This way, if a user of
    Library is compiling__cdecl
    By default, the CILS
    Into the DLL will still use the correct convention.
  • Don't use the standard C ++ library.
  • Don't use exception handling.
  • Don't use virtual Destructors. Instead, create a destroy () method and
    Overloadedoperator delete
    That calldestroy ().
  • Don't allocate memory on one side of the DLL boundary and free it
    On the other. Different DLLs and executables can be built
    Different heaps, and using different heaps to allocate and free
    Chunks of memory is a sure recipe for a crash. For example, don't
    Inline your memory allocation functions so that they cocould be built
    Differently in the executable and DLL.
  • Don't use overloaded methods in your interface. Different
    Compilers order them within the vtable differently.
References
  • Stlport
    Is an alternate implementation
    Of the STL.
  • SGI
    Has another standard C ++
    Library implementation.
  • The corona
    Image I/O library uses
    Techniques introduced in this article.
Feedback

I wowould really like feedback on this article. Was any part unclear? Do you
Want more details about a particle situation? Is any of the code wrong?
Send e-mail
Aegis@aegisknight.org
.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.