- Good morning, everyone. Before I started, I
just wanted to make sure you're all aware that this
is a talk about DLLs, right? There's people all over the conference talking about very
interesting things right now, but we're going to talk about DLLs. (audience laughs) All right, just I want to give you the opportunity to leave if you want to. So actually, the talk is titled Everything You Ever
Wanted to Know about DLLs, but I need to amend that a little bit. So it's just actually going to be a few things you probably
didn't want to know about DLLs. If I was to cover everything there is to know about them, it would take me a whole conference worth of sessions. So this is going to be sort of an introduction to how DLLs work. We're gonna touch on a lot of things, we're not going into a
ton of detail in the talk. So my name is James McNellis,
I'm a Senior Engineer on the Windows debuggers
team, so I work on WinDbg. You may have been at our talk yesterday, we announced and released some brand new time-travel or reverse
debugging tools for Windows, which are very exciting, you
should go check them out. Most importantly on this slide, you can see my Twitter handle, you should all go and follow
me, that would also be great. So I've got a lot of things that I'd like to talk about here. Some of the things toward
the end, we may cut, depending on time, we'll have to see. So we're gonna start by looking at how you build a DLL, how
to use a DLL in a program, look at what's inside of a DLL. We'll look at explicit
and implicit linking, what happens when you load a DLL, how to diagnose DLL loading failures, various ways to specify
what a DLL exports. We'll look at data exports, delay loading, C++ threads and then, DLL Hell and then, maybe there will
be time for questions. So what we're not going to talk about, we're not going to talk
about dynamic libraries or shared objects on other platforms. So, obviously, many other platforms have shared libraries of various forms. I've been programming on
Windows for a long time now, so I don't have that
familiarity that I'd need to give a talk on that and
also, I only have an hour. So I have to constrain what
I'm going to talk about. This is CppCon, so we're not
going to talk about .NET, we're going to talk about DLLs that have native code in them. We're not going to focus on any other kinds of DLLs or uses of DLLs,
like resource DLLs as well. And so we'll mention a lot of things that we won't discuss in depth, but I've tried to put enough information on the slides that if
you search for things, you'll be able to find them
quite easily using Bing, or if you prefer, Google. So what and why? So a DLL is a dynamic link library, that's what those letters stand for. So it's a library that
contains code and data, just like a static library, but it can be loaded dynamically at run-time. So you don't actually have to link it into your program when
you actually build it. You can choose whether or
not to load it at run-time. Additionally, it can be shared or reused between multiple programs. So a static library can also be shared between multiple programs because you could link into,
for example, 20 programs. But then, if you
distribute those programs, well, you have 20 copies of that code or data or whatever you've
linked from that on disk. So DLLs, you only need
to have one copy of it, you don't need to have multiple copies. So most normal DLLs have
a .dll file extension, you don't actually have to
have a .dll file extension, it could be a .txt extension,
it will work just as well. Well, some things will work just as well. So why use DLLs? So the biggest advantage
that I already touched on is multiple programs
can share code and data without each programming
having it's own copy, so this can reduce disk space usage because your EXEs are smaller, you have this code that's actually shared. It can also reduce memory usage because if you have DLLs that are loaded into many different processes, all of the read-only pages of those DLLs can generally be shared
between the multiple processes. You can reduce overall memory
usage across the system, even if you may be increasing slightly your own virtual address
base usage within a process. You can defer decision of whether to load functionality until run-time, so perhaps, you may not always need
some functionality, like, for example, if
you write a print driver, you don't want to load your print driver into every single process,
regardless of whether it's printing or not, you want to load it when you actually need to go and print. Or, perhaps, you want to support other kinds of open-ended,
extensibility-like plugins. There's also maintainability benefits. So componentization is enabled by DLLs. If you only work on one
little part of your project, you can keep rebuilding just your one DLL. You don't have to rebuild the
entire thing, necessarily. It can improve serviceability because if, let's say, there's a critical security bug in some of your code. If it's in a static library,
you actually have to go and rebuild every single thing that's linked that bug into the program. Whereas with a DLL, you
can just rebuild the DLL, redistribute it and every
one picks up the fixes. And then, finally, there's
some improved maintainability, basically for the same reason. There's also disadvantages. So it certainly makes
software distribution more complicated, right? If you build everything into a single EXE, well, you can just give that to people and run it and it works happily. But with DLLs, there's mobile files, maybe you have to install
some of them differently. So it can be a bit more complex. There's increased potential
for incompatibilities, I'm sure many people here have had to deal with DLL Hell or DLL Hell-like issues. And then, it's a small
thing, it's impossible to optimize code across DLL boundaries, or, at least, with today's tooling. So if you stat it with link code in, you can actually do whole
program optimization, link-time code generation to optimize across different source files. But with DLLs, there's
actually a hard boundary. Every call into a DLL is an indirect call. So enough talking, let's
build a little DLL. So I'm gonna build a DLL named Hello.dll. Basically all of my slides,
I'm going to actually, we're just going to use the command line. I'm going to have all
the commands you need. Most of the command output will be shown. Sometimes I've trimmed things down just to fit them on the slides, to focus on individual things. But you should be able to
take all these commands, all the sample code and
just copy and paste it and do the same thing. We're going to use the
visual C++ tool set, but you could use an ngw, you
could use CLion on Windows, they'll do the same thing,
it's just a different tool set. So we'll start by
looking at this Hello.cpp that we're going to use to build a DLL. Now, I know you're here at CppCon, you're excited for awesome advanced C++. This is not going to be it. This is about as advanced as the code that we're going to be
showing in this talk because we're focusing
on the DLL mechanism, not the C++ code. So here, this is just a very simple function called GetGreeting. It returns a pointer to a string. The only non-standard
thing we've had to do here is it has the cdecl calling convention and the reason is, is
that when you're talking across the DLL boundary,
you want to make sure that everyone agrees on the calling convention since there's more than one. Otherwise, you'll end
up corrupting your stack or breaking something. So all of our exports
will be explicitly cdecl, you can pick a calling
convention just as long as you use the same one on both sides. So we're going to take this,
we're going to compile it, so cl/c compiles it, it does not link it. And so that'll produce Hello.obj On most of the future slides, I'm going to skip the compilation step because it's always the same, there's nothing special about that. And we're just going to
show the linking step. So here is where we're
going to produce the DLLs. We take the Hello.obj file,
we pass the DLL option to the linker which tells it, "Don't build an XE," which is the default, "build a DLL." We say NOENTRY, the only
important thing right now is that basically reduces
the amount of stuff that gets put into the DLL which makes this example a lot simpler. And then we use /EXPORT to tell it GetGreeting is part of the
public interface of this DLL so GetGreeting is one of the things that you can actually use
from outside of the DLL. And so if we run that, it'll actually, well it says it created Hello.lib We'll talk about that in a bit. It also created Hello.DLL,
though it didn't tell us that. So now that we have our
DLL, we can just run it. No, we can't just run it. Sorry, that won't work. So we're going to have to write a program that actually uses this. So I've written a program
called PrintGreeting, and we have a main function in it. What we're going to do is we're going to call this LoadLibraryFunction that basically goes, finds the DLL, loads it into memory and
gives us a handle back called an H module,
that we can use to refer to the DLL that's been loaded in memory. So this will do that. We'll then create a function pointer type that matches the type of this function that we're exporting, and we're going to call it GetProcAddress for the GetGreeting functions, so we're going to ask
the loader to give us the address of this
function inside of that DLL. Reinterpret cast it to
the appropriate type, we'll then just call puts
to print the string out, and then FreeLibrary because
we're done using the DLL. And then finally we need two headers because we are using
functionality from std.io for puts and Windows for the various library loading functions. So then we can compile and link that, and if we run the program
we see it prints out Hello, C++ Programmers! For those of you who have
a short attention span, that was the same string
that we had in our DLL, so good, we've got it working. This is our example. All right, so let's take a look at what's actually inside of
that Hello.dll that we built. So, when I want to look inside a file, there's a program called
Type, very helpful. So here's what's inside of the file. It's not particularly useful. I asked on Twitter, I told
people I couldn't figure out is there a better tool for this? Someone suggested Notepad, so that works unfortunately just as well. We have some other tools
we'll be looking at here. So, a DLL file consists
of basically a handful of things that are just
in sequence in the file. So it starts off, every DLL
has a DOS stub program in it. It's completely useless
now, but it's there for legacy purposes from back when Windows and DOS inter-operated. It then has a PE signature which marks the beginning of the PE. PE stands for portable executable, so every DLL and EXE is
one of these PE files. There's a big specification on the Microsoft documentation website that has all of the details
that I'm about to talk about. It then has a COFF file header which has a little
information about the file. It has an optional header
that's not actually optional, that has DLL information in it. It then has a set of section headers that tell you how to find
information in the DLL, like what's actually inside of it. And then it just has all of
the code and data after that. So we'll start by looking at the DOS stub and the PE signature. So, I've opened up in a Hex editor, you can see it's a little stub program or, well, it's probably not easy to see that. But basically it's a little
DOS program that prints out. This program cannot be run in DOS mode so that back when DOS and
Windows inter-operated, if you ran a Windows program in DOS it wouldn't actually run, it would just print out this message. So then we can also see here it's got the PE signature at the end, so there's the PE in two null terminators. How do we find that? There's basically a well-defined way, there's add offset 3C in the file, there's a value, and that tells you the offset of the PE signature, so it's at C8 in this file. All right, that's all
just how we get to find the information we want to look for. So next step, we're going to look at the COFF file header,
the optional header and the section headers. So to do that, we're going
to use a tool called DUMPBIN, which basically just
parses DLL or EXE files and it gives you all sorts
of information about them. So it goes and parses all the
data structures inside of them and presents it in a
mostly readable text form. So here we've run dumpbin/headers which prints out all of
the header information and at the top of those we'll see that okay, it's recognized
that this is Hello.dll. It's found the PE signature which means that it's at least starting to find that it's a valid file. It's discovered it's a DLL, and then it's going to print out the actual contents of the COFF header. So we can see here it tells
what machine this is made for, so this is the kind of process the DLL has to be loaded into. It tells you that this DLL
file has two sections in it, which we're going to need
later when go to look for the section headers. It tells you the time and date stamp that this DLL was built. This can actually be any value, so it's not necessarily
the time and date stamp. It is by default, but
you can customize that. It tells you the size
of the optional header, and then it has some characteristics. This file contains executable code, so executable does not
mean that it's an EXE, it just means it can be
loaded as an executable DLL. It says it can handle large addresses. This is a 64-bit binary, it has to be able to handle large addresses,
32-bit is different, and that it's a DLL, not an XE. So then what the loader will do is it'll look at that size
of the optional header and that will tell it the type to use to interpret the optional header. So there's two types of optional headers. There's PE32 and PE32+, and as you would guess,
PE32 is for 32-bit binaries and PE32+ is for 64-bit binaries. So you can see here
there's also a magic number that allows that linker
or the loader to make sure that it's parsing the right kind of data. We can see here the entry point is null. We'll talk about entry points later, but basically we told it "no entry", so that's what we're expecting to see. Has the preferred base
address for the image, which becomes important later. It has some alignment information, it tells us the size of the
image is Hex 3000 bytes. And it's got some characteristics, some extra information, and then finally there's some directories
which are additional metadata about what's inside of the DLL. So you'll see that in the directories it actually says RVA and size. So what is an RVA? And to talk about that, we have to talk about how we address within a DLL. So let's say you've got
your program running. So we've got the heap there,
we've got PrintGreeting.exe We've got a couple thread stacks, some other data somewhere. And we want to go and load our Hello.dll Now our Hello.dll wants to
load at this base address. The loader will say, "All
right, is that space free?" Yes, great, it'll load it there, and everything works
fine, everything's happy. But what happens if we've
started a couple other threads and they're now occupying
that space and memory? It needs to go and put
the DLL somewhere else. We can't load the DLL there,
that memory's already in use. We'd go and overwrite something,
we can't move data around in a native process because you've got just random pointers pointing to things. So maybe the loader will
load it up here instead. We'll talk a little bit more
about this later in the talk. In general, DLLs actually don't get loaded at the preferred base address anymore. Basically the security
feature of the loader will try to load them at a
random address in each process so it's not predictable
where the data will be. But the important thing is
that a DLL can be loaded at all sorts of different
places in your address space. So we need a way to
address things within a DLL without relying on the actual address at which it's loaded. So what we use are what are called RVAs or Relative Virtual Addresses, which are just offsets from
the beginning of the DLL. So if you want to find
the address of something in memory and you have
its RVA, you just add it to the base address at which
the DLL was actually loaded. Or if you have the address of
something and you want its RVA you just subtract the
base address from it. So for example if our
GetGreeting function in Hello.dll has an RVA of 2000, and
if Hello gets loaded at its preferred base address there, then GetGreeting will be
located at this address because again, you just
add the two together. So that's the optional header. The optional header then gets followed by the section headers, so
they're just one after the other, and these tell you where
to find the actual data inside of the DLL. So here for example is the first section, it's the text section which contains code. We can see that because in the flags it has execute and this needs
to be mapped into a page in memory with execute
and read privileges. We can see here the virtual address, so this is the RVA where
this section will be located when the DLL is loaded. And then we can see the virtual size, which is the number of bytes that are actually contained here. There's only eight bytes
of code in this DLL. And then some other information about how it's laid out in the file. The second section is an .rdata section which contains read-only data, we can see that in the flags here that it will need to be mapped into a page that has read-only permissions, so not writeable, not executable. And it's got slightly more data in it. From the optional header we also saw that this DLL had two directories, there are many more it could have had. And the RVAs there happen to
be inside of this section, and we can tell that because the RVA of the export directory, for example, is 2040, and this section occupies all of the virtual addresses
from 2000 up until 20D7. You can see that on the third
or fourth line of output. So from Hello.dll's headers we know that the DLL will occupy
three pages in memory when loaded into the process. One page will contain the headers, one page will contain the .text section, one page will contain that .rdata section, and we'll show that in a
bit more detail in a bit. These aren't the only possible sections, there's other kinds of sections
we might find in a DLL, but these are the only
ones this program needs. The DLL has additional metadata
in a pair of directories, there's the debug directory
that we're not going to look at but it basically has some
very basic debug information. And there's an export
directory that we will look at in a little bit. And all of that data is
contained in the .rdata section because it's basically just read-only data that the loader's going to use. So then after that, basically
we just have the sections, so the section headers told us where to find the data in the file and so we can now look at what's actually inside of the sections. And to do that we can use dumpbin/rawdata which will actually just
print out the hex of the data that's inside of each section. So here we'll start with the text section, and we can see that, well
okay, it contains some bytes. Since this is executable code, we can also use the
disasm option of dumpbin which will actually disassemble it, and so we can see it's two instructions. All right, well that's not
very exciting but there it is. We then look at the .rdata section and again we can use the raw data option, and it's got a bit more data. Most notably, it has our string there, so this is where the string is located. It's a string literal,
it's read-only data. It'll be in the .rdata section. Additionally, there's two directories that we talked about previously
are also located in here, and so I can highlight those. We're not going to talk
about the raw bytes there. But we will look at what's
inside of the export directory. So the export directory defines the public service of the DLL, so it defines all of the things that other DLLs or EXEs
can use from this DLL. And so to get information
out of it, there's an option of dumpbin, you just call
dumpbin/exports on the DLL. And here it'll print out,
okay, we have one function that's exportable so if you
have handle to this DLL, you can call get proc address, and you can get this one function. That's the only thing
that's available here. So at this point we can
put everything together. So in the export directory we see, okay, that GetGreeting function
is located at RVA 1000. Well I remember from previously, RVA 1000 is in the .txt section, And so we can disassemble the .txt section and we can see, okay, that's
the first instruction in there. Now, if you're familiar with, so that's pointing basically
to that first instruction, and if you're familiar with x64 calling conventions and assembly, basically if you have a
function that returns a pointer like our GetGreeting
function, you put the pointer into the RAX register and then you return. And so here we can see
that this is going to load the address of whatever it at RVA 2000 into the RAX register and then return. Well, what's at RVA 2000? Well, I remember that was
inside of our .rdata section. And so if we look in there,
that's at the very beginning of the .rdata section, And it's going to return
the address of our string, or the address of our string literal. So that's kind of the basics
of what's inside of the DLL, how all the pieces point at
each other, work together. So, in this example that we had previously of our PrintGreeting, we saw how to link explicitly to the DLL, so where you actually call load library to get the handle of the
DLL, you call GetProcAddress to get the actual address of the function. But there's a little problem here, and that is that load
library is actually defined in a DLL itself, and GetProcAddress is also defined in a DLL. So both of these are
defined in KERNEL32.dll. So, well we can't call
LoadLibrary to get Kernel32 so that we can get the
address of LoadLibrary. That would not work. So DLLs can actually have
implicit dependencies. So what you can do is you
can run dumpbin/dependents, which will tell you
all of the dependencies that an EXE or a DLL has. Our PrintGreeting has
dependency on KERNEL32.dll, and we can run dumpbin/imports to find out what exactly it's importing from that DLL. And so here it'll say, all right, it has the following imports. And we can see FreeLibrary,
GetProcAddress, and LoadLibrary, which are the three functions that we call in our main function. And then it turns up because
we've statically linked the C run-time into this
XE, so we can call puts, there's actually a whole
bunch more imports. So, I don't want to
look at Kernel2 for this because again there's so many functions and it's big and complex, so what I want to do is I want to convert this PrintGreeting program that's used explicit linking to our DLL, and I want to convert it into this one. So basically we just want to declare our GetGreeting function and then call it, and then use implicit linking
to get everything to work. So what we're going to do is again, we're going to previously
we linked our Hello.dll, and it printed out
Creating library Hello.lib. That library is an import
library for Hello.dll. And so what we can actually
do, we can actually also run dumpbin exports on that .lib and it'll tell us okay, well the GetGreeting function is available. So what's actually inside of there, you have to run dumpbin/all to get more detailed information, and
this prints out tons of stuff so I'm just going to show
the important excerpts. It has the two linkable symbols, so it has a GetGreeting symbol
that it can be linked to and then it has an imp_GetGreeting. And then there's an extra
member inside of the library that actually says okay,
this GetGreeting function is a code export, so
it's a function, not data and it's located inside of Hello.dll. So what happens inside the linker is this is as if the
library had the following. So it's as if we had a function
pointer of the correct type, and it's got a global
variable imp_GetGreeting that gets initialized to the address of the GetGreeting function
inside the other DLL, and that happens through magic, and we'll see how that magic
works in an upcoming section. But for now, just assume
that this ends up being the address of the GetGreeting function. And then GetGreeting is
basically just a stub that gets statically
linked into your program that calls through that function pointer. So this way, inside of our main function if we call GetGreeting,
it'll end up calling through that function pointer and getting the function from the DLL. So then if we have our
PrintGreeting program, we can compile it, we can run it, and we get our message again. Then if we run dumpbin/dependents on our PrintImplicit program, as compared to our PrintGreeting
program, we can see, well now it depends on Hello.dll
just like the other one depended only on Kernel32. And if we look at the imports, we can see okay, it imports GetGreeting,
and we no longer import the LoadLibrary, FreeLibrary
and GetProcAddress from KERNEL32 but we still
do import all the things needed for the C standard library. All right. So, so far, we've looked at how you can use /export to tell the
linker what you want your export service to be. There's a few other ways and there's a few advanced things that we can do, so we'll look at that. So I'm kind of tired of this silly example of our greeting program so I
came up with a new example. I thought it'd be useful to have a DLL that has commonly used math constants. So to that we're going to
have these three functions, now I get one, two and three. And so from previously we know
that we can just pass /export for each one of those. Create the DLL and then
if we dumpbin the exports we'll see, okay it exports
all three of those, so that's just the same
as we were doing before except now we have three of them. And then if we look in the library it also has all three of them. You can also rename
exports, so for example here if instead of calling it GetThree, we wanted to call it GetOnePlusTwo, we don't even need to rename the function inside of the code. We can leave it named GetThree, I don't know why we
wouldn't want to change that but it's possible. And then we can export it as GetOnePlusTwo and so then you see in the exports list and in the import library,
it's just renamed the symbol but in our code it's stayed the same. Now let's say we want to add
GetThree back, we can do that. And you can note in the exports list that we haven't added a
new function to the DLL, we've just added a new name
for it in the export list. So here you can see
GetOnePlusTwo and GetThree are both RVA 1020. You can also have private exports, so these are exports that
you can call GetProcAddress to get them. But they're not in the import library, so you can't implicitly link to them, you can only explicitly link to them. So this is useful, for
example, if you want to deprecate some functionality,
you don't want people calling something anymore, but you can't remove it
because you've got people depending on the existing APIs. So this /export can get very unwieldy if you got 1000 exports
from your DLL, right? You don't want to have to
keep updating your make files every time you add new exports, and you don't want to have to maintain a gigantic list of these options. I expect you'd run up against some limit on the length of the
command line eventually. So another option is you
can use what's called a module definition file. So our numbers program is still
the same as it was before, but now we have a new
file called Numbers.def. So this file just says this
is a module definition file for Numbers.dll, the DLL is implicit. And then it has the list of exports, so here we export the GetOne function, we're going to export
GetTwo as being private, and then we'll do the rename trick. I'm just doing this to show you the syntax of the different options. When we link, instead of
passing /export we pass /def to tell it what the
module definition file is. And then if we run dumpbin,
everything is just as it was with the /export options. So this is basically just
another way of specifying all of the exports for the DLL. There's another option that lets you do it inside of your code. You can annotate a function
with _declspec(dllexport), and then we don't have to
tell the linker anything. Basically we just pass it the object file, and then if we dumpbin the exports we see it's got all the exports. Magic. It turns out it's actually not magic. What happens is the compiler
basically just injects into the object file some strings, and it tells the linker,
"pretend as if you've got these "on the command line." So basically _declspec(dllexport)
just gets turned into these linker directives and so it's exactly the same
thing under the hood. Finally there's a fourth option. I don't really know why one would use this but it's a possibility. You can actually pass those directives using pragma comment. So there's this Visual C++ specific pragma that you can use and so this
will inject those directives into the object file just like the _decalspec(dllexport)
did automatically. And so if we do that we
can see the same exports. Cool. So now kind of the meat of the talk. What happens when we load Hello.dll? So, the loader has to do a lot of things and there's a lot of, there's a lot of advanced
features that DLLs can use, there's a lot of interesting
things they can do. We're not going to touch on most of those, we're going to focus on
the things that are germane to our simple example here. So we're going to look
at five steps basically. So first the loader
needs to find Hello.dll, it needs to map it into memory correctly, it needs to load any DLLs
that our Hello.dll depends on, any implicit dependencies that it has. It's going to bind imports from DLLs on which Hello.dll depends. So it needs to actually
go and initialize those function pointers like
imp_GetGreeting that we had. And then it needs to call
the entry point for Hello.dll to let it initialize itself. So before I get into all that, I just wanted to note, so
DLLs are reference counted so for example, here if
we call LoadLibrary twice on Hello.dll, the first
time it will load the DLL and its refcount will be one. If you call it again, the
refcount will then just be incremented to the two, it's
not going to load the DLL a second time. Both Hello1 and Hello2
will actually be the same, they'll be the same value, same handle. And then when we call FreeLibrary
on the first of those, it'll just decrement the
ref count, it won't actually unload the DLL. And then when we call
FreeLibrary a second time, it will then unload the DLL. All right, so the first
thing we have to do is we have to find the right DLL to load. If we say LoadLibrary Hello.dll, how does the loader know
where to find Hello.dll? So, the easy case is if
we were to have passed it an absolute path. So here, instead of just
calling LoadLibrary Hello.dll, we call Load Library A:\Hello.dll. So if A:\Hello.dll has
already been loaded, the loader will just
return its module handle, so that's easy, right? Again, it's going to be reference counted. Otherwise, the loader's actually just going to find that file on disk. If it's there, it'll load it. If not, it'll fail the load. Additionally to this, you can actually load different DLLs
that have the same name from different paths,
so here if I have one on my A drive, one on my B drive, I guess I have two floppy
drives on this laptop, then it would actually load both of those. Those would be different module handles and you'd have two different
Hello.dlls in memory. So then after that, if
we haven't passed it an absolute path, so here first we've called LoadLibrary for the
absolute path Hello.dll, it's gotten loaded. The second time we call LoadLibrary on Hello.dll with no path, the loader's going to say, "Is there already a DLL
with that name loaded?" In this case, yes, and
so it's actually just going to return, it's
going to say, "I assume "that's the DLL you're looking for, "and I'm going to return
the handle to that." So here, HelloDll1 and
HelloDll2 are the same. And we saw previously, well
you can actually have two DLLs with the same name that
are already loaded. In that case, the loader
is just going to pick whichever DLL was loaded first, so in this case our HelloDllX
that was loaded with no path is the same as A, because
that was the first of the two that we loaded. So then, okay, we say there's no DLL with the name we're looking
for that's already been loaded. And the loader needs to find
the one we're looking for. So what it's going to ask
first is, is it a known DLL? So there's a small set
of operating system DLLs that are considered well-known
to the operating system and if you call LoadLibrary for them or if you have a dependency on them, it's always going to load the operating system version of it. It's never going to do any
of the other searching, and it's basically to
prevent DLL hijacking. It's to prevent someone from
putting a malicious DLL in and replacing some OS
functionality with their own. So here for example, if
we try to load Kernel32 or Ntdll or Ole32,
these are all known DLLs so they get loaded from the
system directory every time. Whereas if we call
LoadLibrary on our Hello.dll, the OS doesn't know what
that is and so the loader will continue with the search process. If you want to know what
the set of known DLLs are, you can use, they're all
listed in the registry, you can just look at this registry key. These are from my Windows 10 laptop. So after that, all right. So it's not already loaded into memory, we don't have an absolute path, it's not a known DLL. The loader needs to go
and search for the DLL. So this is the standard search path. Basically, it's going to start by looking in the same directory that
our application is located, so the A drive for my
little test program here. It'll look in the system
directory, which will be System32 for 32-bit programs, it'll be 62 for- No, I've got that backwards. Anyway, it'll be one of those. It'll then look in the
16-bit system directory which is just named
System, because legacy. It'll then look in the current directory, so wherever you ran the program from. And then it'll go through the directories in your path environment variable. And basically it searches
these in that order, and as soon as it finds one, it'll say, "That's the DLL I must be looking for." The current directory
used to be searched first, that changed in Windows XP,
so that's no longer the case. This search process, it turns
out, is highly customizable, so there's DLL redirections,
side by side components, the path obviously can be customized, it's an environment variable. There's an AddDllDirectory function, there's a whole bunch of flags you can pass to LoadLibraryEx. Windows Store and Windows
Universal Applications are totally different in how they work. I'm not going to talk about all of those. I list them here so that if
you're curious about them, all of those you can search for them and find more information about them. All right, so the loader has
found the DLL it's looking for. In our case it was easy,
it was just right next to the XE we were running. But it now needs to map
that DLL into memory. So it's going to open up the DLL and it's going to look at the header, and it's going to say, "Excellent, "Hello.dll occupies 3000, or Hex 3000 "is basically 12,000 bytes in memory, "so I'm just going to copy
the whole file in memory." But Hello.dll is only 2,048 bytes. And the reason for this is
the DLL actually has different alignment in the file where
it has Hex 200 byte alignment, and in memory, where it has
Hex 1000 byte alignment. So in memory it has to
have 4,096 byte alignment, because that's the size of a page, and as we saw previously in our Hello.dll we have a .txt section, we have an .rdata section, and they have to have different protections, right? The text section needs to
have execute privileges, whereas the .rdata section you don't want to have execute privileges. So basically each
section has to be aligned to a page boundary in memory. In the file, though, it
uses 512 byte alignment, and the reason is that's
the FAT sector size. So back when this was
designed, that was the default. You can actually change
that, there's a linker option to change the alignment, so
you could make it smaller if you wanted, but the default is probably
fine for most purposes. So sections have to be
page-aligned in memory. This is what I was saying previously, that the .txt section has
different permissions on it than the .rdata section, so they have to be on different pages. Additionally, we don't actually
need to store everything in the file, so for example,
let's say I have a version of our Hello.dll called
HelloBuffer, and I've just thrown an extra one megabyte global
buffer inside of that DLL. Well, the DLL file is still
only 2,048 bytes in size. If we look in the headers,
we actually end up seeing that okay, the DLL does
have a larger image size. When it gets mapped
into memory it does have that extra megabyte that
it's going to take up. But then if we look in
the data section, we see that the size of the raw data is zero, because again, this is
going to be zero initialized so the default zero initialization
that the loader's going to do of these pages is sufficient. We don't actually need to put
any data in the file for this. So there's some benefit here that anything that's zero initialized, we don't need a whole bunch of zeroes
inside of our file. So, to map the DLL into memory, basically the loader
needs to map the DLL file and figure out what the image size is, where all of the sections are located. It needs to allocate a
contiguous page-aligned block of memory of that size, so
all of these pages do have to be allocated contiguously, otherwise the relative virtual addresses don't work correctly. And it has to copy the
contents of each section into the appropriate area
of that block of memory. Later, it will set the
appropriate page protections on each page in the mapped DLL. Before it does that,
there's a couple more things it has to do. So next up, it has to do
relocation, potentially. So let's say we have another little DLL we're going to write. And in it we've got two global variables, and I've exported them just so that we can get their addresses very
easily in the next slides. So first we've got a
global variable named Two, which is just an integer
with the value two, and then we have a global pointer variable that just holds the address of that. And we link it. So how do these look in the DLL? Well, we can look at the exports. Again, this is why I exported them. And we can see that Two is at RVA 1000 and the pointer is at RVA 1008. And so if we look at the .rdata section we can see okay, Two is at RVA 1000 and PointerToTwo is at RVA 1008. And you can see that the
PointerToTwo actually contains the actual pointer we're
expecting to find it at, so it's assuming that the DLL is located at its preferred base address. So this is only going to
work if PointerGlobal.dll gets loaded at that
preferred base address. If it gets loaded at, say 90 million Hex, then this won't work. That pointer will be pointing
somewhere completely wrong. So what we do is we use, the DLL file has what
are called relocations. And the relocations are basically a table of all of the pointers in the DLL that need to be fixed
up if the DLL is loaded somewhere other than its
preferred base address. So it's actually just a very simple table, so if you can dump in relocations
to get the list of them, and basically we have one here and it just says that
there is a pointer located eight bytes from the start
of the section beginning at RVA 1000. And the reasons you get
this kind of weird output is that the table is very compact, so they don't actually
store all of the data all together in one place in the DLL. What the loader will
do is it'll just update each pointer listed in
the relocation table by subtracting the preferred base address and adding the actual base address. So for example, here it'll
take the original pointer, it'll subtract the preferred base address, add the actual base address it was loaded, we're assuming it's
loaded at that address, and that gives us the pointer value which the loader will just write into that location in memory. The reason that the pointer contains the address it would
have if the DLL is loaded at the preferred base address, instead of just having, say,
the RVA to start off with is because a long time ago, the loader would try to load DLLs at their preferred base address. So if that address was available, it would try to load the DLL there, and it would only fall
back to relocating it if it absolutely had to. So basically it was more
efficient to do that. Nowadays, though, there's a feature called ASLR, Address Space
Layout Randomization, where basically the loader will always try and load the DLL at some other location so that attackers can't
predict where the C library is, and use C library functionality
to produce exploits. All right, so now that
we've relocated the DLL, all the pointers are patched up, we now need to load
all of the dependencies and bind any imports. So if we look at the
imports in our Hello.dll, we see it doesn't have any, so all right, we can't use
Hello.dll for this part. So let's add a new function to Hello.dll that will give us a dependency. So we already had our GetGreeting function that returns a narrow string. (mumbles) I get a wide greeting function that takes a buffer and it copies that into a wide character buffer instead. And I've done this
because then we can call MultiByteToWideChar which
we would have to import from Kernel 32. So when we link it, we now
just add the new export to the command line, we add kernel32.lib so it links in the
import for that function that we're now calling. We look at its exports, we see
yes we've got both functions, and we look at its imports and we see yes now we're importing MultiByteToWideChar. So this is for exposition, I just thought it was easier to show as fake pseudocode. Basically the loader's going to loop over all of the DLL dependencies. It's going to load each dependency, so it's going to, in this
case, we've only got one, so it's going to go and load Kernel32, which will probably already
be loaded in the process. If that fails, it'll return failure, so it'll actually fail
to load your DLL as well. And then for each function
that gets imported, it calls GetProcAddress to go
and get the actual procedure. The loader internally doesn't
actually call LoadLibrary or GetProcAddress, but
this is conceptually exactly what the loader is going to do. So, or in English,
basically the loader's going to load each DLL that we are dependent on, and then get all the required exports that it can fill in those imp variables, those function pointers. All right, at this point
the DLL is basically loaded, almost ready for use. The loader now gives
the DLL the opportunity to initialize itself if it needs to, and it does this through an entry point which is conventionally called DllMain. So DllMain is a function that
you implement in your program, and it's like the C main
function but for DLLs. So it takes three parameters. The instance is your actual DLL handle, so it's the same one that will
be returned from LoadLibrary if someone's loading you that way. The reason indicates why
the functions being called, so there's four different times when the loader calls this function. It calls it on process attach, which is when the DLL gets loaded. It calls it on process detach, which is when the DLL gets unloaded, and then it calls it on thread
attach and thread detach, which are when a thread
starts or stops running within the process. And then the reserved parameter just gives a little extra information
for some of those. And then you get the opportunity to return true on success
or false on failure. If you return false then
your DLL fails to load. Finally, calls to DLL main are sychronized by a global lock, called the Loader Lock. So basically, only one
thread can be initializing a DLL at any one time. Not all DLLs have an entry point, so for example our Hello
program when we linked it, we said no entry, I don't
want an entry point. And then if we look at the headers there we saw there's a null
pointer for the entry point. So there's no entry, so basically
the loader will see that and it'll say, "All right, you don't need "to be initialized, I don't
need to do anything special." So let's build a little
DLL with an entry point. Here I have just a
simple DLL main function that just prints out a message when it gets called for process
attach or process detach and then returns true. So when we compile that,
we basically instead of passing no entry, we pass /ENTRY with the entry point that
we want the loader to call. So we'll write a little test
program to demonstrate it. And so here basically we just print out I'm about to load the DLL. I load it, I then say, "All right, I've loaded the
DLL, I'm about to unload it." I call the FreeLibrary
and then I print out, all right, I've unloaded it. We link that, and then we run it, we should see, all right,
we're in the main function, about to load the DLL. We're now loading the
DLL, so we're in DLL main for the process attach, which
it prints out correctly. We're back in the main function, the DLL has completed loading. The DLL main gets called again
when we call FreeLibrary, because it's going to free
the DLL and unload it, and then we're back in main
at the end of the program. So if you write entry points you do have to be extremely careful,
so MSDN has an article on dynamic-link library best practices. The short version is do
as little as possible as you have to when your DLL gets loaded. Be very careful when
calling into other DLLs from your entry point because they may not have been initialized themselves, and do not sychronize with other threads from your entry point because again the entry point is called
but the loader lock held, so you often end up with deadlocks if you try synchronizing
with other threads. In general, in C and C++ programs you won't specify your own entry point. We'll look at this in a little bit when we discuss C++ specific things. But instead, you'll let the C run-time provide the entry point,
and you can define DLL main which it will call. All right, so that's what it has to do in order to load a DLL, but what happens if something fails, something
doesn't work correctly? How do you diagnose
what went wrong, right? So LoadLibrary is just going
to return a null pointer and a status code. How do you get more information? So I know this talk is
early in the morning. If you've fallen asleep,
now would be a good time to wake up, because this is the most important thing I'm
going to share with you today. I know a lot of people
don't know about this, but it makes debugging
this sort of thing so easy. So what I'm going to do is,
I'm going to delete Hello.dll so that when we run our
PrintGreeting program, oh, well it doesn't print anything out. And if we look at the error level, it's that happens to be the code
for an access violation and the reason for that is, well it failed to load the DLL and we didn't
have any error programming in our test program, so it's
just everything's failed. So how do we find out what went wrong? What we're going to do is
the Windows debuggers package comes with this tool called Gflags. So we're going to run Gflags
and we're going to say that when our program
runs, show loader snaps. So then if we open our
program in the debugger, basically the loader is
going to print out lots of information, basically
all of the things that the loader is doing. So when it starts up, it's going to say, "Hey, I've just started
running this process," so it's going to just give
you a little information. So here's what it's going
to print when it tries to load Hello.dll, which is going to fail. So it says, "All right, I've started, "I'm trying to load Hello.dll." I'm going to see if it's a known DLL. It says, "Nope, it's not a
known DLL, that's not it." So then it starts trying
to search the search path, and it actually tells you the setup paths that it's going to try
and search for this DLL. You'll notice this
matches that search order that we were looking at earlier. It's the DLL which our program is, it's the System32 directory,
the system directory, the Windows directory and so
on, until it gets to my path. Then it's going to try each of those. It's going to say, "All right,
can I load A;\Hello.dll?" Nope, that failed. "Can I load System32 Hello.dll?" Nope, that failed. And so on until it gets to the end. This will actually tell you every step that the loader is going to try and do to load this DLL for you,
so you can kind of see what went wrong, right? You can see, oh the DLL
wasn't in the right location, or I've got my paths
configured incorrectly. Whatever, this tells you
exactly what the loader did, and you can compare that against what you were expecting it to do and figure out what went wrong. Similarly, if we were to
relink our Hello program and we were to remove the export and then call GetProcAddress to try and get that export, it'll
print out a little warning that says well, when you
tried to call GetProcAddress, it wasn't there, so this
is how you can determine if a load fails because you were depending on an export that doesn't exist for some reason, this will tell you which
exports were missing. Then when you're done, you
just need to run Gflags again with -sls instead of +sls
to turn it off again. Now, you're probably thinking, "Oh, cool, "this is a great reason to
use the Windows debuggers," and since I work on the debuggers team, I'd like to say that,
but this actually works with any debugger. You have to run the
Gflags program separately but then in Visual Studio the output will show up there as well. Basically the loader
uses output debug string. All right, another
little feature there is. So we've already seen
_declspec(dllexport), which you can use inside of a source file so that the linker will know, "All right, "I'll just implicitly export this." So I don't have to pass/export
or things like that. There's also a DLL import, so you use this inside of programs that use a DLL. So what does this do? So, first I'll look at a program that calls these imports without
using _declspec(dllimport). So here's just a simple
program, our main function calls GetOne, there's no DLL import. We link this program
with the import library, and if we look at what the
main function ends up doing, it's just a call GetOne,
because it thinks, "All right, "this is just a function I need to call." And then GetOne just jumps
through that function pointer. So then if we use DLL import, it does things a little differently. Basically, this tells the compiler this function is going to be imported, so the compiler knows
when it's generating code for the main function, it can just do the indirect call directly there. It can avoid that extra thunk through the GetOne function stub
that it had in that binary. You might think, "All
right, this is a great idea! "We should totally, definitely
be using DLL import." Today, if you're using
Whole Program Optimization, or Link Time Code Generation, the linker can see right through all of this, and so it will actually do
this optimization for you, even if you don't have
the _declspec(dllimport), so it's not entirely
necessary today obviously, in unoptimized builds
or if you're not using Link Time Code Generation,
this would still be beneficial, but it's not really necessary
if you're actually using all the optimizations you should be using. So we've seen exporting functions,
you can also export data. So here, instead of
exporting GetOne and GetTwo, we can export variables named One and Two, and to do that you use exactly the same methods that we did for
exporting functions, except you should add ,DATA after it or in the def file I think
there's just no comma, you just say DATA. And then, if we
dumpbin/exports we see, great We can see there are RVAs
which point as we'd expect to, well there's a one, and there's a two inside of our DLL. And if we want to write a
program that uses these, so UseConstants we can see,
we just declare the variables, we compile it, and oh it doesn't link. Hmm, that's interesting. So, when you import data you actually have to use _declspec(dllimport). And the reason is that the variables aren't exported directly, they have to be exported by address. The export table only
knows about addresses, it doesn't know what an int is, so when you export an int,
what's in the export table is the address of that integer, so if were actually able
to bind the name One to that address, the types wouldn't match. You would think that that
pointer is the integer. And then we can see we can use it, One is 1; Two is 2. You can also get data
exports using GetProcAddress, but note that again, this is
what I was saying previously, the export is an int
const*, it's not an int. Anyway, that all works just the same. You can also delay load DLLs. So here if we have our DLL
with entry point again, this is again it just prints out when it gets called for a
process attach in detach. We're running out of time, so I'm going to flip through this a little quickly. Actually I'm just going
to skip this section. And get to C++ and
DLLs, so this is CppCon, I figured I should
probably talk a little bit about C++ and DLLs. So first off, you can
have global variables, global C++ variables in DLLs. But you have to use the
default entry point, so basically the CRT provides this entry
point named DllMainCRTStartup. If you provide your own DLL main, it will call that automatically, but the CRT entry point
does a bunch of other things that are necessary in
order to do C and C++ inside of your DLL. So during process attach, it'll if you've statically linked the CRT, it'll go and initialize that. It initializes the security cookie and other run-time check support. It runs constructors for global variables, it initializes atexit
support within the DLL. And then it'll call your DllMain. Finally during process detach, it calls atexit registered functions, it runs destructors for global variables and it'll shut down the CRT if necessary. And it also calls your DLL
main there for process detach. I do want to note, so because all of this is done inside of the entry point, all of the rules that I mentioned for implementing your own DLL main also apply to any global variables that have constructors inside of your DLL, so you can't synchronize
from with a constructor of a global variable in a DLL, because you'll end up deadlocking, or you'll probably end up
deadlocking with the loader lock. You can also export C++ functions, so here is a little example, we can have two add functions, one that takes ints,
one that takes doubles. We can link it, and we look at the exports and we get two of them. I don't know how to
de-mangle those in my head, so one of them is 1,
the other's the other. You can export C++ classes,
so here we've got a class with a couple of member functions. We can link it, and of
course classes aren't things that exist in the DLL,
all you actually have are all of the member functions that operate on the class. So when you export a
class it actually exports all of the member functions for it. So here we've got the
GetValue and Increment that our class has, we have the two constructors
that our class has, and then the compiler
has gone ahead for us and actually generated the copy and moved constructor for
us and exported those. So I have some advice for exporting C++ functions and classes - don't. So on Windows, there's
no standard C++ ABI, so you're very dependent on the compiler, there's no guarantee that
if you throw an exception from one DLL and you
catch it somewhere else that it's guaranteed to work unless you're using the
same run-time library. Basically when you're
exporting things from a DLL, you really want to make sure you're using a very stable ABI like C, or Com or WinRT. The three that are very
well-defined on Windows. So DLLs can have threads
and thread-local storage. I'm not going to have time for that. And actually, I think I'm just going to get to the end so that we
can take a few questions, so if you want to come to the microphones, I'll be happy to answer any
questions, but no heckling. (audience laughs) (audience applauds) Yes. - [Audience Member] Hi,
I'd just like to know, is there ways to prevent
reading string retrawls when you peek into your DLL? - From, pardon? - So, string retrawls,
do you find in your code? Pretty much dumped into a DLL as is and is readable when you peek into them? Can you avoid that? - So, obfuscation? - I'm guessing. - So there are tools to
obfuscate code inside of DLLs. There's a whole bunch of third party tools that can do that, that will try and hide what is actually going on under the cover. It'll go and move code around
to make it very difficult just to disassemble it. But at the end of the
day, if the machine's going to execute it, it's got to be there in some form. - Okay, thanks. - Yes. - [Audience Member]
Hello, I have a question. Is there any mechanism
that different processes can share the text segment from a DLL or that the data segment is
shared, like copy and write like in System 5? - Yes, so the question,
well I guess everyone heard the question, because
we're at the microphones. So yes, so what I said previously is that the loader will attempt with address space layout randomization, it will try and load each DLL
at a different base address each time the program's run. That's actually not entirely true, it will try and use a random base address, but it will try for any given
DLL, like our Hello.dll, it will try to load it
at the same base address in every process, so
that any read-only data and any text pages can be
shared between the processes. So you end up using virtual address space in your process, but actual system memory like that can all be shared, and
that's very critical for things like Kernel32, which is going to be loaded into 200 processes, we don't want 200 copies
of that sitting in memory. Additionally, any writeable
pages are marked copy on write, so basically DLLs will share them until the first time
they try to write to it, at which point the page gets copied, and all that happens behind the scenes in the memory manager,
so you don't actually have to worry about that in your own code. - So I (mumbles) It's only possible if
they are not reloaded to a different address, they
have to be the same address? - Right.
- I'm right? - Yeah, so the loader does try its best to put it at the same address. - But it's not possible
in another case he do. - Then it'll load it at a
different address in that process. - Okay, thanks. - Okay. - [Audience Member] Hello. - Hi. - So, if you were writing a library, would you recommend using
DLL export or avoiding it? - Yeah, so if I was writing a library would I recommend using DLL export? My preferred approach is to
use the module definition file, and the reason for that
is that you actually have one place where your
entire public contract of your DLL is defined, right? So you have one place you know, it's very easy to make
sure that you aren't accidentally losing exports, so let's say that you
have some things that are marked DLL.export, and you
accidentally stop compiling that, or maybe you've
purposely stopped compiling that file into your DLL,
you've probably just lost all of those exports
from your DLL as well, so you've made a breaking
change to your DLL. So the def file, makes sure
that that won't happen, because again, if the
symbols aren't there, you'll actually get a link error, it'll say you've removed this symbol. So basically if you want to maintain a stable contract for your
DLL, using the def file is probably the safest approach. - I was also thinking static
linking versus DLL linking. So if you don't use DLL export, you're avoiding mismatches. - Yes, so specifically for the C run-time, it marks functions as
DLL import in its header, if you're using the DLL run-time. And so if you have shared static libraries that use that functionality, basically static libraries
have to be re-compiled with different flavors of the C run-time in order to link correctly,
which is a big hassle, yeah. - [Audience Member] Hi James. As you may know, I'm a bit
concerned about portability, and my question is do
you think that standards should be something about loading and finding
stuff into shared libraries because when you try to do that on Unix and then on Windows it
gets very messy very quick. - Yes, I do have an opinion on that, and the answer is no, I
don't think the standards should standardize that, because
it's completely different on Linux and Windows. Yes, they both have shared libraries, they both contain code and
data that you can share, but the way that they
work, the way that their contracts are defined for your libraries, the way that lifetime is handled, the way that add exit
and globals are handled, they're all completely different. And so standardizing that,
I think it's just too late. We can't go and change how Windows does it because we've got 30 years
of software doing it one way. We can't go and change how Pozix does it, because again, same thing. So I don't really see any
benefit of standardizing that. I see opportunity for libraries to help paper over the differences, C++ libraries, but I don't think that's a
good fit for the standard. - [Audience Member] Hi
James, I have a question about the one part you skipped over. (mumbles) We think it quite extensively
and I was wondering was the rationale behind
deciding it's not going to be automatic unloaded when you unload the DLL loaded something? - Yes, that I actually
don't know the answer to that question but if you
come chat with me afterwards I can help find it. - Okay, thanks. - Yeah. - You skipped really fast over
two parts I'm interested in but I'll only ask about one of them. Can you just repeat what happens if I've got a global
variable and I initialize it and the constructor or whatever it is is calling something from a DLL. Is that a deadlock or what happens? Does it fix it for me? - It depends, so some
things are safe to call, so for example a lot of
the exports from Kernel32 to do initialization of
synchronization primitives, things like that are safe. Many of the C Runtime
APIs are safe to call. You just have to be extremely careful because if another program calls something that holds this lock
that you might depend on and then that in turn
tries to load a library and then basically you can end up with lock order and
versioning very easily. So you just have to be very careful with what you end up importing. If what you end up using
is global variables, so it's things from your own code, like maybe you know that
something will be safe, but if it's not from your own code you have to use extra care. So I know for Windows, basically there's a small set of functions from Kernel32 that are
guaranteed to be safe, and then we say try not
to call anything else. - Okay, so even if I'm
not doing anything weird with these LoadLibrary.exes but even if I just stick to the
standard C++ plus DLL import I can still end up in a deadlock if I'm not careful.
- Yes. Yep. Yes. - [Audience Member] Hi,
could you please elaborate on your advice not to
export classes from DLLs, because this is what hundreds and hundreds of worth of our projects are doing and it's very, very surprising to learn that it's not advised. - Yeah, so this will have
to be the last question, and then I can take more
questions afterward. So basically the C++ ABI on Windows, basically it's not specified. So the compiler does what it does, and each major release of the compiler, they may make breaking changes, so I know there's been
object layout changes in some versions of the compiler, there may have been name-mangling changes. I don't know if there's any developer on the compiler here, but I don't want to say something false. So basically any major
release, they can change that. And so it's okay to export
C++ things from a DLL, if it's just your one application package that is going to end up using it, right? So if you are using DLLs
solely for componentization within your application,
it's probably fine to use C++ across the boundary, as long as you make sure you're always
recompiling everything with the same toolset,
with the same options to pass things across. If you're trying to build
a reusable component, so something that a library
that someone might use in their own application, if you use C++ across that boundary, you basically need to make sure that whatever options you
use to build your DLL, they're using also on their side, so you might have to distribute
many versions of your DLLs for different versions of the toolset. You might have to deliver
different versions for different compile options,
so that standard library has different layout for different types under some options, so debug and release don't have the same layout for all types. So as long as you control
the full set of things that are exporting the C++ exports and then importing them on
the other side, it's safer, but if you have reusable components, you definitely want to use
some kind of stable ABI. All right, well thank you all for coming. Again, I'm happy to take
questions, I'll be here all week. (audience applauds)
Only one slide about exporting C++ classes from DLLs: "don't". That's too drastic IMO. On a C++ conference I was actually expecting to see a lot of advice about exactly that instead of low-level details about DLL loading that you'll rarely need.
A shame he had to skip the DLL hell section. I’m not sure he was going to cover it, but Sometime I need to understand what is going on in the winsxs folder.
1h+ format is really daunting for these talks. Is it a requirement of cppcon? A lot of these talks can be done in 20-30min, which would be more useful for the viewers.
Is there a (Microsoft) expert that can say something about sharing std types across dll boundaries in the microsoft implementation?
According to this article (https://support.microsoft.com/de-de/help/172396/you-may-experience-an-access-violation-when-you-access-an-stl-object-t from 2005!) it doesn’t work with certain types that include use static variables inside the dll, eg std::map. I tried and it seems to work now.
(All questions assume dlls are build the same version/options)
Can std library types passed across the boundary?
What about allocators and destruction? If I use make shared and pass the smart pointer that should work in all cases?
If a class passed over the boundary includes a private value member from std and that member get only accessed through functions inside the class that should work in all cases too?
I hope there will be a part 2.