www.digitalmars.com         C & C++   DMDScript  

c++.command-line - Building a simple DLL

reply Michael <Michael_member pathlink.com> writes:
I'm having the hardest time trying to build a simple 32-bit console only DLL. As
a Unix dude, I can't fathom having to maintain a .DEF file of exported symbols.
That is just too hard to swallow. I have over a hundred source files...I'm
supposed to maintain a DEF file for these classes by hand? Why not just export
everything and forget about it ala Unix. Much less headache and room for error.

Enough ranting...I have a module Foo.cpp with an accompanying header. I'd like
this to be a DLL called Foo.dll. I've defined DllMain() inside Foo.cpp and I've
added __declspec(dllexport) after each class declaration (how tedious). So for
Bar inside Foo.cpp:

#define EXPORT_DLL __declspec(dllexport)

class EXPORT_DLL Bar { ... };

I've got a simple driver called Driver.cpp which links against an import library
derived from Foo.dll. When including Foo.h in Driver.cpp EXPORT_DLL becomes
__declspec(dllimport). Again, tedious. Welcome to Windows I guess.

By explicitly using __declspec for each class, the theory goes that I won't need
a .DEF file. Is this assumption correct? I'm new to Windows programming.

Here's how I'm building everything. By the way, everything does build without
warnings or errors, but my executable does *nothing* (I have a method in Bar
which just dumps a message to cout, yet I get no output...static link works fine
and produces the expected output). The intention is for a simple 32-bit console
application.

$ dmc -ND -DBUILD_DLL -I\dm\stlport\stlport -c Foo.cpp

Note that my BUILD_DLL macro just defines EXPORT_DLL to be
__declspec(dllexport). Also, I want the -ND switch so that I link against the
dynamic DMC RTLs.

$ dmc -oFoo.dll -ND -L/implib Foo.obj

Note here that I'm telling dmc to build me an import library to link against.
Output from dmc at this point (when the linker runs):
link Foo,Foo.dll,,user32+kernel32/noi/implib;

I do get a DLL and an import LIB. It appears that DMC saw the DllMain() entry
point as I get no complaints.

Finally:

$ dmc -ND -I\dm\stlport\stlport -oDriver.exe Driver.cpp Foo.lib

The linker responds with:
link Driver,Driver.exe,,Foo+user32+kernel32/noi;

I run Driver and get no output (the Driver simply creates Foo and calls a method
that just prints to cout...it works fine when statically linked but produces no
output when I perform the above steps). Note that Driver is a console
application so it has an entry point of main().

I have a feeling I need a DEF file which sucks, because I've gone through the
pain of exporting/importing my class declarations with __declspec. Why should I
now be bothered with maintaining a DEF file? Remember, this is a toy
example...my real code has over a 100 modules and at least as many classes. Do
people actually have to manually maintain DEF files? Again, I'm in disbelief
that Windows programming could be this stupid.

Is there a way to dump the symbols from an import library so that I can at least
check that my exports were correctly performed?

Also, what is the equivalent of UNIX ldd for listing the DLLs an executable is
linked against? I would have expected this application to crash but I get no
dialog or message...nothing appears to happen.

If I do need a DEF file, is there a tool that can generate this for me from the
command line (I hate IDEs)? What would the minimum DEF file for my example look
like for a 32-bit console DLL? Most importantly, is there a way to avoid
manually putting the export symbols into the DEF file? If not, I'm going back to
UNIX.

Thanks for any help!

Sorry for the ranting, but I paid $25 bucks for this. :-)
Feb 05 2004
parent reply Scott Michel <scottm mordred.cs.ucla.edu> writes:
Michael <Michael_member pathlink.com> wrote:
 I'm having the hardest time trying to build a simple 32-bit console only DLL.
As
 a Unix dude, I can't fathom having to maintain a .DEF file of exported symbols.
 That is just too hard to swallow. I have over a hundred source files...I'm
 supposed to maintain a DEF file for these classes by hand? Why not just export
 everything and forget about it ala Unix. Much less headache and room for error.

Yeah, I can sympathize as a FreeBSD-er. Normally, you shouldn't need a .DEF file if you make use of __declspec properly. It's a bit of a shock coming from the Unix world, but it's not so bad once you get used to the platform. BTW: A "console DLL" is an oxymoron. A DLL is a DLL, a console app is a console app. Console apps can be linked with DLLs.
 Enough ranting...I have a module Foo.cpp with an accompanying header. I'd like
 this to be a DLL called Foo.dll. I've defined DllMain() inside Foo.cpp and I've
 added __declspec(dllexport) after each class declaration (how tedious). So for
 Bar inside Foo.cpp:
 
 #define EXPORT_DLL __declspec(dllexport)

You should rephrase this as: #ifdef BUILD_DLL #define EXPORT_DLL __declspec(dllexport) #else #define EXPORT_DLL __declspec(dllimport) #endif
 By explicitly using __declspec for each class, the theory goes that I won't
need
 a .DEF file. Is this assumption correct? I'm new to Windows programming.

Yup.
 Here's how I'm building everything. By the way, everything does build without
 warnings or errors, but my executable does *nothing* (I have a method in Bar
 which just dumps a message to cout, yet I get no output...static link works
fine
 and produces the expected output). The intention is for a simple 32-bit console
 application.
 
 $ dmc -ND -DBUILD_DLL -I\dm\stlport\stlport -c Foo.cpp
 
 Note that my BUILD_DLL macro just defines EXPORT_DLL to be
 __declspec(dllexport). Also, I want the -ND switch so that I link against the
 dynamic DMC RTLs.
 
 $ dmc -oFoo.dll -ND -L/implib Foo.obj

missing '-WD -mn'. '-mn' is the default, but it's always good to be explicit to prevent later maintainer confusion. :-) -ND tells the compiler to assume that the C/C++ runtime comes from DLLs, not the statically linked libraries. This is probably OK.
 $ dmc -ND -I\dm\stlport\stlport -oDriver.exe Driver.cpp Foo.lib
 
 The linker responds with:
 link Driver,Driver.exe,,Foo+user32+kernel32/noi;
 
 I run Driver and get no output (the Driver simply creates Foo and calls a
method
 that just prints to cout...it works fine when statically linked but produces no
 output when I perform the above steps). Note that Driver is a console
 application so it has an entry point of main().

Recompile as above and see if you get output, I suspect you should. If not, you might want to consider using STLport for iostream by adding -I\dm\stlport\stlport to your compile line.
 I have a feeling I need a DEF file which sucks, because I've gone through the
 pain of exporting/importing my class declarations with __declspec. Why should I
 now be bothered with maintaining a DEF file? Remember, this is a toy
 example...my real code has over a 100 modules and at least as many classes. Do
 people actually have to manually maintain DEF files? Again, I'm in disbelief
 that Windows programming could be this stupid.

No. Once upon a time in Windows 3.1 we did have that problem. The only time one really needs a DEF file, technically, is if you export by ordinal. But few libs do that, other than MS's system libs. Oh, before I forget, DLLs are much like "no undefined" shared objects -- if you end up with a lot of undefined symbols when you create a DLL: a) You probably forgot a library when linking the DLL. msdn.microsoft.com can help you figure out which library you're missing b) You forgot to __declspec(dllexport) a symbol or class. You'd be surprised at what has to be exported. But the linker will take care of creating the export library (w/o you creating a DEF file.)
 Is there a way to dump the symbols from an import library so that I can at
least
 check that my exports were correctly performed?

Cygwin's objdump is a help, as is DMC's dumpobj.
 Also, what is the equivalent of UNIX ldd for listing the DLLs an executable is
 linked against? I would have expected this application to crash but I get no
 dialog or message...nothing appears to happen.

If you find one, I'd like to know about it.
 Sorry for the ranting, but I paid $25 bucks for this. :-)

Actually, compared to other tools available, it's **darned good** value for the money. At least you won't turn into a fossil waiting for gcc to get around to compiling your code and the runtime is a DLL (libstdc++ isn't.) -scooter
Feb 05 2004
next sibling parent reply Jan Knepper <jan smartsoft.us> writes:
 Yeah, I can sympathize as a FreeBSD-er.

FreeBSD huh?!... ;-) Good choice! digitalmars.com is hosted on FreeBSD... www.digitaldaemon.com -- ManiaC++ Jan Knepper But as for me and my household, we shall use Mozilla... www.mozilla.org
Feb 06 2004
parent Scott Michel <scottm mordred.cs.ucla.edu> writes:
Jan Knepper <jan smartsoft.us> wrote:
 Yeah, I can sympathize as a FreeBSD-er.

FreeBSD huh?!... ;-) Good choice! digitalmars.com is hosted on FreeBSD... www.digitaldaemon.com

I once contributed to early versions of Linux, but decided to give up on it after it became apparent that Linux is a kernel and everything else is a mess. I've actually found my ancient (*) e-mail "scottm intime.intime.com" and "scottm intime.com" still in the ibcs2 source files. It's always a source of amusement to the sysadmins here at CS that in the eight years I've been here, I haven't been knocked over, whereas all of the Linux boxen get hacked regularly. But, the "consensus" is that BSD is dead, it's no longer useful or pertinent. Funny thing is that FreeBSD is quite popular at MS Research. I've been pretty dedicated to FreeBSD since the 2.x series a few years ago. But it's been a while since I did kernel hacking. The closest I came to doing kernel stuff was 2002, where I did some extensive hacking on a Conexant huMMingbird cordless telephone chip that was being used by a pair of pico- satellites launched off the back of Endeavor. It's is 6502-based system that does spread spectrum. Pictures at http://mordred.cs.ucla.edu/STS-113/ -scooter (*) My company's connection to the "Internet" at the time was via UUCP through randvax!smc!intime (Santa Monica College and RAND Corporation) -- Scott Michel | No research proposal ever survives UCLA Computer Science | contact with implementation. PhD Graduate Student | !! Futuaris nisi irrisus ridebis !!
Feb 06 2004
prev sibling parent reply Michael <Michael_member pathlink.com> writes:
Hi Scott, 
 
Thanks for the help, but it's still a no go. I took your advice and
added "-WD 
-mn" to explicitly set the memory model. Now at least the build
breaks when 
creating the DLL. That's progress. :-) 
 
 $ dmc -ND -DBUILD_DLL
-I\dm\stlport\stlport -c Foo.cc 
 $ dmc -oFoo.dll -ND -WD -mn -L/implib
-I\dm\stlport\stlport Foo.obj 
  link Foo,Foo.dll,,,Foo/noi/implib;
..OPTLINK header/copyright elided... 
 
  C:\dm\bin\..\lib\SND.lib(dllstart)
Error 42: Symbol Undefined _GetVersion 0 
  C:\dm\bin\..\lib\SND.lib(semlock)
Error 42: Symbol Undefined _GetTickCount 0 
  ... 
  and so on ad nauseum. All
undefined symbol messages from SND.lib which I  
  assume is the dynamic C++ RTL
that I'm supposed to be linking to via -ND? 
 
  By the way, the SND.lib import
library is where it's supposed to be: 
 
  Directory of C:\dm\lib
09/08/2003  09:27PM   482,816 SND.lib 
 
  One clue which doesn't look
good...DMC generated the following DEF file: 
 
  LIBRARY "Foo.dll"
DESCRIPTION 'Foo as a DLL' 
  EXETYPE NT 
  SUBSYSTEM WINDOWS 
  CODE SHARED
EXECUTE 
  DATA WRITE 
 
  Shouldn't the SUBSYSTEM be CONSOLE and not WINDOWS? I
tried adding  
  -L/su:console to the link line but was no better off.
Driver.cpp includes a  
  definition of main() not WinMain(). Shouldn't DMC
figure this out? 
 
  Any more clues? 
 
  Thanks, 
  Michael 
Feb 06 2004
next sibling parent reply Michael <Michael_member pathlink.com> writes:
Ok, I figured this one out. I had to link with kernel32.lib explicitly when
building the DLL. For building an exe, DMC links against kernel32.lib and
user32.lib implicitly.

Things are working now...thanks for the help! I think this should be in the FAQ.
I can't believe more people haven't run into these problems. Maybe a "DMC for
UNIX developers" section. :-)

Michael
Feb 08 2004
parent reply Scott Michel <scottm mordred.cs.ucla.edu> writes:
Michael <Michael_member pathlink.com> wrote:
 Ok, I figured this one out. I had to link with kernel32.lib explicitly when
 building the DLL. For building an exe, DMC links against kernel32.lib and
 user32.lib implicitly.

Not completely unexpected, since a DLL, like a Unix shared object/library, has slightly different link semantics than an ordinary application, console or windows. There are times and places where you might not want to link against kernel32 and user32. I can't think of any off the top of my head at the moment. But having the control over linking is desirable.
 Things are working now...thanks for the help! I think this should be in the
FAQ.
 I can't believe more people haven't run into these problems. Maybe a "DMC for
 UNIX developers" section. :-)

:-) :-) :-) -scooter
Feb 08 2004
parent Scott Michel <scottm mordred.cs.ucla.edu> writes:
Scott Michel <scottm mordred.cs.ucla.edu> wrote:
 Michael <Michael_member pathlink.com> wrote:
 Ok, I figured this one out. I had to link with kernel32.lib explicitly when
 building the DLL. For building an exe, DMC links against kernel32.lib and
 user32.lib implicitly.

Not completely unexpected, since a DLL, like a Unix shared object/library, has slightly different link semantics than an ordinary application, console or windows. There are times and places where you might not want to link against kernel32 and user32. I can't think of any off the top of my head at the moment. But having the control over linking is desirable.
 Things are working now...thanks for the help! I think this should be in the
FAQ.
 I can't believe more people haven't run into these problems. Maybe a "DMC for
 UNIX developers" section. :-)

:-) :-) :-)

The biggest one is getting __declspec(dllexport) and __declspec(dllimport) right. The other big one is DLLs can't have undefined symbols. You have to specify all of the additional libs that link against the DLL. It's like specifying "-no-undefined" to the GNU ld linker and requiring that all libraries linked are shared libraries. Other than those two, it's all about what's in the SDKs provided by MS. That's more than an FAQ. -scooter
Feb 08 2004
prev sibling parent reply Scott Michel <scottm mordred.cs.ucla.edu> writes:
Michael <Michael_member pathlink.com> wrote:
 Hi Scott, 
 
 Thanks for the help, but it's still a no go. I took your advice and
 added "-WD 
 -mn" to explicitly set the memory model. Now at least the build
 breaks when 
 creating the DLL. That's progress. :-) 
 
 $ dmc -ND -DBUILD_DLL
 -I\dm\stlport\stlport -c Foo.cc 

This should also have "-WD -mn" in the compiler flags. Think of "-WD" as "-fpic -fPIC -DPIC" to generate position indep. code.
 $ dmc -oFoo.dll -ND -WD -mn -L/implib
 -I\dm\stlport\stlport Foo.obj 
  link Foo,Foo.dll,,,Foo/noi/implib;
 ..OPTLINK header/copyright elided... 

"-WD -mn" isn't needed here since you're linking.
 
  C:\dm\bin\..\lib\SND.lib(dllstart)
 Error 42: Symbol Undefined _GetVersion 0 
  C:\dm\bin\..\lib\SND.lib(semlock)
 Error 42: Symbol Undefined _GetTickCount 0 
  ... 
  and so on ad nauseum. All
 undefined symbol messages from SND.lib which I  
  assume is the dynamic C++ RTL
 that I'm supposed to be linking to via -ND? 
 
  By the way, the SND.lib import
 library is where it's supposed to be: 
 
  Directory of C:\dm\lib
 09/08/2003  09:27PM   482,816 SND.lib 
 
  One clue which doesn't look
 good...DMC generated the following DEF file: 
 
  LIBRARY "Foo.dll"
 DESCRIPTION 'Foo as a DLL' 
  EXETYPE NT 
  SUBSYSTEM WINDOWS 
  CODE SHARED
 EXECUTE 
  DATA WRITE 
 
  Shouldn't the SUBSYSTEM be CONSOLE and not WINDOWS? I
 tried adding  

Nope. You can think of DLLs as a special kind of EXE. A console mode EXE has a different entry point that creates the console window for you. Regular EXEs and DLLs don't create console windows ordinarily. That's why "console mode DLL" is an oxymoron, as previously noted.
  -L/su:console to the link line but was no better off.
 Driver.cpp includes a  
  definition of main() not WinMain(). Shouldn't DMC
 figure this out? 

Vide supra.
  Any more clues? 
 
  Thanks, 
  Michael 

Feb 08 2004
parent reply Michael <Michael_member pathlink.com> writes:
 $ dmc -ND -DBUILD_DLL
 -I\dm\stlport\stlport -c Foo.cc 

This should also have "-WD -mn" in the compiler flags. Think of "-WD" as "-fpic -fPIC -DPIC" to generate position indep. code.

Thanks Scott. I've done some experimenting and learned a few things... I left off "-mn" since it seems to be the default on Windows XP at least, and I found that "-WD" doesn't seem to have the semantics of -fPIC for GCC. I was able to leave off "-WD" when compiling the object files that would be used to build the DLL. This implies "-WD" doesn't generate position independent code, unless I was very lucky (or unlucky). :-)
 $ dmc -oFoo.dll -ND -WD -mn -L/implib
 -I\dm\stlport\stlport Foo.obj 
  link Foo,Foo.dll,,,Foo/noi/implib;
 ..OPTLINK header/copyright elided... 

"-WD -mn" isn't needed here since you're linking.

Again, my experiments show just the opposite. When linking to build the DLL from object files I *had* to add "-WD" or I would get strange behavior at runtime (in my case, no output). Even when I added "-WD" to compile (which I found was unnecesary) I did need to use it for linking the DLL. Thanks for helping out...it gave me enough insight to get through the initial quirks. I'm actually finding the control I have over exported symbols has a benefit. I don't need to export *all* of my classes, which helps with encapsulation. I can prevent clients from accessing internal classes. It's not as tedious as I had anticipated and forced me to consider what the public interface of my library should be. I still like developing on UNIX flavors better. ;-) Thanks, Michael
Feb 09 2004
parent reply Scott Michel <scottm cs.ucla.edu> writes:
Michael wrote:

 $ dmc -ND -DBUILD_DLL
 -I\dm\stlport\stlport -c Foo.cc

This should also have "-WD -mn" in the compiler flags. Think of "-WD" as "-fpic -fPIC -DPIC" to generate position indep. code.

Thanks Scott. I've done some experimenting and learned a few things... I left off "-mn" since it seems to be the default on Windows XP at least, and I found that "-WD" doesn't seem to have the semantics of -fPIC for GCC. I was able to leave off "-WD" when compiling the object files that would be used to build the DLL. This implies "-WD" doesn't generate position independent code, unless I was very lucky (or unlucky). :-)

'-mn' is the default memory model, so it's usually unnecessary on the 32-bit Win32 platforms (NT, 2K, XP). Doesn't hurt to be explicit. '-WD' is for function prolog/epilog generation. It's not exactly PIC, which is why I said you can "think" of it as the Win32 equivalent of PIC. It's really more about some assumptions about segment register loading that need to be made to access the DLL's equivalent of Unix's ".data" and ".bss" segments; the DLL executes in its own address space. Under Unix and Unix-like OSes, shared objects are mmap-ed into the process's address space... not so under Win32. That's why the DllMain() function exists, so you can tell when someone attaches to your address space as a process or thread. This is generally useful if you have some per-thread or per-process state you want to allocate. But this means that the DS segment register has to be loaded with the DLL's data segment. IIRC, there's some funniness with the FS segment register as well. Note also that for this reason, you also have to do the __dllexport(dllimport) funkiness to set up the correct cross address space invocation. [Note: Others, like Jan and Walter, may refine this explanation considerably, but for an overview, it should suffice.] While you may get away with not compiling your source with '-WD' (it looks like it works), you might want to do so anyway to CYA.
 $ dmc -oFoo.dll -ND -WD -mn -L/implib
 -I\dm\stlport\stlport Foo.obj
  link Foo,Foo.dll,,,Foo/noi/implib;
 ..OPTLINK header/copyright elided...

"-WD -mn" isn't needed here since you're linking.

Again, my experiments show just the opposite. When linking to build the DLL from object files I *had* to add "-WD" or I would get strange behavior at runtime (in my case, no output). Even when I added "-WD" to compile (which I found was unnecesary) I did need to use it for linking the DLL.

I usually go right for a '.DEF' file and invoke link when building my DLL; I don't invoke dmc. Thus, the '-WD' is superfluous. Really. :-) [Note: dmc probably needs the '-WD' flag to generate the correct DEF file.] Since you spent the $25 for the CD, use the IDDE to start a make file, then add to it as you go along. It'll create the DEF file (you might have one there already) and away you go...
 Thanks for helping out...it gave me enough insight to get through the
 initial quirks. I'm actually finding the control I have over exported
 symbols has a benefit. I don't need to export *all* of my classes, which
 helps with encapsulation. I can prevent clients from accessing internal
 classes. It's not as tedious as I had anticipated and forced me to
 consider what the public interface of my library should be. I still like
 developing on UNIX flavors better. ;-)

There's no doubt that Unix better architected in many respects (some Unices are better than others ;-). However, there's something to be said about the level of commoditization that Win32 has brought to software development. The usual comment about Unix that I've heard from is the lack of APIs that GTK+/Gnome and Qt supply for application-as-object interoperability, too many standards to choose from, no HTML browser object that can be invoked via X11R6, etc. Sometimes it just hard to argue against success. As you point out, it does make you think a bit about your API and what you export, versus exporting everything in the Unix shared object world. -scooter
Feb 09 2004
parent reply Michael <Michael_member pathlink.com> writes:
That's why the DllMain() function exists, so
you can tell when someone attaches to your address space as a process or
thread.

Yes, I just looked into that. I have already found some nice uses of that, such as initializing singletons. I started with just an empty DllMain() returning TRUE which seems to fulfill the minimum requirement.
[Note: dmc probably needs the '-WD' flag to generate the correct DEF file.]

Yes, that looks like what's happening. I really wanted to avoid creating my own DEF, and so far have been getting by. It does feel like I'm going against the grain a little though.
There's no doubt that Unix better architected in many respects (some Unices
are better than others ;-). However, there's something to be said about the
level of commoditization that Win32 has brought to software development.
The usual comment about Unix that I've heard from is the lack of APIs that
GTK+/Gnome and Qt supply for application-as-object interoperability, too
many standards to choose from, no HTML browser object that can be invoked
via X11R6, etc.

Well, you've got componentization across UNIX platforms now. I don't know any UNIX which doesn't support some implementation of CORBA at least. When I need off the shelf components I just go to Java though. I agree about all the standards though. So much to sift through. By the way, I did run into a problem that I haven't solved. I continued my experimentation by changing my test class to a template class. Now my __declspec doesn't seem to work. The important bits look like this: template <typename Type> class EXPORT_DLL Foo { ..elided... }; I get undefined symbols when I try to link my DLL to the driver executable. I was afraid things were going too smoothly. Also, I'm concerned whether my inlined methods were really inlined. From the size of the DLL it looks like even the inlined member functions are built out of line. Any special tricks I need to know about exporting/importing template classes? Don't tell me I have to instantiate them...please no. :-( Thanks, Michael
Feb 09 2004
parent Scott Michel <scottm cs.ucla.edu> writes:
Michael wrote:

That's why the DllMain() function exists, so
you can tell when someone attaches to your address space as a process or
thread.

Yes, I just looked into that. I have already found some nice uses of that, such as initializing singletons. I started with just an empty DllMain() returning TRUE which seems to fulfill the minimum requirement.
[Note: dmc probably needs the '-WD' flag to generate the correct DEF
[file.]

Yes, that looks like what's happening. I really wanted to avoid creating my own DEF, and so far have been getting by. It does feel like I'm going against the grain a little though.

No worse than building up the libtool command line to build a shared object. It's just another file. :-) Actually, you might try something like this in your make rule: link $(LFLAGS) << $(OBJS) $(OBJS_2) $(EVEN_MORE_OBJS) $(THE_DLLS_NAME) $(LIBS_NEEDED_TO_LINK) $(DEF_FILE) << The DEF file is the one that starts with "LIBRARY". It's not where you put the object files, BTW. You'll still need to tell the linker what kind of target it's building.
 By the way, I did run into a problem that I haven't solved. I continued my
 experimentation by changing my test class to a template class. Now my
 __declspec doesn't seem to work. The important bits look like this:
 
 template <typename Type>
 class EXPORT_DLL Foo
 {
 ..elided...
 };
 
 I get undefined symbols when I try to link my DLL to the driver
 executable. I was afraid things were going too smoothly. Also, I'm
 concerned whether my inlined methods were really inlined. From the size of
 the DLL it looks like even the inlined member functions are built out of
 line.
 
 Any special tricks I need to know about exporting/importing template
 classes? Don't tell me I have to instantiate them...please no. :-(

No, you don't need to instantiate them but you do need to give the compiler a clue: template EXPPORT_DLL Foo<int>; -scooter
Feb 10 2004