Calling C++ unmanaged member functions from C# whe

2020-06-29 05:56发布

问题:

So I have an unmanaged DLL that exports only a C style factory method that returns a new instance of a class (simplified here to look simple).

hello.h

#if defined(HWLIBRARY_EXPORT) // inside DLL
#   define HWAPI   __declspec(dllexport)
#else // outside DLL
#   define HWAPI   __declspec(dllimport)
#endif

struct HelloWorld{
   public:
    virtual void sayHello() = 0;
    virtual void release() = 0;
};
extern "C" HWAPI HelloWorld* GetHW();

hello.cpp

  #include "hello.h"

struct HelloWorldImpl : HelloWorld
{
    void sayHello(){
        int triv;
        std::cout<<"Hello World!";
        std::cin>>triv;
    };
    void release(){
        this->HelloWorldImpl::~HelloWorldImpl();
    };
};
HelloWorld* GetHW(){
    HelloWorld* ptr = new HelloWorldImpl();
    return ptr;
};

Now, I can use dllimport to access GetHW() but is there a way to access the member functions of the returned 'struct'... ie, sayHello and release?

回答1:

Maybe there are some other ways and tricks, but since your class is not a POD the safe and tried way for me is to export each member function as a nonmember function with an additional pointer argument.

extern "C" HWAPI HelloWorld* GetHW();
extern "C" HWAPI void SayHello(HelloWorld* p); //{p->SayHello();}
extern "C" HWAPI void Releasr(HelloWorld* p); //{p->Release();}

In C#, you import all these, then create a wrapper class that will do something like this:

class HelloWorld
{
   public HelloWorld() {ptr = Imports.GetHW();}
   public void SayHello {Imports.SayHello(ptr); }
   public ~HelloWorld() {Imports.Release(ptr);}
   private IntPtr ptr;
}


回答2:

Ok... the matter is complicated. Really complicated.

I should warn that another method to access these methods would be preferred. Create a C function for each method or use COM. Choose what method your prefer. Both methods require some work on the C++ side.

Since we are trying to answer the question now we try to call a method from a C++ class.

First problem:

You need to decorate all classes or class methods with __declspec(export). If a symbol is not exported there is no way you can access that symbol. Of course this don't works well with templates.

Second problem:

Class methods use calling convention __thiscall. Default P/Invoke requires __stdcall convention.

However you can change the attribute CallingConvention to CallingConvention.ThisCall in DllImportAttribute, so problem solved.

Third problem:

Every C++ compiler have its own way to generate exported C++ members names!

First in this post I assume the DLL was compiled with Microsoft Visual C++! Don't know with other compilers what may happen.

DllImport attribute requires the exact name of the method. Since it is generated by the C++ compiler there is absolutely no easy way to recover the name back without looking at the DLL export table (this is due virtuals, overloads, namespaces, class names etc. etc.).

The only way I know to fetch the method name is to use a dll dump tool, for example, i use often "dumpbin.exe /EXPORTS file.dll". This will print out your dll methods. You can use rtti too to print method names, this can help you to map dll exports with your code.

Now we can write our dllimport. For example.

[DllImport(
    "MyDll.dll",
    EntryPoint="?MyFunction@CMyClass@@QAEXH@Z",
    CallingConvention=CallingConvention.ThisCall)]
public static extern void MyFunction(IntPtr myClassObject, int parameter);

The EntryPoint attribute is the exact name of your exported method.

Ok we are done, but let's analyze the problems in doing that:

Fourth problem

There is not an official document from microsoft that explain how exactly method names for c++ exports are generated and for this reason exported names can change if you modify your C++ source code.

This is the biggest headache you may have following this road!

Fiveth problem

Garbage collector and safe object disposal... Instead of IntPtr, use SafeHandles, both if you are going to use C functions or if you are using the C++ way.

You can define a SafeHandle that represent the pointer to your C++ object. This SafeHandle must call the destructor of your C++ object in the overridden ReleaseHandle function.

Conclusion

As I said before just using C functions or COM objects would be easier, more portable and less tedious.

COM interop was built to avoid problems with native C++ compiler features. Com is an old technology, have a lot of problems and weird things but still works, is still supported and will be supported for a long long time.

If your object graph in C++ is going to be quite complicated, use COM. C# tlbimp tool automatically generates C# classes that bind to COM objects on the fly, without the need to do any DllImport.

If you need simple stuff, just use plain C.



回答3:

I was also stuck with the same issue. When i googled, able to find out two solutions.

Solution1: Expose all the member functions in the C-style.

Solution2: Write a managed C++ dll exposing the functionality of native C++ dll, which later can be used in your C# dll.

Its been three years this question was asked. Any better solution other than the above two solutions?

Sorry if there are any mistakes in the post.