Using a C++ library in C# winforms
Asked Answered
G

4

8

I'm trying to use the SPARK particle system in OpenTK.
my project contains the header files in folders, with just two header files which only includes the others, and the folders contain the source files too.
I have tried several approaches so far, but nothing has worked for me yet, those are what I've tried:

1. P/Invoke

This is writing some code in your C++ project which built the dll and then using the DllImport attribute in C# (which obviously needs using System.Runtime.InteropServices;). I discovered the hard way that this doesn't work with classes, it only works for methods outside classes, so this approach was ineffective.

2. Wrapper classes

This is writing a class that contains a pointer to the original class. I discovered that the difficulty actually arises from calling unmanaged code(no automatic memory management) from managed code, that's why wrapper classes are needed, and that's why you have to redefine methods' signatures and let them call the original methods.

Of course this has advantages, like naming the classes and methods in a better way, but the library is so big so you can see the effort of this.

3. Use of an automatic wrapper:

This is a good approach, especially with xInterop++. I was really optimistic about this and thought it would work, it says "give me the .h files and the dll and I'll build the .NET dll for you". Good but doing so gives an error; in brief:

You must make sure .h files and the dll are consistent and that the library works in a C++ project.

I have tried several things to deal with this error:

  1. Knowing what the dll contains: it is difficult as I learned from Googling and from this site, so my try failed.
  2. Putting header files in a new project and building it: received errors, fixed them, and then built the project and it worked well. I uploaded the dll file with the header files to xInterop. It then told the classes that were found but would then state that nothing was found! I searched and learned that the compiler must be told which classes are needed to be exposed by the dll by marking every class that is needed using the following statement:_declspec(dllexport).
  3. I used Find & Replace to fix this thing and tried again and classes were shown, so I launched xInterop and received the same error.
  4. It asked to ensure that the dll works. After verifying that the file worked I launched the program and linker errors were produced.

Here is where I'm stuck, these are the linker errors I get:

main.obj : error LNK2019: unresolved external symbol "void __cdecl SPK::swapParticles(class SPK::Particle &,class SPK::Particle &)" (?swapParticles@SPK@@YAXAAVParticle@1@0@Z) referenced in function "private: void __thiscall SPK::Pool::swapElements(class SPK::Particle &,class SPK::Particle &)" (?swapElements@?$Pool@VParticle@SPK@@@SPK@@AAEXAAVParticle@2@0@Z) main.obj : error LNK2001: unresolved external symbol "unsigned int SPK::randomSeed" (?randomSeed@SPK@@3IA) main.obj : error LNK2001: unresolved external symbol "unsigned long const SPK::NO_ID" (?NO_ID@SPK@@3KB) main.obj : error LNK2001: unresolved external symbol "public: static float const * const SPK::Transformable::IDENTITY" (?IDENTITY@Transformable@SPK@@2QBMB)

This is the code that produced those errors:

#include "Extensions/Emitters/SPK_RandomEmitter.h"

using namespace SPK;

int main()
{   
    RandomEmitter e;
    e.changeFlow(6);
    e.getFlow();
    return 0;
}

So that's my problem, I'm sorry for explaining too much but I've done a three days search without finding any solution.

PS:

the library is very big, so an automatic solution is a must.

Gonsalez answered 18/11, 2014 at 2:30 Comment(9)
I think you are going to have to learn how to do the interop yourself, might I suggest an excellent resource Expert C++/CLIMonomerous
@ChrisO I don't know why but amazon links doesn't open .Gonsalez
The is Expert C++/CLI by Marcus Heege. It might help you to know there are actually two flavors of P/Invoke, explicit and implicit. See this intro article on Wikipedia for a good starting point of the differences between these two types. Depending on the function signatures of SPK DLL, it might not be possible to use the explicit version of P/Invoke. One common example, if the functions use std::basic_string then you'll have to write a custom C++/CLI wrapper to do the marshaling.Monomerous
@ChrisO so no way around ? writing a custom wrapper is hard when you have a big library.Gonsalez
If you really need to use classes and you can't export them, you can export normal functions which act as wrappers to the class. Example: ideone.com/at1FT5Lemuroid
@AcidShout I can export them, but the above linker errors are producedGonsalez
@AcidShout anyway, the library is big so an automatic thing is a needGonsalez
I only found the name of the library in your question after googling the names in the linker error messages. It could be a good idea to present facts first and subjective opinions / war stories second.Upbeat
@niceman, I am the author of xInterop C++ .NET Bridge, you mentioned a link error when using xInterop. xInterop C++ .NET Bridge only uses .h, .hpp, those head files and the DLL itself to create wrapper, I am wondering how you used xInterop? I actually just downloaded Spark.DLL and Spark.h file and I was able to create the wrapper in a few minutes. I will be happy to help you out.Lazo
T
5

This is a very, very unfriendly C++ library to have to interop with. Scratch the idea that pinvoke can work, C++ classes require C++/CLI wrappers. There are a great many classes with many small methods. The library depends on composition to generate effects so any approach that tries to do the interop with a few God classes is a dead avenue.

The most significant hazard is that it heavily relies on multiple inheritance. Not supported in .NET, this will defeat any tool that auto-generate wrappers. Also note that it only supports OpenGL rendering, not a terribly popular graphics api on Windows.

The library is attractive, and has been around for quite a while, but nobody has successfully ported it to .NET yet. This is unsurprising. In my opinion, you don't stand a chance. Only a rewrite could work.

Trilley answered 18/11, 2014 at 2:30 Comment(1)
Well didn't notice the use of multiple inheritance, +1Gonsalez
D
4

PInvoke is the way to do what you are looking for. Doesn't matter if you have or do't have the code for that DLL so long you know the function signature.

Have a look at these articles from MSDN and code project that cover basics of PInvoke:

  1. Platform Invoke Tutorial
  2. P/Invoke Tutorial: Basics (Part 1)

Edit: There are tools that can possibly generate DllImport signature for you. I have NOT tried any of these myself. Have a look:

  1. P/Invoke Signature Generator
  2. Easiest way to generate P/Invoke code?
  3. This one
  4. http://www.swig.org/

Hope that helps.

Disseminule answered 18/11, 2014 at 4:47 Comment(1)
I can recommed SWIG, used many times, but generally for Java bindings of custom C++ libraries.Denticulation
M
1

If your native dll exports some classes, then I would strongly suggest creating another native DLL wrapper for the original one. It should export a few functions and no classes at all.

Exported functioned could be something like:

my_lib_create_context( void ** const ppContext );
my_lib_delete_context( void * const pContext );
my_lib_do_something( void * const pContext, T param1, T param2 );

Inside my_lib_create_context() create an instance of your class and pass the pointer back through the ppContext parameter. Inside my_lib_do_something() cast the pContext to a pointer of your class type and use it.

Also, when writing your wrapper, pay attention to calling convention, because you will need to pass that information to the .NET world (I think stdcall is default if not explicitly defined).

EDIT:
Regarding that part on how to do it:
Create a new C++ solution/project, select DLL type. Then add .def file to that project. Add to that file this:

EXPORTS
my_lib_create_context @1
my_lib_delete_context @2
my_lib_do_something @3

Then add some header file where you will put function signatures like this:

typedef void * SomeContext;

extern "C"
{
  int __stdcall my_lib_create_context( /* [ out ] */ SomeContext * ppContext );
  int __stdcall my_lib_delete_context( /* [ in ] */ SomeContext pContext );
  // TO DO: ... you get it by now...
}

Implement these functions in .cpp file. Once you are done, create a wrapper in C# for this DLL and use it.

Milldam answered 4/1, 2015 at 18:29 Comment(5)
I didn't understand, explain with example, how to write those three methodsGonsalez
Anyway I know this will produce the same linker errors I posted in my questionGonsalez
why not just create a c# wrapper instantly, why another dll. and I want to use the classes, I don't want to use just functionsGonsalez
@niceman, you need an extra wrapper because of the classes. You cannot use C++ classes in C# AFAIK. pContext will hold the address of your exported class. If you still don't get it, you should read some books before trying to solve this. But generally, it is a trivial task.Milldam
is there an automatic thing to do this extra wrapper, the library is big and I don't want to exclude functionsGonsalez
T
1

Hmm P/Invoke call GetProcessAdress .. so importing ABI problem is so so..

http://www.codeproject.com/Articles/18032/How-to-Marshal-a-C-Class here are your answer give credit to those guy

Tailwind answered 7/1, 2015 at 20:43 Comment(3)
thanks but again the library is big, is there an automatic(tried) thing ? and a tutorial to use that thing would be better.Gonsalez
#4804994Tailwind
thank you danbo but again , an automatic thing is a mustGonsalez

© 2022 - 2024 — McMap. All rights reserved.