CppCon 2017: James McNellis “Everything You Ever Wanted to Know about DLLs”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

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.

👍︎︎ 23 👤︎︎ u/zvrba 📅︎︎ Oct 13 2017 🗫︎ replies

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.

👍︎︎ 3 👤︎︎ u/Wriiight 📅︎︎ Oct 13 2017 🗫︎ replies

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.

👍︎︎ 7 👤︎︎ u/zerexim 📅︎︎ Oct 13 2017 🗫︎ replies

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?

👍︎︎ 3 👤︎︎ u/oximilion 📅︎︎ Oct 14 2017 🗫︎ replies

I hope there will be a part 2.

👍︎︎ 1 👤︎︎ u/kalmoc 📅︎︎ Oct 13 2017 🗫︎ replies
Captions
- 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)
Info
Channel: CppCon
Views: 69,906
Rating: 4.9175 out of 5
Keywords: James McNellis, CppCon 2017, Computer Science (Field), + C (Programming Language), Bash Films, conference video recording services, conference recording services, nationwide conference recording services, conference videography services, conference video recording, conference filming services, conference services, conference recording, conference live streaming, event videographers, capture presentation slides, record presentation slides, event video recording
Id: JPQWQfDhICA
Channel Id: undefined
Length: 62min 27sec (3747 seconds)
Published: Thu Oct 12 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.