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

时间:2013-03-28 00:49:29

标签: c# c++ dll interop pinvoke

我是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的内容。

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

4 个答案:

答案 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)

从这开始:

关于编组的内容:

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

以下有点复杂:

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

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

C#/.NET PInvoke Interop SDK

该工具xInterop NGen++ 2.0 has been released.如果您有兴趣为本机C ++ DLL创建C#包装器,请查看它。

(我是SDK工具的作者)