Skip to main content
GameDev.net gamedev.net
🔒 Locked

Removing dependency on MSVCR90.dll (and other non-system DLL's)

Started by irreversible Oct 23, 2007 at 11:36 PM 12 replies 35.1k views
Original Post
irreversible
irreversible
Because of redistributability issues I want to strictly remove external dependencies on all non-system dynamic link libraries. I don't mind if relevant portions of the code are stored in my own code - I'm simply having some trouble redistributing these external libraries because of containment issues (since portions of the program I'm writing will be re-redistributed by users, I'd need to contain all externally linked DLL's in the package - just in case; this, however, is kind of pointless given the fact that the user would have to deal with these explicitly. Also - MSVCR90.dll alone is almost 700 kB, which is slightly prohibitive in my case; not to mention redundant). How can I force the linker to include the relevant code portions in the original distributable? To be more specific - currently the following functions are being referenced in MSVCR90.dll: void * __cdecl operator new(unsigned int) void __cdecl operator delete(void *) void __cdecl terminate(void) _except_handler4_common _crt_debugger_hook _CIsin public: void __thiscall type_info::_type_info_dtor_internal_method(void) [?_type_info_dtor_internal_method@type_info@@QAEXXZ] These don't look very MSVC-specific in the first place (that is, why are they referenced in MSVCR90.dll in the first place not some system DLL?).
strtok
strtok
MSVC90.dll is the C/C++ runtime library. Those are VERY MSVCish. The new/delete operators, for instance. You'll find that your operating system does in fact have older versions of Microsoft's C/C++ runtime libraries installed. These new versions aren't already present on most people's systems because they are new (obviously) and most people haven't installed apps that depend on them (yet).

Your options are:

1. Write an Windows installer that installs the C/C++ runtime libraries in the Microsoft approved way (this is easy using WiX, for instance)
2. Try linking statically to the runtime library in your project settings (currently, you probably have it set to something like "Multi-threaded DLL" which introduces the dependency on that DLL). This will make your binary bigger, but you won't need the DLL anymore.
irreversible
irreversible
Thanks, strtok - that did the trick :). Portability is slightly more important for me than size in this case.
irreversible
irreversible
Okay - going into this a bit deeper.

Objective: remove as many dependencies from the DLL as possible while maintaining independence from non-system DLL's.

The reason I'm interested in this is quite simple: when compuling with /MT the compiler seems to include and import a LOT more code than is otherwise referenced when compiling with /MTD.

An example - when compiling a regular DLL with 4 exports (excluding an entry point) that only includes math.h, then the following functions can be found in its import table after linking:

/MT (dependencies included - size 70656 bytes)
	KERNEL32.dllNAME: GetCurrentThreadIdNAME: GetCommandLineANAME: TerminateProcessNAME: GetCurrentProcessNAME: UnhandledExceptionFilterNAME: SetUnhandledExceptionFilterNAME: IsDebuggerPresentNAME: GetLastErrorNAME: HeapFreeNAME: HeapAllocNAME: RaiseExceptionNAME: GetModuleHandleWNAME: GetProcAddressNAME: TlsGetValueNAME: TlsAllocNAME: TlsSetValueNAME: TlsFreeNAME: InterlockedIncrementNAME: SetLastErrorNAME: InterlockedDecrementNAME: SleepNAME: ExitProcessNAME: SetHandleCountNAME: GetStdHandleNAME: GetFileTypeNAME: GetStartupInfoANAME: DeleteCriticalSectionNAME: GetModuleFileNameANAME: FreeEnvironmentStringsANAME: GetEnvironmentStringsNAME: FreeEnvironmentStringsWNAME: WideCharToMultiByteNAME: GetEnvironmentStringsWNAME: HeapCreateNAME: HeapDestroyNAME: VirtualFreeNAME: QueryPerformanceCounterNAME: GetTickCountNAME: GetCurrentProcessIdNAME: GetSystemTimeAsFileTimeNAME: LeaveCriticalSectionNAME: EnterCriticalSectionNAME: VirtualAllocNAME: HeapReAllocNAME: RtlUnwindNAME: HeapSizeNAME: WriteFileNAME: GetCPInfoNAME: GetACPNAME: GetOEMCPNAME: IsValidCodePageNAME: LoadLibraryANAME: InitializeCriticalSectionAndSpinCountNAME: GetLocaleInfoANAME: GetStringTypeANAME: MultiByteToWideCharNAME: GetStringTypeWNAME: LCMapStringANAME: LCMapStringWNAME: GetModuleHandleA


/MTD (dependencies linked dynamically - size 10752 bytes)
	MSVCR90.dllNAME: ?_type_info_dtor_internal_method@type_info@@QAEXXZNAME: __clean_type_info_names_internalNAME: _unlockNAME: ?terminate@@YAXXZNAME: _lockNAME: _onexitNAME: _except_handler4_commonNAME: _crt_debugger_hookNAME: __CppXcptFilterNAME: _adjust_fdivNAME: _amsg_exitNAME: _initterm_eNAME: _inittermNAME: _decode_pointerNAME: _encoded_nullNAME: freeNAME: _malloc_crtNAME: _encode_pointerNAME: ??2@YAPAXI@ZNAME: __dllonexitNAME: ??3@YAXPAX@ZNAME: _CIsin	KERNEL32.dllNAME: GetCurrentProcessIdNAME: GetCurrentThreadIdNAME: GetTickCountNAME: QueryPerformanceCounterNAME: DisableThreadLibraryCallsNAME: IsDebuggerPresentNAME: SetUnhandledExceptionFilterNAME: UnhandledExceptionFilterNAME: GetCurrentProcessNAME: TerminateProcessNAME: InterlockedCompareExchangeNAME: SleepNAME: InterlockedExchangeNAME: GetSystemTimeAsFileTime



What doesn't make sense to me is the number of imported functions from kernel32.dll and the amount of additional code included in the DLL itself:

1) I'm not even sure where these imported functions are used when loading the module
2) why contextually somewhat obscure functions like IsDebuggerPresent() appear in the listing
3) how removing the few dependencies in msvcr90.dll can generate so many dependencies in kernel32.dll when switching from /MTD to /MT
4) why the size of added code in the actual DLL in addition to imports when compiling with /MT is so much greater

I need to export my code only. I don't care for the imports in any respect as I'm not using these in any way (as mentioned I'm not even including windows.h). Why are the compiler/linker forcing my hand with this?

In other words, is there a compiler/linker option to further minimize DLL size or do I have to go into the PE structure head first myself? I'm guessing these can be removed (and safely) and the size of the 70kB version of the DLL can be reduced to 7 kB maximum.

Is there a simpler way of achieving this or can someone point me in a direction that would help me understand which approach to take?

Cheers
silvermace
silvermace
could this possibly be because when linking against a DLL, the functions called by each imported functions need not be imported by your module, whereas static linking requires all code to be present inside your module?

e.g. a function foo() in the DLL version of a library calls bar() and barbar(), but your code only calls foo() so you only need to import foo(). But when statically linking, you need foo() as well as bar() and barbar() in order to remain dependency free?

just a guess...
strtok
strtok
The thing that is important to understand is that your code does not exist in a vacuum. It requires support from the operating system for basic facilities. It is also important to realize that the runtime library does a lot of work before your main function is even called (or if a DLL, then DllMain). All of those functions that are being imported from kernel32.dll are being called from your program/DLL. This doesn't mean YOU are calling them, but that you are calling code that in turn requires those functions. Or, that those functions are part of setup code that gets called before your main entry point. The runtime library sits between your C/C++ program/DLL and the operating system. Therefore, the runtime library must call into kernel32.dll to do basic things like allocate memory, initialize the environment, etc. When you statically link the runtime library, it becomes part of your EXE/DLL rather than sitting off in another DLL (msvc90.dll). Hence, you now have more imports.

If you are asking how you can remove dependencies on all DLLs, it simply cannot be done. You cannot have a working Windows program without some basic support from some system DLLs such as kernel32.dll.

Now, you don't need to worry about somebody not having kernel32.dll. It's an integral part of Windows; everybody has it. What you've done is remove the need to distribute msvc90.dll, which is what a lot of people don't have.
irreversible
irreversible
I know that; which is why I've been doing some research. The reason I'm so adamant on reducing the size of my e DLL's is the following:

I have a number of small DLL's each of which stores a small component for a larger system. These small components are included on a need basis according to what the user comes up with: if the user only uses 3-4 components, then only those are included in the "saved file" created by him. This presents me with a nasty dilemma:

1) either make all the components dynamically dependent on external code (which would be the best solution as there can be a lot of these small libraries)
2) include only the smallest necessary amount of code in each of the component DLL's
3) find some other solution

Now, option number 1 would be my preferred option were it not for the fact that msvcr90.dll is almost 800 kB, which is a ridiculous amount of data to be included (most of which wouldn't ever be used). However, in light of the fact that I can get almost 10 times space reduction when using /MTD as opposed to /MT, this difference is made up for when using 10 or so, or more components libraries.

Option number 2 is the one I'm trying to pursue right now. However, it seems linkers aren't generally smart enough to be able to link ONLY the required code, which is why they link the entire bloody CRT library. Which is stupid. I'm also, however, currently looking into tinyclib.lib.

Option number 3 is beginning to look like the most reasonable one at the moment however: since, when using /MT, the bulk of the 70 or so kB of space used is used up by the statically linked CRT, I'm contemplating including this as a separate DLL that would accompany and be shared by any number of component libraries. I'm not sure if that would work, but it'd only generate less than 100 kB of overhead (as opposed to 800 kB) and should be shareable among all component libraries if used in conjunction with tinyclib.lib.

The only problem with tinyclib.lib right now is that MSVC is refusing to link it, forcing me to go into the code itself xD
strtok
strtok
Or you can just dynamically link to the runtime library, and compile your code with an older version of MSVC (of which most systems are guaranteed to have the runtime libraries...perhaps version 6)?
Evil Steve
Evil Steve
Quote:
Original post by irreversible
Okay - going into this a bit deeper.

Objective: remove as many dependencies from the DLL as possible while maintaining independence from non-system DLL's.

The reason I'm interested in this is quite simple: when compuling with /MT the compiler seems to include and import a LOT more code than is otherwise referenced when compiling with /MTD.

An example - when compiling a regular DLL with 4 exports (excluding an entry point) that only includes math.h, then the following functions can be found in its import table after linking:

/MT (dependencies included - size 70656 bytes)
*** Source Snippet Removed ***

/MTD (dependencies linked dynamically - size 10752 bytes)
*** Source Snippet Removed ***


What doesn't make sense to me is the number of imported functions from kernel32.dll and the amount of additional code included in the DLL itself:

1) I'm not even sure where these imported functions are used when loading the module
2) why contextually somewhat obscure functions like IsDebuggerPresent() appear in the listing
3) how removing the few dependencies in msvcr90.dll can generate so many dependencies in kernel32.dll when switching from /MTD to /MT
4) why the size of added code in the actual DLL in addition to imports when compiling with /MT is so much greater

I need to export my code only. I don't care for the imports in any respect as I'm not using these in any way (as mentioned I'm not even including windows.h). Why are the compiler/linker forcing my hand with this?

In other words, is there a compiler/linker option to further minimize DLL size or do I have to go into the PE structure head first myself? I'm guessing these can be removed (and safely) and the size of the 70kB version of the DLL can be reduced to 7 kB maximum.


1. Many of the imports are used in the CRT startup code (If you stick a breakpoint at the end of main(), you can step into the CRT startup code if you have the CRT source installed).

2. What version of MSVC? In VC2005, /MT is Multi-threaded DLL and /MTD is Multi-Threaded Debug DLL (Hence the IsDebuggerPresent() and other weird imports).

3. I'd guess that msvcr90.dll links to the kernel32 functions instead. You could try running Depends.exe (Comes with the platform SDK) to see what modules reference what functions.

4. In older MSVC versions, the debug CRT used to be a static lib, and the retail one was a DLL. That might be at least partially the case here, with the debug version including more stuff. Again, this depends on the MSVC version you're using.
Adam_42
Adam_42
You can completely eliminate the CRT startup code, and therefore the 800k dll (or exe size bloat depending on how you link).

Take a look at this article for details.

The short version is that you tell the linker to ignore the default c library, and implement an extern "C" void __cdecl mainCRTStartup() function which is the program entry point.
irreversible
irreversible
Adam - that article's good for pre-VC8. For some reason VC8 and upwards link to stuff like _type_info_dtor_internal_method() even if you remove all dependencies from the DLL itself. That's also why I cannot simply change the name of the MSVC RT library (more specifically the version number in the name during loading - which would otherwise have been a brilliant solution) - the earlier libraries simply do not include some of these functions.

I was hoping it'd be possible to simply override the referenced DLL version, but it's not - the library has to be compiled with an earlier version. To this end I only need msvcrt.lib (I hope) from an earlier version of MSVC. Since I don't have VC6 or earlier (4.0 preferably as looking through the MSVC RT libraries in system32 seems to indicate that is the standard used: msvcrt40.dll forwards most functions to msvcrt.dll, whereas all newer or older runtimes are standalones).

Since I haven't been able to find a repository of msvcrt.lib on the web, I created a .def file from msvcrt40.dll and compiled it into an import library. I then added it to the project, explicitly excluded msvcrt.lib (which links to msvcr90.dll) and compiled as /MD (sorry for the confusion above with me using /MTD - I didn't mean the debug version).

I'm now getting the following errors:

Error	2	error LNK2001: unresolved external symbol @__security_check_cookie@4Error	3	error LNK2001: unresolved external symbol "const type_info::`vftable'" (??_7type_info@@6B@)Error	4	error LNK2001: unresolved external symbol "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z)Error	5	error LNK2001: unresolved external symbol __DllMainCRTStartup@12	



Resolving the last two is easy; the second one I don't know about yet and the first one is completely eluding me: even if I replace the entire startup code with my own and exclude msvcrt.lib and libc.lib explicitly, it pops up.

Ideas?


To recap: at this point I'd like to keep the CRT for MSVC4.0 as an import library, which would allow me to compile the component dll's as /MD, bringing them down to a 10kB region. I'm not 100% sure whether I want to replace the whole of the startup code - this only reduces the size by a couple of kilobytes and could create dependency problems later when I want to expand on this framework (eg write the actual content for the component libraries).

PS: when creating the import library for msvcrt40.dll, link.exe gave a lot of warnings in the following format:

msvcrt40.def: warning LNK4102: export of deleting destructor 'functionName'; image may not run correctly

I'm not sure what that means precisely or how serious it is.
irreversible
irreversible
Ha! Using the correct import runtime did the trick :)

I'm down to 7kB with linkage to MSVCRT.DLL directly, which pretty much guarantees that I'll never miss a dependency (well, I hope); plus I now have the whole of CRT and no iffy cropped/self-written loading/CRT routines.

Now, the conclusive question is what does msvcrt.dll ship with - I'm assuming it's included with every windows by default (since, after all, it IS the C runtime). However, I obtained the import library by pretty much asking around and was forwarded it; I have no idea what version of msvrct.dll it's for. Is there a way to find out? Or more to the point - is it even relevant?
Lode
Lode
Why doesn't MS just make a compiler that works on their own operating system without needing extra dlls?

How does MS themselves compile their own programs they ship with windows then? Don't they use VS?
jpetrie
jpetrie
Quote:

Why doesn't MS just make a compiler that works on their own operating system without needing extra dlls?

The compiler works fine.

C and C++ need runtimes, however, or they don't work fine. As has been pointed out, you can strip away the runtime if you're careful and really need to, but this has little practical value most of the time -- and must be done with care, e.g., you'd better know what you're giving up... most people don't, assuming that all that standard behavior they expect from C or C++ Just Happens somehow. These are usually the same people who think that the Win32 API (for example) is implemented in term of the C standard library, and not the other way around.

Anyway, the point is, without these runtimes you lose quite a lot (who calls main, who initializes your globals, provides your heap, et cetera). Including these runtimes can be done statically or dynamically. Crudely speaking where size is concerned, dynamically linking has the benefit of reducing overall size of all dependant applications, this they all link to the same thing. This makes distribution slightly more troublesome, which is why people developing small apps without installers are often irked by it. In constrast, static linking prevents those distribution problems, but bloats the file size over all applications because each appliaction duplicates the aspects of the standard library it uses.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.