Subject: V Win32 DLL -- HowTo

From: Eyal Ben-David <[email-masked]>
Date: Mon, 03 Feb 1997 17:23:24 -0200

Recently I built the V (ver116) library as a Win32 DLL with:

Watcom  C++  10.6
Visual  C++  4.2
Borland C++  5.0

I believe that the method is also applicable to other compilers
and previous versions of the above compilers. I tested the V apps
that come with	V and they all run OK. The port is not complicated
and it can be automated. I've already done it in the past (v115) but only
for Watcom and with a lot of manual work.

Some Features:
~~~~~~~~~~~~~~
- Same source files for building static and dynamic library.
- Same source files for all compilers.
- Application code is not changed at all !
- All V sample programs and tests run OK.
- Automatic build from sources to dll.
- V implementation is not changed at all.

I think that future releases of V should be DLL-aware, so this is
a first step towards this goal.

I want to upload my work and need some help about it. Meanwhile, here
are the changes needed to build the DLL.

1. Global Changes in header files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dll's have interface similar to C++ `public' and `private'. When
you use a dll you usually communicate with its `public' functions.
You achieve publicity by changing the attribute of a function.
for example:
    void __declspec(dllexport) foo(int a);

When you want to export a whole class (along with its static data)
you declare:

    class __declspec(dllexport) ClassName {
    // ...
    };

Since I use a single source code for both dynamic library 
and static library, I used some macros in the file "v/v_defs.h":

************************************************
// support for V DLL
#if defined (__BORLANDC__)
  #define V_EXPORT  __export
  #define V_IMPORT  __import
#else
  #define V_EXPORT  __declspec(dllexport)
  #define V_IMPORT  __declspec(dllimport)
#endif    

#if defined (BUILD_V_DLL)
  #if !defined(_WIN32)
    #error V DLL is supported under Win32 only.
  #endif
  #define V_CLASS V_EXPORT
  #define V_FUNC  V_EXPORT
#else
  #define V_CLASS  
  #define V_FUNC
#endif  

//  See Appendix for explanation
//
#define DUMMY_DEF_CTOR(ClassName)

//  The following is needed only when using VC++.
//
#if defined (BUILD_V_DLL) && defined (_MSC_VER)
  #undef  DUMMY_DEF_CTOR
  #define DUMMY_DEF_CTOR(ClassName)  \
    friend void _dummy();            \
    protected: ClassName() {}        \
    private:
#endif
*********************************************************

So, the first thing to do is:

  1.1  Add the code fragment (from above) to the beginning of "v_defs.h"
  1.2  Add V_CLASS attribute in every class declaration.
  1.3  Add DUMMY_DEF_CTOR for in every V class that does
       not have a default constructor (or a constructor with all
       default arguments)
       for example:

       class V_CLASS ClassName {
           DUMMY_DEF_CTOR(ClassName)  // first thing !

           // ...
       };

       This is related only to Visual C++  -- See Appendix.

  1.4  Add V_FUNC attribute for every global function.
      
      V_DEFS.H
      ~~~~~~~~
      extern void V_FUNC vSysWarning(char* msg);
      extern void V_FUNC vSysError(char* msg);

      VUTIL.H:
      ~~~~~~~~
      extern void V_FUNC LongToStr(long intg, char* str);
      extern void V_FUNC IntToStr(int intg, char* str);
      extern int  V_FUNC vLblLen(char *str);
      extern int  V_FUNC vTextLen(char *str, int& numLines);
      extern void V_FUNC vGetLocalTime(char* tm);
      extern void V_FUNC vGetLocalDate(char* tm);
      extern void V_FUNC ByteToStr(unsigned char byteval, char* str);
      extern long V_FUNC StrToLong(char* str);

      VAPP.H  (inside vApp class declaration)
      ~~~~~~
      friend int V_FUNC CMain(HANDLE hInstance, HANDLE hPrevInstance,
                              LPSTR lpszCmdLine, int nCmdShow);

  1.5 Make sure that every source does include "v_defs.h".
      I simply put the following:

        #if !defined (V_DEFS_H)
          #include <v/v_defs.h>
        #endif

      at the beginning of every V header file.

2. More Local Changes in the source code.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  2.1  File:  VAPP.H
  ~~~~~~~~~~~~~~~~~~
  Put the declaration of AppMain in a conditional compilation
  block as in the following:

  #if defined (BUILD_V_DLL)
    typedef int (*VEntryProc)(int, char**);
    extern void  V_FUNC vRegisterEntryPoint(VEntryProc);
    extern DWORD V_FUNC vGetApp();
  #else
    extern int AppMain(int, char**);
  #endif    

  Remark:  
  Your function AppMain can't be in the DLL, but WinMain calls
  it (through CMain). At startup (WinMain) we give the DLL
  the Address of our AppMain by the function "vRegisterEntryPoint" .

  2.2  File: VAPP.CPP
  ~~~~~~~~~~~~~~~~~~~
		
  2.2.1  Replace WinMain With DllMain
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Exclude WinMain when building the DLL. WinMain will be in a tiny
  startup object file.
  Add the function DllMain (needed in every DLL)

  #if !defined(BUILD_V_DLL)

  int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
                     LPSTR lpszCmdLine, int nCmdShow)
  {
     return CMain(hInstance, hPrevInstance, lpszCmdLine, nCmdShow);
  }
  
  #else

  BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, LPVOID lpvoid )
  {
     return TRUE;
  }

  #endif // BUILD_V_DLL

  2.2.2 Add 'vRegisterEntryPoint' and `vGetApp()'
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  - Add a global pointer-to-function `pVEntryPoint' - the one that
    holds AppMain address.
  - Add the function vRegisterEntryPoint.
  - Add the function vGetApp.

  *******************************************************************
  #if defined(BUILD_V_DLL)

  VEntryProc pVEntryPoint = NULL;   // holds the address of AppMain.

  void  vRegisterEntryPoint(VEntryProc p)
  {
     pVEntryPoint = p;
  }

  // actually we should return vApp*, but VC++ complains that it
  // can't find vGetApp when linking the application.
  
  DWORD vGetApp()
  {
     return (DWORD) theApp;
  }

  #endif
  *******************************************************************

  2.2.3 Call the pointer to function instead of AppMain
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  In function CMain, Call AppMain through the pointer `pVEntryPoint'.

  #if !defined (BUILD_V_DLL)
  retcode = AppMain(argc,argv);   // call the app main program
  #else    
  retcode = (*pVEntryPoint)(argc,argv); 
  #endif
  if (retcode != 0)
      return (retcode);

  2.3 File: VSTARTUP.CPP  (new file)
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  This File is not a part of the DLL but you must link it to your
  program. Best to compile it and merge it with the import library
  of the V DLL.
  This file contains the modified WinMain and The global variables of V.

  The global data of a DLL is not exported, but we need this data in our
  program. We must have access to `theApp' pointer and to the vColor
  constants and strings. I still don't know how to access data in a DLL
  so I duplicated this data. It is not critical at all.
  Here is the source code of this file.

**********************************************************************
// VSTARTUP.CPP

#include <v/v_defs.h>
#include <v/vcolor.h>

class vApp;
typedef int (*VEntryProc)(int, char**);

extern int   AppMain(int, char**);
extern int   CMain(HANDLE , HANDLE , LPSTR , int );
extern void  vRegisterEntryPoint(VEntryProc);
extern DWORD vGetApp();

// This `theApp' belongs to the application (not to the DLL.)
vApp* theApp = NULL;

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
                   LPSTR lpszCmdLine, int nCmdShow)
{
   // pass the address of AppMain to the DLL.
   vRegisterEntryPoint(AppMain);

   // initialize `theApp' of the application 
   theApp = (vApp*) vGetApp();

   // call CMain as in the static library.
   return CMain(hInstance, hPrevInstance, lpszCmdLine, nCmdShow);
}

// Define 16 standard colors for use by everyone

vColor vStdColors[16] =
{
   vColor( 0, 0, 0),	// vC_Black
   vColor( 255, 0, 0),	// vC_Red
   vColor( 127, 0, 0),	// vC_DimRed
   vColor( 0, 255, 0),	// vC_Green
   vColor( 0, 127, 0),	// vC_DimGreen
   vColor( 0, 0, 255),	// vC_Blue
   vColor( 0, 0, 127),	// vC_DimBlue
   vColor( 255, 255, 0), // vC_Yellow
   vColor( 127, 127, 0), // vC_DimYellow
   vColor( 255, 0, 255), // vC_Magenta
   vColor( 127, 0, 127), // vC_DimMagenta
   vColor( 0, 255, 255), // vC_Cyan
   vColor( 0, 127, 127), // vC_DimCyan
   vColor( 63, 63, 63),	 // vC_DarkGray
   vColor( 127, 127, 127), // vC_MedGray
   vColor( 255, 255, 255)  // vC_White
};

char* vColorNames[16] =
{
   "Black", "Red", "Dim Red", "Green", "Dim Green", "Blue",
   "Dim Blue", "Yellow", "Dim Yellow", "Magenta",  "Dim Magenta",
   "Cyan", "Dim Cyan", "Dark Gray", "Med Gray", "White"
};
*******************************************************************

3. Building the DLL.
~~~~~~~~~~~~~~~~~~~~
  3.1 Some tips (not necessarilly related to V dll)
  ~~~~~~~~~~~~~
  - Compile with -DWIN32_LEAN_AND_MEAN
    When you #include "windows.h" you include everything. 
    since we do not use OLE, ODBC etc. there is no need to
    include their headers.

  3.2 Compiler specific command line flags
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Watcom C++:    wpp386 <your-org-flags> -dBUILD_V_DLL <file.cpp>

  Visual C++:    cl -c  <your-org-flags> -DBUILD_V_DLL <file.cpp>

  Borland C++:   bcc32 -c <your-org-flags> -DBUILD_V_DLL <file.cpp>

  When compiling the startup code (vstartup.cpp) compile it WITHOUT
   -DBUILD_V_DLL. 

  - compile all V sources with the above options.
  - compile vstartup.cpp without dll options.

  3.3 Linking the dll
  ~~~~~~~~~~~~~~~~~~~
  Watcom C++:  wlink sys nt_dll file { $(OBJS) } name v116.dll lib { ... }
               wlib v116.lib +v116.dll
               wlib v116.lib +vstartup.obj

  Visual C++:  SEE APPENDIX.

  Borland C++: The linker command line of BC++ is more complicated
               Use the IDE to generate a makefile for the DLL, then
               add the rule:
                 "tlib BCV116.LIB +vstartup.obj"

               at the end of the dll target.

  - Copy the DLL to a directory listed in PATH.
  - Copy the import library to your V LIB directory.

4. Changes in The application code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   NONE !!

5. Building the Application
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instead of linking with the static library, link with the import library.
Thats All !

Borland C++ issues a lot of warning messages. Add the option -w-inq
to linker command line. The program is built and run OK.

Make sure that you compile your app with the same alignment as
the DLL.

APPENDIX - Linking the DLL with VISUAL C++
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you try to produce the dll after step 3.3 you get a lot of 
error messages from the linker. It does not find the definition
of some destructors.

Not sure, but it turns out that Visual C++ needs two kinds of destructors.
The usual one and the one that is called when you delete a vector of
objects (by operator delete[]).

It also turns out that The compiler automatically generate code
for this destructor when you allocate an array of objects.
for example:
	new ClassName[2];

The trick is to supply a dummy function as follows:

void _dummy()
{
   new  ClassName1[2];
   new  ClassName2[2];
   new  ClassName3[2];
   // ...
}

This function is never called but when the compiler sees an array
allocation it also generates the missing destructor.

We are not done yet. The problem is that not all V classes have
a default constructor (or one with all default arguments).
As usual the PreProcessor comes to rescue as follows.

*******************************************
#define DUMMY_DEF_CTOR(ClassName)

#if defined (BUILD_V_DLL) && defined (_MSC_VER)
  #undef  DUMMY_DEF_CTOR
  #define DUMMY_DEF_CTOR(ClassName)  \
    friend void _dummy();            \
    protected: ClassName() {}        \
    private:
#endif
******************************************

We define the above macro as empty so that other compilers will
not issue an error message.

For every V class that does not have a default constructor
We must add the following:

class V_CLASS ClassName {
    DUMMY_DEF_CTOR(ClassName)  // first thing !

    // ...

};

Now we must supply a function _dummy(). I wrote this function in 
"dummy.cpp" and put it in the V/MSVC directory. When compiling the
V sources to produce a DLL, compile this file also.

**************************************************************
// DUMMY.CPP

// include everything.
//
#include <v/v_defs.h>
#include <v/vapp.h>
#include <v/vawinfo.h>
#include <v/vbaseitm.h>
#include <v/vbasewin.h>
#include <v/vboxlblc.h>
#include <v/vbrush.h>
#include <v/vbtncmd.h>
#include <v/vcanvas.h>
#include <v/vcbtncmd.h>
#include <v/vchkboxc.h>
#include <v/vclabelc.h>
#include <v/vcmd.h>
#include <v/vcmdpane.h>
#include <v/vcmdprnt.h>
#include <v/vcmdwin.h>
#include <v/vcolor.h>
#include <v/vcomboc.h>
#include <v/vcpdc.h>
#include <v/vdc.h>
#include <v/vdebug.h>
#include <v/vdialog.h>
#include <v/vfilesel.h>
#include <v/vfinddlg.h>
#include <v/vfont.h>
#include <v/vfontsel.h>
#include <v/vframec.h>
#include <v/vicon.h>
#include <v/vkeys.h>
#include <v/vlabelc.h>
#include <v/vlistc.h>
#include <v/vmemdc.h>
#include <v/vmenu.h>
#include <v/vmodald.h>
#include <v/vnlprntr.h>
#include <v/vnotice.h>
#include <v/vnulldc.h>
#include <v/vpane.h>
#include <v/vpen.h>
#include <v/vprintdc.h>
#include <v/vprinter.h>
#include <v/vprogrsc.h>
#include <v/vradioc.h>
#include <v/vreply.h>
#include <v/vsliderc.h>
#include <v/vspinc.h>
#include <v/vstatusp.h>
#include <v/vtextc.h>
#include <v/vtextcnv.h>
#include <v/vtexted.h>
#include <v/vtextinc.h>
#include <v/vthislst.h>
#include <v/vtimer.h>
#include <v/vutil.h>
#include <v/vwin32.h>
#include <v/vwindc.h>
#include <v/vwindow.h>
#include <v/vwinprdc.h>
#include <v/vwinprtr.h>
#include <v/vynreply.h>

void _dummy()
{
  new _appWorkTimer[2];
  new vApp[2];
  new vAppWinInfo[2];
  new vBaseItem[2];
  new vBaseWindow[2];
  new vBoxedLabelCmd[2];
  new vBrush[2];
  new vButtonCmd[2];
  new vCanvasPane[2];
  new vColorButtonCmd[2];
  new vCheckBoxCmd[2];
  new vColorLabelCmd[2];
  new vCmd[2];
  new vCommandPane[2];
  new vCmdParent[2];
  new vCmdWindow[2];
  new vColor[2];
  new vComboBoxCmd[2];
  new vCanvasPaneDC[2];
  // new vDC[2];                   Abstract class
  new vDebugDialog[2];
  new vDialog[2];
  new vFileSelect[2];
  new vFindDialog[2];
  new vFont[2];
  new vFontSelect[2];
  new vFrameCmd[2];
  new vIcon[2];
  new vLabelCmd[2];
  new vListCmd[2];
  new vMemoryDC[2];
  new vMenuPane[2];
  new vModalDialog[2];
  new vNullPrinter[2];
  new vNoticeDialog[2];
  // new vNullDC[2];               Abstract class
  new vPane[2];
  new vPen[2];
  new vPrintDC[2];
  new vPrinter[2];
  new vProgressCmd[2];
  new vRadioButtonCmd[2];
  new vReplyDialog[2];
  new vSliderCmd[2];
  new vSpinnerCmd[2];
  new vTextCmd[2];
  new vTextCanvasPane[2];
  new vTextEditor[2];
  new vTextInCmd[2];
  new vThisList[2];
  new vTimer[2];
  new vWinDC[2];
  new vWindow[2];
  new vWinPrinterDC[2];
  new vWinPrinter[2];
  new vYNReplyDialog[2];
}
***********************************************************************