C/C++: Making a very tiny DLL (in Visual Studio)

Having been making native extensions for GameMaker for years, there is one thing that always bothered me: although the code is often minimal (some of my smaller extensions have less than 100 lines of code!), the DLLs still come out to 70..100KB OR have to depend on Microsoft Visual C++ Runtime Redistributable, which is non-preferable for games that should "just run".

Having finally figured it out, I decided to write a small post about the matter.

Configuration

If you have not made a project yet, you will need a "[C++] Dynamic Link Library (DLL)". This can also be done for "[C++] Empty Project" if you set it up as a DLL in the wizard.

You'll usually only want to do this for your Release configuration so that you can still debug your code as normal (and benefit from safety checks).

These are the things you'll need to change:

  • Linker ➜ Input ➜ Ignore All Default Libraries: Yes
    The important bit. You can later add needed libraries to Additional Dependencies.
  • Linker ➜ Advanced ➜ No Entry Point: Yes
    If you don't, you'll be seeing

    LNK2001    unresolved external symbol __DllMainCRTStartup@12
    

    Other option is to add an int a_func() {} and specify that name in Entry Point.

  • C/C++ ➜ Code Generation ➜ Runtime Library: Multi-Threaded
    Removes dependency on MSVCR redist. If you are doing this for Debug configuration, you'll need Multi-Threaded Debug instead.
  • C/C++ ➜ Code Generation ➜ Security Check: Disable
    If enabled, generates

    LNK2001    unresolved external symbol @__security_check_cookie@4
    

    and other references to symbols that you no longer have.

  • C/C++ ➜ General ➜ SDL checks: No
    Incompatible with "Security Check" anyway.

Optional:

  • Linker ➜ Debugging ➜ Generate Debug Info: No
    Spares you of embedding a full path to your project folder for a PDB.
  • Linker ➜ Manifest File ➜ Generate Manifest: No
    If your DLL does not change any particular manifest settings (read: you didn't change anything in "Manifest file" section), you may spare yourself some 400 bytes of data by disabling it entirely.

Caveats and fixes

The ones that I have ran into, anyway

_fltused

If you are using float or double types anywhere, you'll be seeing this error:

dllmain.obj : error LNK2001: unresolved external symbol __fltused

According to this antique post, this is a variable assignment that is emitted (even 15 years later!) by MSVC if any floating-point types are used.

The fix is to define it yourself somewhere:

extern "C" int _fltused = 0;

static initializers

If you initialize static variables inside functions with non-primitive values, like so:

double test() {
	static auto out = GetStdHandle(STD_OUTPUT_HANDLE);
	return out != 0;
}

you'll be seeing

LNK2019    unresolved external symbol __tls_index referenced in function
LNK2019    unresolved external symbol __Init_thread_header referenced in function
LNK2019    unresolved external symbol __Init_thread_footer referenced in function
LNK2019    unresolved external symbol __Init_thread_epoch referenced in function
LNK2019    unresolved external symbol __tls_array referenced in function

There are a few ways to get around this: you could move the static variable out of the function:

static auto test_out = GetStdHandle(STD_OUTPUT_HANDLE);
double test() {
	return test_out != 0;
}

Or you could add a flag for it:

double test() {
	static HANDLE out{};
	static auto ready = false;
	if (!ready) {
		ready = true;
		out = GetStdHandle(STD_OUTPUT_HANDLE);
	}
	return out != 0;
}

printf

I have a separate post about this, but, in short, if you want a normal-looking printf function for logging, you can replicate it with WinAPI like so:

void my_printf(const char* pszFormat, ...) {
    char buf[1024];
    va_list argList;
    va_start(argList, pszFormat);
    wvsprintfA(buf, pszFormat, argList);
    va_end(argList);
    DWORD done;
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &done, NULL);
}

after adding user32.lib and kernel32.lib to Additional Dependencies.


And with that we have turned a 70KB DLL into a 2KB DLL!

Related posts:

2 thoughts on “C/C++: Making a very tiny DLL (in Visual Studio)

  1. Was having some trouble a little while ago with missing MSVC++ dependencies, hopefully (among other things) this will take care of those! Fun stuff.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.