home *** CD-ROM | disk | FTP | other *** search
- Newsgroups: comp.os.ms-windows.programmer.misc,comp.windows.ms.programmer
- Path: sparky!uunet!microsoft!wingnut!stevesi
- From: stevesi@microsoft.com (Steven Sinofsky)
- Subject: Re: How do you export a C++ CLASS?
- Message-ID: <1992Aug11.030445.24042@microsoft.com>
- Date: 11 Aug 92 03:04:45 GMT
- Organization: Microsoft Corporation
- References: <1992Aug8.095124.2322@swsrv1.cirr.com>
- Keywords: C++, exported classes, SDK
- Lines: 229
-
-
- 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.
-