c# - 从 C# 调用 C++ 函数,具有大量复杂的输入和输出参数

标签 c# c++ dll interop pinvoke

我是 C# 新手,但广泛使用 C++。我有一个需要从 C# 调用的 C++ 函数。在阅读了 SO 的一些答案和一些谷歌搜索之后,我得出结论,我需要为该函数创建一个纯 C 接口(interface)。我已经这样做了,但仍然对如何从 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 的东西。

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

最佳答案

由于似乎有人对将 It Just Works (IJW) 与 C++/CLI 结合使用感兴趣,因此我将发布一些相关信息,需要进行进一步的谷歌搜索和研究才能弄清楚这一切。可以使用单个编译器标志启用 C++/CLI(/CLI,通过属性页->常规->公共(public)语言运行时支持启用)。 C++/cli 不是 c++,而只是另一种托管语言。 C++/CLI 类可以编译成 dll 并直接从其他 .NET 项目(C#、VB.NET 等)调用。但是,与其他 .NET 语言不同,它可以直接与 C++ 代码交互。

This是学习 C++/CLI 的良好开端。要学习的最重要的事情是告诉你这个类是托管的(.NET 类)而不是 Vanila C++ 的装饰。 “ref”关键字将定义定义为 .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# 移至 native 类型,然后再移回。 (例如托管 Array^ 或 String^ 到 void* 或 std::string)。此过程称为编码。 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 语句,这只是为了给出如何使用它的一般要点。

关于c# - 从 C# 调用 C++ 函数,具有大量复杂的输入和输出参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15672351/

相关文章:

c++ - 为什么我不能用 Qt5/C++ 解析 Cryptsy JSON API?

c# - 使用 Microsoft.JScript 的 JavaScript 和 C# 插值

c# - SSRS 最后执行数据集

c# - 优化 NHibernate 生成的 SQL

c# - 在刻度线中间停止计时器

c++ - 找到玩家说的话并阻止它

c++ - 动态分配内存和取消引用

c# - 我可以拆分 .net DLL 而不必重新编译引用原始 DLL 的其他 DLL 吗?

windows - 如何拦截dll方法调用?

java - 从 Java 确定是 x86 还是 x64 系统