blah blah blah is here! blah blah » Close

up0down
link

As a test, I am writing a small C++ .DLL to be invoked from C#. Reading MSDN it looks like I need a C# wrapper which is also to be a .DLL.

So in all a C# program as an .EXE, invokes a wrapper which is a C# .DLL, which in turn invokes the C++ DLL.

My problem is that at runtime the invoking program gives an error "unable to find an entry point named DoStuff", where DoStuff is the C++ method.

I think it might be because DoStuff is a method in a class and the method is declared with the class name in front. So in the .h include file:

public class Half
{
Half();
~Half();
double DoStuff(double arg);
}

In the .cpp file:

double Half::DoStuff(double arg)
{
return arg/2;
}

I am not sure if the Half:: qualifier is the cause of my problem but the trouble is that if I do not preface the declaration with Half::, then the C++ will not compile.

Please help.

last answered one year ago

1 answers

up0down
link

As DoStuff() is an instance function, you need a pointer to a C++ Half object so that you can P/Invoke the function using the 'ThisCall' calling convention which passes 'this' as the first parameter. However, AFAIK*, there's no direct way to P/Invoke Half's instance constructor using the 'new' operator to get the pointer you need.

You'd be better really creating a mixed mode C++/CLI dll to wrap your unmanaged C++ dll and then consume that from C#.

If you don't fancy that, then one thing you could do is to write a static 'Create' function for the Half class which calls the instance constructor internally and then returns a pointer to the instance to the caller. You should then be able to P/Invoke the static function OK.

(* There is in fact a way - see Second Edit below)

EDIT

I thought I'd see if I could get an example working of adding static Create and Destroy functions to your C++ dll so that you can then call instance functions directly from a C# program.

// Half.cpp to be compiled to Half.dll
#include "stdio.h"

class Half
{
private:
double field;

public:
Half();
~Half();
double __declspec(dllexport) DoStuff(double arg);
void __declspec(dllexport) DoMoreStuff(unsigned char* s);
};


Half::Half()
{
field = 3.0;
}

Half::~Half()
{
}

double Half::DoStuff(double arg)
{
return (arg + field)/2;
}

void Half::DoMoreStuff(unsigned char *s)
{
printf("%s\n", s);
}

extern "C" __declspec(dllexport) Half *Half_Create()
{
return new Half();
}

extern "C" __declspec(dllexport) void Half_Destroy(Half *hp)
{
delete hp;
}

As you'll see I've made the C++ dll a little more interesting by adding a field and another instance function to the Half class.

I've also created global (rather than static) Create and Destroy functions so that we can export them using C linkage which prevents the names from being "mangled".

However, this isn't possible for the instance functions whose names are therefore mangled and, in the C# code below, I've used their 'ordinal' numbers within the dll (you can get these from Dumpbin or Dependency Walker) rather than their mangled names as the entrypoint:
using System;
using System.Runtime.InteropServices;

class Test
{
[DllImport("Half.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint="#1")]
static extern void Half_DoMoreStuff(IntPtr pHalf, string s);

[DllImport("Half.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint="#2")]
static extern double Half_DoStuff(IntPtr pHalf, double arg);

[DllImport("Half.dll")]
static extern IntPtr Half_Create();

[DllImport("Half.dll")]
static extern void Half_Destroy(IntPtr pHalf);

static void Main(string[] args)
{
double arg;
if (args.Length > 0)
{
double.TryParse(args[0], out arg);
}
else
{
arg = 0.0;
}

IntPtr pHalf = Half_Create();
double result = Half_DoStuff(pHalf, arg);
Half_DoMoreStuff(pHalf,"Hello from C++ dll");
Half_Destroy(pHalf);
Console.WriteLine("Half of ({0} + 3) is {1}", arg, result);
Console.ReadKey();
}
}

Other than that I think you'll find the code, which works fine, reasonably self-explanatory.

SECOND EDIT

I've been looking into this some more and there is in fact a way to do this without adding global Create and Destroy functions to your C++ dll. However, you need to do your own memory management and therefore need to know how much memory the C++ object requires on the unmanaged heap. You can then use a pointer to this memory to P/Invoke the constructor and, when you're done, P/Invoke the destructor and free the memory.

In this case the C++ class only has one field - a double - and so 8 bytes of memory is needed.

Here's the code on this basis. Notice that the ordinal numbers of the two instance functions have changed now that we're exporting the constructor and destructor as well:
// Half2.cpp to be compiled to Half2.dll
#include "stdio.h"

class __declspec(dllexport) Half // now exporting all functions
{
private:
double field;

public:
Half();
~Half();
double DoStuff(double arg);
void DoMoreStuff(unsigned char* s);
};

Half::Half()
{
field = 3.0;
}

Half::~Half()
{
}

double Half::DoStuff(double arg)
{
return (arg + field)/2;
}

void Half::DoMoreStuff(unsigned char *s)
{
printf("%s\n", s);
}


// C# consumer of Half2.dll
using System;
using System.Runtime.InteropServices;

class Test
{

[DllImport("Half2.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint="#1")]
static extern void Half_Constructor(IntPtr pHalf);

[DllImport("Half2.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint="#2")]
static extern void Half_Destructor(IntPtr pHalf);

[DllImport("Half2.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint="#4")]
static extern void Half_DoMoreStuff(IntPtr pHalf, string s);

[DllImport("Half2.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint="#5")]
static extern double Half_DoStuff(IntPtr pHalf, double arg);

static void Main(string[] args)
{
double arg;
if (args.Length > 0)
{
double.TryParse(args[0], out arg);
}
else
{
arg = 0.0;
}

IntPtr pHalf = Marshal.AllocHGlobal(8);
Half_Constructor(pHalf);
double result = Half_DoStuff(pHalf, arg);
Half_DoMoreStuff(pHalf,"Hello from C++ dll");
Half_Destructor(pHalf);
Marshal.FreeHGlobal(pHalf);
Console.WriteLine("Half of ({0} + 3) is {1}", arg, result);
Console.ReadKey();
}
}

In practice, unless you only wanted to call a couple of functions, you'd probably want to use this technique to wrap your C++ class into a C# class which implemented IDisposable etc. and would ensure you didn't miss out any of the steps.

Feedback