我是新来的C#但与C ++了广泛的合作。 我有一个C ++函数,需要从C#调用。 阅读从SO一些答案和一些google搜索后,我的结论是我需要做一个纯粹的C接口的功能。 我已经这样做了,但我仍然感到困惑如何从C#调用它。
在C ++函数如下:
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
);
同样的功能,在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 ++代码内完成的所有的输出变量(指针)的内存分配。 这可能意味着我们需要申报代码为不安全,是否正确?
我们如何处理内存释放? 我应该写,做由C / C ++分配对象/阵列的解除分配的另一API(功能)?
C ++代码必须是符合标准的平台独立的,所以我不能在其中插入任何Windows特定的东西。
我希望有人能够理解这一点,并提供一个答案,或者至少指向我在正确的方向。
由于好像是在使用它只是工作(IJW)与C ++ / CLI一些利益,我会张贴有关的一些信息,进一步谷歌搜索和研究需要做推测这一切。 C ++ / CLI可以与单个编译器标记(/ CLI,通过属性页面 - 启用>通用>公共语言运行时支持)来启用。 C ++ / CLI是不是 C ++,而只是另一种托管语言。 C ++ / CLI类可被编译为DLL的,并直接从其他.NET项目(C#,VB.NET,ECT)调用。 然而,与其他.NET语言也可直接与C ++代码交互。
这是一个不错的开端,以学习C ++ / CLI。 学习最重要的事情就是告诉你类管理的(.NET类),而不是Vanila C ++的装饰品。 “参考”关键字decalres定义为.NET的定义:
public ref class Foo{ public: void bar();};//Managed class, visible to C#
public ref struct Foo{};//Managed struct, visible to C#
所有引用类与手柄,而不是指针或引用提及。 句柄是由^运算符表示。 为了使新的手柄,您使用gcnew,以及访问功能/手柄的成员,请使用 - >操作。
//in main
Foo^ a = gcnew Foo();
a->bar();
你经常需要从C#常见的结构转移到原生类型,然后再返回。 (如管理阵列^或字符串^到void *或std ::字符串)。 这个过程被称为封送处理。 这个方便的表格是搞清楚了这一点非常有用。
一个常见的任务是创建一个包装的原生类,这样做:
//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)));
}
};
}
警告:我完全错过了一堆的#include和使用#using语句,这只是给如何使用这个一般的要点。
从这个开始:
而一些有关这个编组:
需要注意的是Marshal.Copy也重载阵列使用。 随着编组,你就可以摆脱ref
除此之外,你想。 只要写在他们的方式C / C ++。
而下面是复杂一点:
- 物理磁盘大小不正确(IoCtlDiskGetDriveGeometry)
2种方式,我经常看到这种处理是要么有一个“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互操作的SDK可能会对你有所帮助。 但是你可以自己做它。 对于简单的类有几个方法,你可以写在托管的C#代码你自己的代码。
从.NET世界实例化一个C ++对象的基本思想是分配从.NET C ++对象的确切大小,然后调用它从C ++ DLL导出初始化对象的构造,那么你就可以调用任何的功能,以访问该C ++对象,如果任何的方法的涉及其他C ++类,则需要将它们包装在C#类,以及,用于与原始类型的方法,可以简单P /调用它们。 如果你只有几个方法来调用,这将是简单的,手工编码用不了多长时间。 当你用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互操作SDK
该工具, xInterop NGEN ++ 2.0已经发布。 请检查出来,如果你有兴趣在本地C ++ DLL创建C#包装。
(我是SDK工具的作者)
文章来源: Calling C++ function from C#, with lots of complicated input and output parameters