From: stevesi@microsoft.com (Steven Sinofsky)
Subject: Re: How do you export a C++ CLASS?
Date: 11 Aug 92 03:04:45 GMT
Organization: Microsoft Corporation
Keywords: C++, exported classes, SDK


This is a question that comes up rather often so I thought I
would post this lengthly message regarding DLL support in MFC
that I recently sent to a magazine writer.  There are a number
of very interesting issues regarding DLLs and C++.  Have fun...

In article <1992Aug8.095124.2322@swsrv1.cirr.com> toma@swsrv1.cirr.com (Tom Armistead) writes:
>Is there a way to export a C++ class (and all of it's member functions) so
>that they can be called using C++ conventions (instead of normal C)?
>
>We would like to put som String and Container classes in a DLL???  I saw
>a reference to this procedure in one of the technical notes (tn011?) that
>came with the MFC stuff, but can find nothing related to in in the manuals?
The only information for DLL support is in the technical note and there
is also some information available via Compu$erve.

>
> Note on DLL support in MFC:

> From my perspective the DLL issue is two big problems: (1)
implementation issues with a C++ compiler, and (2) problems with the
DLL mechanism and C++ (or why you don't want to be exporting class
interfaces in DLLs).

For the first part, both Microsoft C/C++ and BCC have essentially the
same (somewhat lame) implementation for exporting class interfaces. 
Essentially you declare a class as _export and all of its members
(public, private, protected, static) will be "exported," that is
available for calling by client programs.  You must compile such
interfaces large model, and you must be sure to export any non class
functions that your clients might need (like binary operators in a
string class).  BCC requires the -WD -ml switches, Microsoft C/C++
requires the -GD -AL switches.  After building your .OBJ you must
link it with LIBW and the large model DLL runtimes (in Microsoft
C/C++ these are LDLLCEW.LIB) and create a .DLL.  Then compile any
required resources and bind them to the DLL using RC.EXE.  If you
wish to use an import library (as an aside, one isn't required if you
use an explicit LoadLibrary and GetProcAddress calls, as is typically
done in big apps) then you simply run MS implib over the DLL
producing an import library for client code.

As an example, consider the following class that I'll do as an
addition to the MFC sample application HELLOAPP.  First let's declare
a class:

==== File EXP.H ====
class _export FOO  {
public:
	FOO();				// constructor
	virtual void Member();		// virtual member function
	static void SMember();	// static member
};

void FAR PASCAL _export Global();	// requires export and far! 
PASCAL is a convention


=== File EXP.CPP ====
#include "windows.h"
#include "exp.h"

void FOO::SMember()
{
	::OutputDebugString("FOO::SMember called\r\n");
}

void PASCAL FAR _export Global()
{
	::OutputDebugString("Global function called\r\n");
}

FOO::FOO()
{
	::OutputDebugString("FOO::FOO called\r\n");
}

void FOO::Member()
{
	::OutputDebugString("FOO::Mem called\r\n");
}

static HINSTANCE hInstLib = NULL; // probably use after we write more code
int FAR PASCAL LibMain(HINSTANCE hInst, WORD wDS, WORD cbHeap, LPSTR
lpCmdLine)
{
	hInstLib = hInst;
	::OutputDebugString("In EXP's LibMain\r\n");
	return TRUE;
}


extern "C" int FAR PASCAL _WEP(int)
{
	::OutputDebugString("My WEP has been called\r\n");
	return 1;
}

=== File EXP.DEF ===
LIBRARY      EXP
DESCRIPTION  'Play around with DLLs and MFC'

EXETYPE      WINDOWS
STUB         'WINSTUB.EXE'

CODE         PRELOAD MOVEABLE DISCARDABLE
DATA         PRELOAD MOVEABLE SINGLE

EXPORTS
	WEP 	@1	RESIDENTNAME	


=== Compile EXP.BAT ===
cl -c -GD -AL exp.cpp
link /packcode /align:4 exp,exp.dll,exp.map/map,libw ldllcew,exp.def;
rc /t /30 exp.dll
implib exp.lib exp.dll

=== Use the DLL ===
Now just to see the code get used, let's add the following lines to
the InitInstance of MFC's helloapp example:

BOOL CHelloApp::InitInstance()
{
	m_pMainWnd = new CHelloWindow();
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();

	FOO* pFoo = new FOO;
	pFoo->Member();
	FOO::SMember();
	::Global();

	return TRUE;
}

Modify the makefile to include EXP.LIB that you created above and add
#include "exp.h" to helloapp.cpp.


Things will then work (to see the ::OutputDebugString be sure to have
DBWIN.EXE running or run under CodeView.)

So pretty easy you say....that brings us to the second issue,
problems of C++ and DLLs.

The big reason we chose not to support a single DLL for MFC
applications is that C++ interfaces and DLLs don't work well
together.  The DLL mechanism was designed for C and works best for
that.  There are a number of big problems:

1. versioning and schema evolution: changing a C++ interface (public,
protected, or private) will require updating the DLL, creating a new
import lib, and relinking (and probably recompiling all client code. 
Because of typesafe linkage, you can't even change a char* to a const
char* without having to deal with this mess.  Thus you lost a *key*
benefit of DLLs--the ability to replace parts of your application
without replacing the whole thing.  If we were to provide MFC.DLL in
version 1, and change even 1 API in version 1, even if that change
were totally benign (like adding a const), you could not use the new
DLL with an existing application.  We would then be forced to ship
MFC2.DLL or make all of our customers recompile their applications. 
Every user would have MFC.DLL and MFC2.DLL, which is a pretty big
size hit and pretty soon that starts to add up and you lose the
benefits of shared code (check out BCC 3.1 and OWL31.DLL).

2. lack of encapsulation: even though you only want users to use the
public interfaces of your class, the private interfaces will still be
exported to the DLL.  This is a generic problem with packaging C++
code, since the headers still contain the private and protected
stuff, but now you are adding entry points to your DLL.

3. inlines: where do they go?  If you want your DLL to be standalone,
then you need to outline your inline functions and export each and
every one in your DLL.  If you want the benefit of inlines, then you
must ship interface files that include the inlines along with the DLL
and the DLL is no longer a stand alone entity.  Just a mess...

4. static objects: do you have one copy of static objects?  are they
constructed only when the DLL is loaded?  what about static objects
that you wish to have one per client of the running DLL?  Another
whole can of worms that is made especially difficult because of the
limits of DLL functionality in Windows (one datasegment, no per DLL
instance data).  NT has addressed a number of these issues in
enhancing the DLL mechanism.

5. efficiency: MY FAVORITE!  If you built the DLL above you found
that it was around 5K!  That's huge considering all it has is 4 null
functions.  The reason for this size is that, first, the code is
large model, which is always a big size hit (30-40% for an average
application).  Second, each entry point in a DLL has a fixed overhead
of 8 bytes + the name of the function (remember decorated names are
really long).  Of course, in C you can refer to DLL entry points by
ordinals, but C++ makes this nearly impossible because you want to
automate the "def" file exports section and there is no way to get
the ordinal into the syntax of the member function.  This name table
is limited to 64K (about 2000 entry points, and MFC 1.0 has 1800!).
	Then, recall that our DLL is large model.  That seems really
bad on the face of it, but it is even worse.  These functions all
have the DLL exit/entry _loadds sequence, which adds another 8 bytes
or so to each function.  The problem is that you have no way of
exporting only parts of your class, since DLLs can call across DLL
boundaries and member functions can call other member functions it
must be an all or nothing thing.  In C, you have the option of
exporting a very narrow interface and implementing the rest of your
code however you want.  In C, it is very common to implement a DLL
interface in large model/exported function and then implement the
real guts of the DLL in medium model standard Windows code.  This is
a major savings in speed and size. 
	Also, if you export a class then you must, by definition,
export all of its base classes (yuck!).

So the problem we faced for MFC was that the previous five items were
pretty significant.  These 5 items are the sum of the price paid for
using a C++ interface DLL, and you can't choose to pay for only a few
of them--you must pay all.  We found few people who would be willing
to give up all five in order to have a DLL.  That is everyone finds
at least one of these important enough to avoid exporting C++
interfaces in a DLL.  That is why we urge customers to implement a
DLL in MFC, but stick to exporting a straight C interfaces (see
MFC\DOC\TN011.TXT).   My guess is that an MFC.DLL would be several
hundred thousand bytes, which is pretty huge considering that the
entire library is only 65K of medium model code.



Make sense,
Steven
-- 
Steven Sinofsky
stevesi@microsoft.com
Disclaimer: I don't speak for Microsoft, BillG does that.
Newsgroups: comp.windows.ms.programmer,comp.os.ms-windows.programmer.misc