So I am doing this as a learning moment and I’m not afraid to say I have no idea what I’m doing here. It might also be worth mentioning that I don’t know much about C++ in this scenario.
In C#, I’ve used DllImport plenty of times to bring in stuff from user32.dll or other DLLs that I haven’t written, but I’m looking to better understand how the other half (the C++ half) is implemented to make this happen.
The C++ code I have is simple and just to verify that the call went through successfully:
#include <iostream>
using namespace std;
__declspec(dllexport) void HelloWorld() {
cout << "Hello, World" << endl;
}
I don’t know what the importance of __declspec(dllexport) is, but I’ve seen it on a couple websites that didn’t touch much on its importance.
My C# isn’t very different than previous DllImports I’ve done before:
[DllImport("TestDLL.dll")]
static extern void HelloWorld();
static void Main(string[] args) {
HelloWorld();
}
I’m compiled the C++ DLL and put it in the C# project and it’s copied to the bin folder. When I run the C# project I get an EntryPointNotFoundException at the call to HelloWorld() inside the main function.
My guess is that I need to either change the C++ code or the compilation flags of the C++ project. Currently “Use of MFC” is set to “Use Standard Windows Libraries” and there’s no use of ATL or CLR. Any help would be greatly appreciated.
C++ is a language that supports overloading. In other words, you can have more than one version of HelloWorld(). You could also export HelloWorld(int), a distinct version. It is also a language that requires a linker. In order to not confuzzle the linker about the same name for different functions, the compiler decorates the name of the function. Aka “name mangling”.
The tool you want to use to troubleshoot problems like this is Dumpbin.exe. Run it from the Visual Studio Command Prompt on your DLL with the /exports option. You’ll see this:
Bunch of gobbledegook, the exported name is shown in parentheses. Note the ? in the front and @@YAXXZ after the name, that’s why the CLR cannot find the exported function. A function that takes an int argument will be exported as ?HelloWorld@@YAXH@Z (try it).
The [DllImport] directive supports this, you can use EntryPoint property to give the exported name. Or you can tell the C++ compiler that it should generate code that a C compiler can use. Put
extern "C"in front of the declaration and the C++ compiler will suppress the name decoration. And won’t support function overloads anymore of course. Dumpbin.exe now shows this:Note that the name is still not plain “HelloWorld”, there’s an underscore in front of the name. That’s a decoration that helps catch mistakes with the calling convention. In 32-bit code there are 5 distinct ways to call a function. Three of which are common with DLLs, __cdecl, __stdcall and __thiscall. The C++ compiler defaults to __cdecl for regular free functions.
This is also a property of the [DllImport] attribute, the CallingConvention property. The default that’s used if it isn’t specified is CallingConvention.StdCall. Which matches the calling convention for many DLLs, particularly the Windows ones, but doesn’t match the C++ compiler’s default so you still have a problem. Simply use the property or declare your C++ function like this:
And the Dumpbin.exe output now looks like:
Note the added @0, it describes the size of the stack activation frame. In other words, how many bytes worth of arguments are passed. This helps catch a declaration mistake at link time, such mistakes are extremely difficult to diagnose at runtime.
You can now use the [DllImport] attribute as you originally had it, the pinvoke marshaller is smart enough to sort out the decoration of the actual function. You can help it with the ExactSpelling and EntryPoint properties, it will be slightly quicker but nothing you’d ever notice.
First question last: __declspec(dllexport) is just a hint to the compiler that you intend to export the function from a DLL. It will generate a wee bit of extra code that can help making the exported function call faster (nothing the CLR uses). And passes an instruction to the linker that the function needs to be exported. Exporting functions can also be done with a .def file but that’s doing it the hard way.