从C#调用C ++函数,具有许多复杂的输入和输出参数

我是C#的新手,但他在C ++方面做了很多工作。 我有一个需要从C#调用的C ++函数。 在阅读了SO和一些谷歌搜索的一些答案之后,我得出结论,我需要为该函数创建一个纯C接口。 我已经这样做了,但我仍然对如何从C#调用它感到困惑。

C ++中的函数如下所示:

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

用C语言转换的相同函数如下所示:

 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 ); 

这是基于这个建议。

现在我需要从C#调用它,这是混乱的地方。 我尽力转换结构。

C#代码如下所示:

 [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 ); 

在C / C ++代码中完成所有输出变量(指针)的内存分配。 这可能意味着我们需要声明代码不安全,对吗?

我们如何处理内存释放? 我应该编写另一个API(函数)来解析由C / C ++分配的对象/数组吗?

C ++代码需要符合标准且与平台无关,因此我无法在其中插入任何特定于Windows的内容。

我希望有人能理解这一点并提供答案,或者至少指出我正确的方向。

由于似乎对使用It Just Works(IJW)和C ++ / CLI感兴趣,我将发布一些有关这方面的信息,需要进行进一步的谷歌搜索和研究来完成所有工作。 可以使用单个编译器标志启用C ++ / CLI(/ CLI,通过“属性页” – >“通用” – >“公共语言运行时支持”启用)。 C ++ / cli 不是 c ++,而只是另一种托管语言。 C ++ / CLI类可以编译成dll,并直接从其他.NET项目(C#,VB.NET,ect)调用。 但是,与其他.NET语言不同,它可以直接与C ++代码交互。

这是学习C ++ / CLI的好开始。 要学习的最重要的事情是告诉你类是管理的(.NET类)而不是Vanila C ++的装饰。 “ref”关键字将定义decal为.NET定义:

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

所有引用类都使用Handles而不是指针或引用引用。 句柄由^运算符表示。 要创建一个新句柄,可以使用gcnew,要访问句柄的函数/成员,请使用 – >运算符。

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

您经常需要将C#中常见的结构移动到本机类型,然后再移回。 (例如托管数组^或字符串^到void *或std :: string)。 这个过程叫做Marshaling。 这个方便的表格非常有用。

一个常见的任务是为本机类创建一个包装器,如下所示:

 //Foo.h #include  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(_ptr->bar(marshal_as(b))); } }; } 

警告:我完全错过了一堆#include和#using语句,这只是为了给出如何使用它的一般要点。

从这开始:

  • 如何从C#中将结构作为指针传递给C dll

还有一些关于编组的内容:

  • 深度复制数组c#而不进行序列化

请注意, Marshal.Copy也会为数组使用重载。 通过编组,除了你想要的之外,你可以摆脱ref 。 只是以他们的方式编写C / C ++。

以下是有点复杂:

  • 物理磁盘大小不正确(IoCtlDiskGetDriveGeometry)

我经常看到的两种方法是使用’FreeResource’样式API,或者在函数中指定输出缓冲区的大小。

方法1

C ++

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

客户(任何语言)

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

方法2

C ++

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

客户(任何语言)

 char * name = new char[20]; GetName(name, 20); ... 

如果您愿意使用第三方工具,有一个名为C#/ .NET的工具PInvoke Interop SDK可能对您有所帮助。 但你也可以自己做。 对于使用几种方法的简单类,您可以在托管C#代码中编写自己的代码。

从.NET世界实例化C ++对象的基本思想是从.NET分配C ++对象的确切大小,然后调用从C ++ DLL导出的构造函数来初始化对象,然后你就可以调用任何一个访问该C ++对象的函数,如果任何方法涉及其他C ++类,您也需要将它们包装在C#类中,对于具有基本类型的方法,您可以简单地P / Invoke它们。 如果你只有几种方法可以调用,那很简单,手动编码不会花费很长时间。 完成C ++对象后,可以调用C ++对象的析构函数方法,该方法也是导出函数。 如果它没有,那么你只需要从.NET中释放你的记忆。

这是一个例子。

 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); } } 

有关详细信息,请参阅以下链接,

C#/。NET PInvoke Interop SDK

该工具xInterop NGen ++ 2.0已经发布。 如果您有兴趣为本机C ++ DLL创建C#包装器,请查看它。

(我是SDK工具的作者)