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.
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.
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:
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);
...
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,
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)
来源:https://stackoverflow.com/questions/15672351/calling-c-function-from-c-with-lots-of-complicated-input-and-output-paramete