DLL examples for Gnu-Win32 using EGCS/Cygwin32 and EGCS/Mingw32 
===============================================================

Mumit Khan <URL:mailto:khan@xraylith.wisc.edu>
<URL:http://www.xraylith.wisc.edu/~khan/>

Last Change: Sat Mar 13 15:34:17 CST 1999
An up-to-date version of this document can be found at:
  <URL:http://www.xraylith.wisc.edu/~khan/software/gnu-win32/>

===============================================================

TOC:
  - INTRO
  - CHANGES FROM 0.2.4
  - CHANGES FROM 0.2.1
  - CHANGES FROM 0.2
  - CHANGES FROM 0.1
  - DLLWRAP DOCS
  - DLLTOOL ENHANCEMENTS
  - WRAP UP
  - EXAMPLES

INTRO:
======

Here're some examples on how to build C, C++ and F77 DLL's and client 
programs that use these DLLs. These have been tested under EGCS 1.1
compiler on cygwin32 (b19.x) and mingw32 and MSVC 4.2 compiler on WinNT 
4.0SP3.

These examples assume you have the following at the very least:
  
  - compilers  : egcs-1.1.1 or newer
  - binutils   : binutils-2.9.1
  - dllhelpers : dllhelpers-0.2.5

Earlier versions of dllhelpers provided enhanced versions of dlltool
and dllwrap. These are now integrated in Mingw and Cygwin distributions,
so no more binaries, just examples.

What are the alternatives to making relocatable DLLs? Well, there are few:

  (1) You could always do it the current way of manually running 
      gcc/c++/f77/ld and dlltool multiple times. If you already have
      Makefiles rules set up and working, then don't bother changing 
      them; I'm not changing my existing ones.
  (2) You could use something like Cygnus' ``cygshared'' script, which
      automates (1) in the same manner as dllwrap. It's however somewhat
      customized for cygwin32 and will need some changes to run under 
      mingw32. One drawback with using cygshared-like shell script is 
      that it's unlikely to work under mingw32. 

CHANGES FROM 0.2.4 TO 0.2.5:
============================

The 0.2.5 release has the following changes from 0.2.4:

Compiler version:
  - Remove support for egcs version < 1.1.1. Need 1.1.1 or newer. May
    still work for 1.1, but only for C and C++ and not for f77.

Packaging changes:
  - no more binaries for dlltool and dllwrap. These are now part of the
    standard compiler distributions (as of egcs-1.1.1).

Cygwin changes:
  - No more special cases for Cygwin as of b20.1.

CHANGES FROM 0.2.1 TO 0.2.4:
============================

The 0.2.4 release has the following changes from 0.2.1:

DLLWRAP changes:
  - New or dlltool options that are handled specially:
    --implib or --output-lib: This will create an import library.
    --add-stdcall-alias: See dlltool enhancement below.
    --output-def: Save the <DEF> file. Useful when no <DEF> is provided.
    --image-base: Specify an image base.
  - one stop DLL building. Now dllwrap will automatically create the DEF
    file if none is provided on the command line. Beware however that this 
    may not be what you expect (you can save the auto-generated DEF file
    by providing a --output-def <DEF> option). You can also create the
    import library in this one step by using --implib or --output-lib
    option.
  - new option "--image-base <BASEADDR>" to change the DLL image base. If
    this is not specified, then dllwrap automatically supplies an image
    base address based on the output file name. This avoids the time spent
    in relocating the DLLs. ``objdump -p <DLL> | grep ImageBase'' or
    ``dumpbin /headers <DLL> | grep "image base"'' will print the image 
    base address for the <DLL>.
  - create temporary .exp file in the current directory to avoid Borland
    IMPLIB confusion.

DLLTOOL changes:

  - new option --add-stdcall-alias: This adds aliases without @<n> for
    stdcall symbols. Useful when using LoadLibrary/GetProcAddress API.
  - the LIBRARY or NAME in DEF file is now treated properly and supplied
    as the DLL name in the import library. Command line --dllname <NAME>
    overrides it of course.
  - bug fix in LIBRARY or NAME handling in DEF files. Now can have an
    extension.

DLL example changes:
  
  - Added DOS .bat files to build the examples.
  - Remove the need for separately running dlltool to create DEF file and
    IMPORT library. Now dllwrap can do that in one shot.

CHANGES FROM 0.2 TO 0.2.1:
==========================

A memory allocation bug fix in dllwrap.

CHANGES FROM 0.1 TO 0.2:
========================

The 0.2 release has the following changes from 0.1:

DLLWRAP changes:
  - new option "--entry <ENTRY>" to change the entry point. This overrides
    the built-in defaults for various platforms and should be used only if
    you know what you're doing. DLLWRAP also tells dlltool to exclude these
    entry points from the export list.
  - The temporary filenames are created with the expected extensions (such
    as .exp, .base); this helps with --nodelete option, which also forces
    the temporary files to be created in the current directory.

DLL example changes:

  - Cygwin32 DLL initialization now uses DECLARE_CYGWIN_DLL macro defined
    in <cygwin32/cygwin_dll.h> and works without any hacks. Thanks to Stan
    Cox for his cygshared script where I found the use of this macro. 
  - Rewrite the DLL initialization code, dllinit.c, to get rid of all the
    hacks to get C++ global static initializers to run. Enhance the
    documentation in the file. The DLL initialization code is now very 
    clean and portable between EGCS (cygwin32/mingw32) and MSVC.

DLLWRAP:
========

I wrote dllwrap to replace the shell scripts that I had been using, and 
so far I'm quite happy with it. In the long run, dlltool should be folded
into the linker, and all this will become obsolete.

How does dllwrap work?
----------------------

  dllwrap, as the name suggests, is just a "wrapper" around the various
  existing tools needed to create a relocatable DLL in a single shot.
  You basically invoke it as something like the following:
    
    % dllwrap -o foo.dll --def app.def a.o b.o c.o dllinit.o
  
  And, dllwrap will run the following programs on i386-mingw32:

    gcc -Wl,--base-file,<base_file> -mdll -Wl,-e,_DllMainCRTStartup@12 \
	-o foo.dll a.o b.o c.o dllinit.o
    dlltool --base-file <base_file> --output-exp <exp_file> \
	--def app.def
    gcc -Wl,--base-file,<base_file> <exp_file> -mdll \
    	-Wl,-e,_DllMainCRTStartup@12 \
	-o foo.dll a.o b.o c.o dllinit.o
    dlltool --base-file <base_file> --output-exp <exp_file>  \
	--def app.def
    gcc <exp_file> -mdll -Wl,-e,_DllMainCRTStartup@12 \
	-o foo.dll a.o b.o c.o dllinit.o

  Where <base_file> and <exp_file> are temporary files that are deleted at
  exit unless you specify '--nodelete' option.

  Note the "-mdll" option for mingw32. This is expanded by mingw32 gcc and
  passed on to the linker as "--dll -e _DllMainCRTStartup@12" and also
  causes the linker to link in dllcrt1.o instead of the usual crt1.o.

  On i386-cygwin32, some of the linker flags are a bit different; eg.,
  cygwin32 doesn't have -mdll, so normally you'll have to explicitly 
  supply the correct flags. ``dllwrap'' tries to take care of those 
  little details for you.

  So, say you run the following on i386-cygwin32:

    % dllwrap -o foo.dll --def app.def a.o b.o c.o dllinit.o
  
  And, dllwrap will run the following programs on i386-cygwin32:

    gcc -Wl,--base-file,<base_file> -Wl,--dll -nostartfiles \
        -Wl,-e,__cygwin32_dll_entry@12 \
	-o foo.dll a.o b.o c.o dllinit.o
    dlltool --base-file <base_tmpfile> --output-exp <exp_file> \
	--def app.def
    gcc -Wl,--base-file,<base_tmpfile> <exp_file> -Wl,--dll \
        -Wl,-e,__cygwin32_dll_entry@12 \
	-o foo.dll a.o b.o c.o dllinit.o
    dlltool --base-file <base_tmpfile> --output-exp <exp_file>  \
	--def app.def
    gcc <exp_file> -Wl,--dll \
        -Wl,-e,__cygwin32_dll_entry@12 \
	-o foo.dll a.o b.o c.o dllinit.o

  To recap, the default "driver" flags for mingw32 and cygwin32:
    mingw32  : -mdll
    cygwin32 : -Wl,--dll -nostartfiles

  You can override both using --driver-flags <FLAGS> option to dllwrap.

  and, the default DLL user defined entry points and the callbacks are:

    mingw32  : 
      DLL entry point      : DllMainCRTStartup	(_DllMainCRTStartup@12)
      Entry point callback : DllMain		(_DllMain@12)

    cygwin32 : _cygwin32_dll_entry
      DLL entry point      : _cygwin32_dll_entry(__cygwin32_dll_entry@12)
      Entry point callback : DllMain		(_DllMain@12)

  The linker calls the entry point, which in turn calls your callback
  routine. See the dllinit.c for examples.

  You can override the entry point using the --entry <ENTRY> option to 
  dllwrap.
  
  The tricky part is to figure out what options from the command line
  dllwrap should pass on to dlltool and what to the language driver! 
  It's a mess, but seems to work so far. Note that I usually never use 
  "ld" to create dlls or anything else for that matter, but rather use 
  gcc, c++ or g77 as appropriate which avoids lots of hassle. dllwrap
  assumes that the language driver accepts "-Wl,opt,value" options 
  which are passed onto the real thing.

  The default language driver is "gcc", but you can change that with
  --driver-name; eg., to create a C++ DLL, you should use the following:

    % dllwrap --driver-name=c++ [rest of options]

  If you're not sure what dllwrap is going to do, check out the --dry-run
  option which simply tells what programs it's going to run and what
  the various parameters it's going to pass to these programs. The 
  --verbose (or -v) option to dllwrap will show the various programs as 
  it runs them.

  Make sure you supply a export definition file to dllwrap if you want 
  your DLL to export any symbols!

Command line options:
---------------------
  
  % dllwrap --help
    Usage ./dllwrap <options> <object-files>
      Generic options:
       --quiet, -q            Work quietly
       --verbose, -v          Verbose
       --version              Print dllwrap version
       --implib <outname>     Synonym for --output-lib
      Options for ./dllwrap:
       --driver-name <driver> Defaults to "gcc"
       --driver-flags <flags> Override default ld flags
       --dlltool-name <dlltool> Defaults to "dlltool"
       --entry <entry>        Specify alternate DLL entry point
       --image-base <base>    Specify image base address
       --target <machine>     i386-cygwin32 or i386-mingw32
       --dry-run              Show what needs to be run
      Options passed to DLLTOOL:
       --machine <machine>
       --output-exp <outname> Generate export file.
       --output-lib <outname> Generate input library.
       --add-indirect         Add dll indirects to export file.
       --dllname <name>       Name of input dll to put into output lib.
       --def <deffile>        Name input .def file
       --output-def <deffile> Name output .def file
       --export-all-symbols     Export all symbols to .def
       --no-export-all-symbols  Only export .drectve symbols
       --exclude-symbols <list> Exclude <list> from .def
       --no-default-excludes    Zap default exclude symbols
       --base-file <basefile> Read linker generated base file
       --no-idata4           Don't generate idata$4 section
       --no-idata5           Don't generate idata$5 section
       -U                     Add underscores to .lib
       -k                     Kill @<n> from exported names
       --add-stdcall-alias    Add aliases without @<n>
       --as <name>            Use <name> for assembler
       --nodelete             Keep temp files.
      Rest are passed unmodified to the language driver

  The --driver-name option is used to pass the language driver name
  to dllwrap. The default is "gcc". If you're building a C++ DLL, then
  you should use ``--driver-name c++''; for F77 DLL, --driver-name g77''.
  If you don't specify a alternate language driver, then you must
  explicitly specify the language run-time libraries that you may need;
  eg., if your code needs the C++ runtime library, and you don't specify
  the C++ language driver, then you must include ``-lstdc++'' on the
  command line.

  The --driver-flags override the built-in flags passed to the linker via
  the language driver to create a dll. Only use if you know what you're
  doing. See the default driver flags in previous section.

  The --dlltool-name option are rarely needed when working natively on 
  Win32; if you're working with a cross environment however, it's very 
  useful. For example, you can use the following if you're on a Unix host:
    
    % dllwrap --driver-name=i386-mingw32-gcc \
    	--dlltool-name=i386-mingw32-dlltool \
	[rest of options]
  
  The --target option is obsolescent and should be avoided. I will at some
  point take it out.

DLLTOOL ENHANCEMENTS:
=====================

The dlltool enhancement has to do with creating export definition files.
Currently, the popular method is to do something like the following :

  app.def: $(OBJS)
	echo 'EXPORTS' > tmp.def
	-for o in $(OBJS); do \
	  $(NM) --extern-only --defined-only $$o | \
	    sed -e 's/[^ ]* [^ ]* //' -e 's/^_//' | \
	    fgrep -v DllEntryPoint | fgrep -v DllMain | \
	    fgrep -v impure_ptr >> tmp.def; \
	done
	mv tmp.def $@

It works, but it's highly error-prone. I've enhanced dlltool to do the
same using the following command :

  app.def: $(OBJS)
	$(DLLTOOL) --export-all --output-def $@ $(OBJS)

Better, isn't it? This enhancement adds the following command line
options:
  
  --export-all		exports all external and defined symbols
  --no-export-all	current behaviour (default)
  --exclude-symbols	exclude a list of comma/colon separated symbols
			from the output def file. This is useful if you
			have a DLL entry point that is not called DllMain.
  --no-default-excludes Turn of internal set of default "exclude" list.
			Current list of built-in exclude list is:
			    DllMain@12,DllEntryPoint@0,impure_ptr
  --add-stdcall-alias	This adds aliases without @<n> for stdcall symbos.
  			Only relevant when creating DEF files from a list
			of object files/archives, and you don't want to
			use the decoration when using GetProcAddress API.

Default is "--no-export-all --no-default-excludes" which is the current
behaviour.


WRAP UP:
=======

So, with these two new tools, how do you create a relocatable DLL? Here's
a sample Makefile entry (more details in the examples provided):
  
  DLL_NAME = app.dll		# created by dllwrap
  DLL_EXP_DEF = app.def		# created by enhanced dlltool
  DLL_EXP_LIB = libapp.a	# created by any version of dlltool

  DLLWRAP_FLAGS = --driver-name $(CC) --def $(DLL_EXP_DEF)

  $(DLL_NAME): $(DLL_OBJS) $(DLL_EXP_DEF)
	  $(DLLWRAP) $(DLLWRAP_FLAGS) -o $(DLL_NAME) \
	      $(DLL_OBJS) $(DLL_LDFLAGS) $(DLL_LDLIBS)

  $(DLL_EXP_LIB): $(DLL_EXP_DEF)
	  $(DLLTOOL) --dllname $(DLL_NAME) --def $(DLL_EXP_DEF) \
	      --output-lib $(DLL_EXP_LIB)

  $(DLL_EXP_DEF): $(DLL_OBJS)
	  $(DLLTOOL) --export-all --output-def $@ $(DLL_OBJS)

When "ld" is fixed to obviate the need for dlltool and the multiple
passes, you can simply change that command to use "gcc -shared" instead of
dllwrap.

EXAMPLES:
=========

There are a total of 3 (hopefully) working examples using 3 of the 4
languages directly supported by egcs/win32: C, C++ and F77. The C and
F77 examples are backward compatible with egcs-1.0.x, but the C++ one
as it stands will not work with egcs-1.0.x.

See the c/cdll.h file for examples of how to add the compatibility flags
so that code is portable between egcs-1.0.x, which does not understand
__attribute__((dllimport|dllexport)), and egcs-1.1, which fully supports
MS-style dllimport/export attributes. 

See the c++/dllclass.h file for examples of how to write new C++ code 
for egcs-1.1 and newer where you don't have to worry about a compiler 
that does not support Microsoft-style dllimport/dllexport attributes.

The F77 DLL support only works for subroutines and functions, and WILL 
NOT work for common block variables; g77 front-end currently AFAIK
provides no facility for attaching attributes to common block variables,
and I have not had time to look at what needs to be done to support it
yet. Once the front-end parser supports GCC attributes, it should be 
straightforward to add DLL support for COMMON block variables.

=================================== x ===================================

Mumit Khan <URL:mailto:khan@xraylith.wisc.edu>
<URL:http://www.xraylith.wisc.edu/~khan/>

Last Change: Sat Mar 13 15:34:17 CST 1999
An up-to-date version of this document can be found at:
  <URL:http://www.xraylith.wisc.edu/~khan/software/gnu-win32/>

