Search code examples
c#c++dllinteroppinvoke

Calling C++ function from C#, with lots of complicated input and output parameters


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.


Solution

  • 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.