从C#调用C ++函数,有很多复杂的输入和输出参数的(Calling C++ function fr

2019-08-18 01:50发布

我是新来的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特定的东西。

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

Answer 1:

由于好像是在使用它只是工作(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语句,这只是给如何使用这个一般的要点。



Answer 2:

从这个开始:

  • 如何通过结构指针从C#C DLL

而一些有关这个编组:

  • 深度复制的阵列C#而不系列化

需要注意的是Marshal.Copy也重载阵列使用。 随着编组,你就可以摆脱ref除此之外,你想。 只要写在他们的方式C / C ++。

而下面是复杂一点:

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


Answer 3:

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


Answer 4:

如果您愿意使用第三方工具,有一个名为工具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