There are two PCs with Visual Studio 2017 installed. I’m running a simple program on both of them, one that lists the name of modules (exes/DLLs) inside its own process.
But I get wildly different results. On one PC, I only get 7 modules:
Lab7_1.exe
ntdll.dll
KERNEL32.DLL
KERNELBASE.dll
MSVCP140D.dll
VCRUNTIME140D.dll
ucrtbased.dll
On the other, I get whopping 31 modules. The full list includes, for example, user32.dll, which my sample program isn’t using (it’s a console app, not a GUI app).
So the question is: what exactly affects the list of DLLs imported by default? Debug/Release and x86/x64 switches produces some difference, but nothing that drastic.
Differences between platform tools versions (and corresponding versions of MS VC++ Redist) I can understand, but why are different system DLLs being imported as well?
I’m unsure where else to look.
Context: it’s a part of an assignment. On of those PCs is mine, the other is where the students work. The assignment goes like this "We have this set of modules by default, now we use MessageBoxA()
, and we see that more modules are imported, user32.dll among them". Which doesn’t quite work if user32.dll is always imported by default.
Since the behaviour is vastly different, and I can’t reproduce it on my PC, it’s hard to adapt the assignment so the students can see import mechanics at work.
Sample program code:
#include <iostream>
#include <vector>
#include <string>
#include <Windows.h>
#include <Psapi.h>
using namespace std;
#pragma comment(lib, "psapi.lib") //needed for MS VS 2010
void EnumerateModules(vector<HMODULE>& modules)
{
HANDLE me = GetCurrentProcess();
DWORD needed_size = 0;
while (true)
{
DWORD actual_size = modules.size() * sizeof(HMODULE);
EnumProcessModules(
me, //which process
modules.data(), //where to put the module handlers
actual_size, //allocated buffer size
&needed_size //desired buffer size
);
if (needed_size != actual_size)
modules.resize(needed_size / sizeof(HMODULE));
else
break;
}
}
string ModuleName(HMODULE module)
{
HANDLE me = GetCurrentProcess();
string buffer(FILENAME_MAX, 0);
DWORD real_length = GetModuleBaseNameA(
me, //which process
module, //which module
&buffer[0], //where to put the name
buffer.size() //size of the name buffer
);
if (real_length > 0)
return buffer.substr(0, real_length);
buffer = "";
return buffer;
}
int main(int argc, char* argv[])
{
setlocale(0, "");
vector<HMODULE> modules;
EnumerateModules(modules);
cout << modules.size() << " modules:" << endl;
for (size_t i = 0; i < modules.size(); i++)
{
string name = ModuleName(modules[i]);
cout << name.c_str() << endl;
}
return 0;
}
2
Answers
Okay, turns out it's not related to Visual Studio settings. I compiled the sample code on one machine, ran in on the other, and got the same result as if I compiled the code there. So it's either Windows version (7 vs 10) or MS VS Redistributable version (though I think both projects used v140). In either case, I can't reproduce the behaviour on my machine unless I'm willing to set up a VM.
Win32 programs, by default, will import ALL their dependent DLLs up front before any code is executed. When you invoke
MessageBox
it MIGHT pull in other modules via LoadLibrary, but user32.dll is already loaded before that function is invoked.The project setting you are wanting is delay loading which will generate the behavior you are claiming will happen. That is, the DLL won’t effectively be loaded until the function is actually invoked.