12/31/2013

12-31-13 - Statically Linked DLL

Oodle for Windows is shipped as a DLL.

I have to do this because the multiplicity of incompatible CRT libs on Windows has made shipping libs for Windows an impossible disaster.

(seriously, jesus christ, stop adding features to your products and make it so that we can share C libs. Computers are becoming an increasingly broken disaster of band-aided together non-functioning bits.)

The problem is that clients (reasonably so) hate DLLs. Because DLLs are also an annoying disaster on Windows (having to distribute multiple files, accidentally loading from an unexpected place, and what if you have multiple products that rely on different versions of the same DLL, etc.).

Anyway, it seems to me that the best solution is actually a "statically linked DLL".

The DLL is the only way on Windows to combine code packages without mashing their CRT together, and being able to have some functions publicly linked and others resolved privately. So you want that. But you don't want an extra file dangling around that causes problems, you just want it linked into your EXE.

You can build your DLL as DelayLoad, and do the LoadLibrary for it yourself, so the client still sees it like a normal import lib, but you actually get the DLL from inside the EXE. The goal is to act like a static lib, but avoid all the link conflict problems.

The most straightforward way to do it would be to link the DLL in to your EXE as bytes, and at startup write it out to a temp dir, then LoadLibrary that file.

The better way is to write your own "LoadLibraryFromMem". A quick search finds some leads on that :

Loading Win3264 DLLs manually without LoadLibrary() - CodeProject
Loading a DLL from memory � ~magogpublic
LoadLibrary replacement - Source Codes - rohitab.com - Forums

Crazy or wise?

9 comments:

jeffatrad said...

We have that already - RADDLL is in shared. You can load a Win 32-bit DLL from memory on Win, Mac and Linux. I need to make a 64-bit version, though.

jeffatrad said...

The problem is that it's becoming increasingly hard to allocate memory that you write to that can later have the execute bit on. On SE Linux, I've never found a way.

cbloom said...

Oh yeah, you remind me that libc version problems on Linux/gcc is a comparably disastrous problem, if not worse.

johnb said...

@Jeff

Google suggests that the "recommended" way to create writable + executable memory on SELinux is to use two mappings, one writable, one executable. Since the two mappings need to share the same memory, you need to give them a name in the filesystem.

See: http://www.akkadia.org/drepper/selinux-mem.html

It looks really ugly to me, particularly because if the filesystem you use for the temp file is mounted noexec, then the whole thing fails, so you need to try multiple locations to find somewhere that you can create an executable file.

There's some code in libffi to do it:

https://github.com/atgreen/libffi/blob/master/src/closures.c

(disclaimer: I haven't tested any of this myself)

bubu said...

How about /Zl (Z + lowercase L) to compile static libs? http://msdn.microsoft.com/en-us/library/f1tbxcxh.aspx
We are shipping static libs compiled with /Zl and customers are linking it with whatever runtime they use - release, release dll, debug, debug dll. Of course, that works only if you don't use STL in API. We have only simply C API.

Fabian 'ryg' Giesen said...

If you load the DLL manually from memory, how are people supposed to get symbols in the debugger?

Cure worse than the disease if you ask me.

breakin said...

No comment on your idea, but it would be nice if link.exe could "pre-link" a library. That is; pull in all external dependencies and put them in a symbol "namespace".

That would truly be a statically linked DLL. I think you've written about it before, but I could be mistaken.

Might lead to symbols from CRT being duplicated, and each "module" would have their own heap (same restrictions as in a DLL where you might use the wrong CRT). Basically a DLL with static CRT packaged as a library. Might not work perfectly if the external dependencies depend on DLL's other than kernel32.dll etc, but otherwise it should be safe.

This is a fairly windows-only solutions. I'm not sure that you can statically link everything on linux/osx in the same manner..?

cbloom said...

Yeah, we've rambled about this a bit before.

IMO every extern being public is one of the biggest disasters of C and in practice makes all C libraries totally unpredictable and dangerous. You can just never know when linking two libraries together will cause them to change behavior.

Many of the gcc platforms have the ability to specify private/public linkage on libraries. But it needs to be cleaned up and made official as some kind of C "packages".

We also need a big change to the CRT. Particularly malloc and stdio need to be built on top of pluggable function pointers, so that the client can explicitly decide whether heaps are shared between packages or not, etc.

There should be two clearly separate parts of the CRT. The pure code part (strcmp) that is linked in to your package and does not create any external dependencies, and the system-dependent part (the plugins for malloc/stdio). That would very easily eliminate the whole problem of "I built with elibc 4.3.20.shit.fuck and the client only has libce 20.3.4.foobar"

All of this is so trivial and would make a huge difference to the usability of C.

breakin said...

Agreed.

I looked a bit at doing this myself (writing a "linker" that would put symbols in a namespace) but I think my conclusion was that C++-linking model was too hard. Lots of stuff that needs to happen and be put in right place etc. And by pulling in CRT all specials features are needed. Pure C would work, but that is not interesting if all dependencies need to be pure C as well.

I'm toying with the idea of using link.exe with some settings to produce a DLL that is then converted back into a library. I'm thinking that PIC (position independent code) might be used etc.

That way LTCG (link time code generation) would work and link.exe would do everything required. One drawback (apart from it being somewhat involved to do) would be that all the "exported" functions would probably end up in one segment, requiring all of them to end up in the final EXE-file. A second pass of LTCG might remove them, but not sure. Also stuff like PIC might result in subpar performance.

Things that are hardish are global constructors, exception unwind etc.

I think I've accepted DLL-files for now...

old rants