我是C#的新手,但他已经广泛使用C ++。我有一个需要从C#调用的C ++函数。在阅读了SO和一些谷歌搜索的一些答案后,我得出结论,我需要为该函数创建一个纯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
);
这基于this建议。
现在我需要从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的内容。
我希望有人能理解这一点并提供答案,或者至少指出我正确的方向。
答案 0 :(得分:4)
由于似乎对使用It Just Works(IJW)和C ++ / CLI感兴趣,我会发布一些有关这方面的信息,需要进行进一步的谷歌搜索和研究以完成所有工作。可以使用单个编译器标志启用C ++ / CLI(/ CLI,通过Property Page-&gt; General-&gt; Common Language Runtime Support启用)。 C ++ / cli是 NOT c ++,而只是另一种托管语言。 C ++ / CLI类可以编译成dll,并直接从其他.NET项目(C#,VB.NET,ect)调用。但是,与其他.NET语言不同,它可以直接与C ++代码交互。
This是学习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,并访问句柄的函数/成员,使用 - &gt;操作
//in main
Foo^ a = gcnew Foo();
a->bar();
您经常需要将C#中常见的结构移动到本机类型,然后再移回。 (例如托管数组^或字符串^到void *或std :: string)。这个过程叫做Marshaling。 This handy table对于解决这个问题非常有用。
一个常见的任务是为本机类创建一个包装器,如下所示:
//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语句,这只是为了给出如何使用它的一般要点。
答案 1 :(得分:1)
答案 2 :(得分:0)
我经常看到的两种方法是使用'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);
...
答案 3 :(得分:0)
如果您愿意使用第三方工具,有一个名为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);
}
}
有关详细信息,请参阅以下链接
该工具xInterop NGen++ 2.0 has been released.如果您有兴趣为本机C ++ DLL创建C#包装器,请查看它。
(我是SDK工具的作者)