Corey Ogburn
Corey Ogburn

Reputation: 24717

Writing C++ intended to be called from C#?

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.

Upvotes: 3

Views: 1411

Answers (3)

Hans Passant
Hans Passant

Reputation: 941465

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:

ordinal hint RVA      name

      1    0 000110EB ?HelloWorld@@YAXXZ = @ILT+230(?HelloWorld@@YAXXZ)

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:

ordinal hint RVA      name

      1    0 00011005 HelloWorld = @ILT+0(_HelloWorld)

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:

extern "C" __declspec(dllexport) 
void __stdcall HelloWorld() {
    // etc..
}

And the Dumpbin.exe output now looks like:

ordinal hint RVA      name

      1    0 000110B9 _HelloWorld@0 = @ILT+180(_HelloWorld@0)

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.

Upvotes: 7

Yaur
Yaur

Reputation: 7452

There ae basically two things that are going to influence name mangling which is why you are having trouble importing the function which are if extern "C" is around your function definition and the calling convention of your function.

extern "C" with a cdecl calling convention will give you a clean name that is easy to import, but you will need to add the calling convention to the DllImportAttribute.

Upvotes: 1

Kiril
Kiril

Reputation: 40345

This is probably the best way to do it: How to import and use a unmanaged C++ class from C#?

I would recommend that you create a C++/CLI project which statically links with your pure C++. The C++/CLI project will generate the DLL and you will use it just like any other DLL in C#. Again, see the link above.

Upvotes: 1

Related Questions