3

I am new to C# but have worked extensively with C++. I have a C++ function that needs to be called from C#. After reading some answers from SO and some googling, I conclude that I need to make a pure C interface to the function. I have done this, but am still confused about how to call it from C#.

The function in C++ looks like this:

int processImages(
    std::string& inputFilePath,                      // An input file
    const std::vector<std::string>& inputListOfDirs, // Input list of dirs
    std::vector<InternalStruct>& vecInternalStruct,  // Input/Output struct
    std::vector<std::vector< int > >& OutputIntsForEachFile,
    std::vector< std::vector<SmallStruct> >& vecVecSmallStruct, // Output
    int verboseLevel
    );

The same function, converted in C, looks like this:

int processImagesC(
    char* p_inputFilePath,               // An input file
    char** p_inputListOfDirs,            // Input list of dirs
    size_t* p_numInputDirs,              // Indicating number of elements
    InternalStruct* p_vecInternalStruct, // Input/Output struct
    size_t* p_numInternalStructs, 
    int** p_OutputIntsForEachFile,       // a 2d array each row ending with -1
    size_t* p_numOutputIntsForEachFile //one number indicating its number of rows
    SmallStruct** p_vecVecSmallStruct,   // Output
    size_t* p_numInVecSmallStruct,
    int verboseLevel
    );

This is based on this advice.

Now I need to call this from C#, which is where the confusion is. I have tried my best to convert the structures.

The C# code looks like this:

[DllImport(
    @"C:\path\to\cppdll.dll", CallingConvention=CallingConvention.Cdecl, 
    EntryPoint="processImagesC", SetLastError=true)]
[return: MarshalAs(UnmanagedType.I4)]
unsafe public static extern int processImagesC(
    String inputFilePath,
    String[] inputListOfDirs,
    ref uint numInputListOfDirs,

    // Should I use ref InternalStruct * vecInternalStruct?
    ref InternalStruct[] vecInternalStruct, 

    ref uint numInternalStruct,

    // Or ref int[] or ref int[][] or int[][]?
    ref int[][] OutputIntsForEachFile, 

    ref uint numOutputIntsForEachFile,

    // again, ref ..[], [][], or ref [][]?
    ref SmallStruct[][] vecVecSmallStruct, 

    int verboseLevel
);

There are memory allocations for all the output variables (pointers) done within the C/C++ code. This likely means we need to declare the code as unsafe, correct?

How do we handle memory deallocation? Should I write another API (function) that does the deallocation of objects/arrays allocated by C/C++?

The C++ code needs to be standard compliant and platform independent, so I cannot insert any windows-specific things in it.

I hope someone could make sense of this and provide an answer or at least point me in the right direction.

Community
  • 1
  • 1
KMS
  • 766
  • 1
  • 7
  • 10
  • You should NEVER call C++ from C#. The name mangling format is not garenteed to be the same between compiles. – Cole Tobin Mar 28 '13 at 01:09
  • 5
    If it's anything too complex you could create a managed C++/CLI wrapper around the native C++ to make it easier to call from C#. – Dustin Kingen Mar 28 '13 at 01:43
  • 1
    Going to have to agree with the C++/CLI wrapper approach. There are some utilities in the framework for converting to/from STL types. – Sion Sheevok Mar 28 '13 at 01:58
  • @Romoku can you explain how I can go about creating this managed C++/CLI wrapper? I have no experience with managed C++... – KMS Mar 28 '13 at 02:53
  • Sorry KMS I don't have any experience with it either, but I'll put a bounty on the question if it doesn't get answered properly. (If I can put a bounty on it since I don't see the bounty link anywhere.) – Dustin Kingen Mar 28 '13 at 02:58
  • 1
    C++/CLI is the answer. – David Heffernan Mar 28 '13 at 10:46

4 Answers4

4

Since there seems to be some interest in using It Just Works (IJW) with C++/CLI, I'll post some info about that, further google searches and research will need to be done to figure it all. C++/CLI can be enabled with a single compiler flag (/CLI, enabled through Property Page->General->Common Language Runtime Support). C++/cli is NOT c++, but rather just another managed language. C++/CLI classes can be compiled into dll's and called directly from other .NET projects (C#, VB.NET, ect). However, unlike the other .NET languages it can directly interact with C++ code.

This is an ok start to learning C++/CLI. The big thing to learn is the decorations that tell you the class is managed (.NET class) and not Vanila C++. The "ref" keyword decalres the definition as a .NET definition:

public ref class Foo{ public: void bar();};//Managed class, visible to C#
public ref struct Foo{};//Managed struct, visible to C#

All reference classes are referred to with Handles rather than pointers or references. A handle is denoted by the ^ operator. To make a new handle, you use gcnew, and to access functions/members of the handle, use the -> operator.

//in main
Foo^ a = gcnew Foo();
a->bar();

You often have to move structures common from C# to native types and back again. (such as managed Array^ or String^ to void* or std::string). This process is called Marshaling. This handy table is pretty useful for figuring that out.

A common task is to create a wrapper for a native class, done like this:

//Foo.h
#include <string>
namespace nativeFoo
{
    class Foo
    {
     private:
        std::string fooHeader;
     public:
        Foo() {fooHeader = "asdf";}
        std::string Bar(std::string& b) {return fooHeader+b;}
    };
}
//ManagedFoo.h
#include "foo.h"
namespace managedFoo
{
    public ref class Foo
    {
        private:
             nativeFoo::Foo* _ptr;
        public:
             Foo(){_ptr = new nativeFoo::Foo();}
             ~Foo(){!Foo();}
             !Foo(){if (_ptr){delete ptr;ptr = NULL;}}

             String^ bar(String^ b)
             {
                 return marshal_as<String^>(_ptr->bar(marshal_as<std::string>(b)));
             }
    };
}

Warning: I am totally missing a bunch of #include and #using statements, this is just to give a general gist of how to use this.

IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • You did help him with std::string type, His function has complicated types like std::vector>&, std::vector>, he needs to access them from C# as well. Would you be able to do the same in C++/CLI? Thanks! – xInterop May 04 '13 at 03:26
  • @Dave, In general, it depends how you want to do that. You can only really marshal structs: classes (such as std::vector) are a little trickier. If you can get away with a deep copy to System::Generic::List, I suggest that: (`vector s; List^ l = gcnew List(); l->add((ManagedSmallStruct)s[0]);`). This can lead to performance issues for large arrays. Otherwise, you'll have to make a wrapper class (just like we did for Foo) for the vector, which gets old fast. – IdeaHat May 06 '13 at 13:38
  • 2 minor points: The compiler switch is `/clr`, not `/cli`. And C++/CLI is different from other .NET languages in that it can produce *mixed mode* assemblies, that contain both managed and native code. In your example `nativeFoo::Foo` compiles to native code. If you want to create a pure managed assembly using C++/CLI you would have to use `/clr:pure` or `/clr:safe`. – IInspectable Jan 02 '14 at 19:03
1

Begin from this:

And something about marshalling in this:

Note that Marshal.Copy also overloads for arrays use. With marshalling you can get rid of ref excepting that you do want to. Just write C/C++ in their way.

And following is a little bit complicated:

Community
  • 1
  • 1
Ken Kin
  • 4,503
  • 3
  • 38
  • 76
0

The 2 ways I've often seen this handled is to either have a 'FreeResource' style API, or specify in the function the size of output buffers.

Method 1

C++

void GetName(char ** _str)
{
    if (!_str)
        return; // error
    *_str = new char[20];
    strcpy(*str, "my name");
}

void FreeString(char * _str)
{
    delete str;
}

Client (any language)

char * name;
GetName(&name);
...
FreeString(name);

Method 2

C++

void GetName(char * _str, size_t _len)
{
    if (_len < 20)
        return; // error
    strcpy(str, "my name");
}

Client (any language)

char * name = new char[20];
GetName(name, 20);
...
Rollie
  • 4,391
  • 3
  • 33
  • 55
  • Your answer makes things more clear, however, it would help to see the specific C# code used for calling the dll function. – KMS Mar 28 '13 at 02:55
0

If you are willing to use third party tool, there is a tool named C#/.NET PInvoke Interop SDK may be helpful to you. But you can do it yourself as well. For simple classes with a few methods, you can write your own code in managed C# code.

The basic idea of instantiating a C++ object from .NET world is to allocate exact size of the C++ object from .NET, then call the constructor which is exported from the C++ DLL to initialize the object, then you will be able to call any of the functions to access that C++ object, if any of the method involves other C++ classes, you will need to wrap them in a C# class as well, for methods with primitive types, you can simply P/Invoke them. If you have only a few methods to call, it would be simple, manual coding won't take long. When you are done with the C++ object, you call the destructor method of the C++ object, which is a export function as well. if it does not have one, then you just need to free your memory from .NET.

Here is an example.

public class SampleClass : IDisposable
{    
    [DllImport("YourDll.dll", EntryPoint="ConstructorOfYourClass", CharSet=CharSet.Ansi,          CallingConvention=CallingConvention.ThisCall)]
    public extern static void SampleClassConstructor(IntPtr thisObject);

    [DllImport("YourDll.dll", EntryPoint="DoSomething", CharSet=CharSet.Ansi,      CallingConvention=CallingConvention.ThisCall)]
    public extern static void DoSomething(IntPtr thisObject);

    [DllImport("YourDll.dll", EntryPoint="DoSomethingElse", CharSet=CharSet.Ansi,      CallingConvention=CallingConvention.ThisCall)]
    public extern static void DoSomething(IntPtr thisObject, int x);

    IntPtr ptr;

    public SampleClass(int sizeOfYourCppClass)
    {
        this.ptr = Marshal.AllocHGlobal(sizeOfYourCppClass);
        SampleClassConstructor(this.ptr);  
    }

    public void DoSomething()
    {
        DoSomething(this.ptr);
    }

    public void DoSomethingElse(int x)
    {
        DoSomethingElse(this.ptr, x);
    }

    public void Dispose()
    {
        Marshal.FreeHGlobal(this.ptr);
    }
}

For the detail, please see the below link,

C#/.NET PInvoke Interop SDK

The tool, xInterop NGen++ 2.0 has been released. Please check it out if you are interested in creating C# wrapper for native C++ DLL.

(I am the author of the SDK tool)

xInterop
  • 277
  • 3
  • 9