【问题标题】:Wrapping C++ for use in C#包装 C++ 以在 C# 中使用
【发布时间】:2012-03-01 18:34:20
【问题描述】:

好的,基本上有一个大型 C++ 项目 (Recast) 我想打包,以便在我的 C# 项目中使用它。

我一直在尝试这样做一段时间,这就是我目前所拥有的。我正在使用 C++/CLI 来包装我需要的类,以便我可以在 C# 中使用它们。

但是,我的 C# 项目中还需要大量结构和枚举。那么我该如何包装这些呢?

我现在使用的基本方法是将 dllexport 调用添加到本机 c++ 代码,编译到 dll/lib,将此库添加到我的 C++/CLI 项目并导入 c++ 头文件,然后将 CLI 项目编译为dll,最后将此 dll 添加为对我的 C# 项目的引用。感谢您的帮助。

这是一些代码。由于 C++ 项目太大,我需要一种易于管理的方式。

//**Native unmanaged C++ code
//**Recast.h

enum rcTimerLabel
{
   A,
   B,
   C
};

extern "C" {

class __declspec(dllexport) rcContext
{
   public:
   inline rcContect(bool state);
   virtual ~rcContect() {}
   inline void resetLog() { if(m_logEnabled) doResetLog(); }

   protected:
   bool m_logEnabled;
}

struct rcConfig
{
   int width;
   int height;
}

} // end of extern


// **Managed CLI code
// **MyWrappers.h
#include "Recast.h"

namespace Wrappers
{
   public ref class MyWrapper
   {
   private:
     rcContect* _NativeClass;
   public:
     MyWrapper(bool state);
     ~MyWrapper();
     void resetLog();
     void enableLog(bool state) {_NativeClass->enableLog(state); }
   };
}

//**MyWrapper.cpp
#include "MyWrappers.h"

namespace Wrappers
{
   MyWrapper::MyWrapper(bool state)
   {
      _NativeClass = new rcContext(state);
   }

   MyWrapper::~MyWrapper()
   {
      delete _NativeClass;
   }
   void MyWrapper::resetLog()       
   {
      _NativeClass->resetLog();
   }
}


// **C# code
// **Program.cs

namespace recast_cs_test
{
   public class Program
   {
      static void Main()
      {
          MyWrapper myWrapperTest = new MyWrapper(true);
          myWrapperTest.resetLog();
          myWrapperTest.enableLog(true);
      }
   }
}

【问题讨论】:

标签: c# c++ dll c++-cli wrapper


【解决方案1】:

通常,C/C++ 结构用于与本机代码通信,而您创建 CLI 类以与 .NET 代码通信。 C 结构是“愚蠢的”,因为它们只能存储数据。另一方面,.NET 程序员希望他们的数据结构是“智能的”。例如:

如果我更改结构中的“高度”参数,我知道对象的高度实际上不会改变,直到我将该结构传递给更新函数。但是,在 C# 中,常见的习惯是将值表示为属性,更新属性将立即使这些更改“生效”。

这样我可以做类似的事情:myshape.dimensions.height = 15 并期待它“工作”。

在某种程度上,您向 .NET 开发人员(作为类)公开的结构实际上是 API,其行为被映射到这些类的属性和方法。而在 C 中,结构只是用作传递给执行工作的函数和从函数传递的变量。换句话说,.NET 通常是面向对象的范例,而 C 不是。很多 C++ 代码实际上都是 C 语言,并带有一些花哨的元素。

如果您正在编写 C 和 .NET 之间的转换层,那么您工作的很大一部分是设计将构成您的新 API 的对象并为您的底层功能提供转换。 C 代码中的结构不一定是新对象层次结构的一部分;它们只是 C API 的一部分。

编辑添加:

还要考虑

此外,您可能需要重新考虑使用 C++/CLI 的选择,并改用 C# 和 p/invoke。出于各种原因,我曾经使用 C++/CLI 为 OpenSSL 编写了一个包装器,虽然它的构建简单和无缝工作令人印象深刻,但也有一些烦恼。具体来说,绑定很紧密,所以每次父项目 (OpenSSL) 更新他们的库时,我都必须重新编译我的包装器以匹配。此外,我的包装器永远与特定架构(64 位或 32 位)相关联,该架构也必须与底层库的构建架构相匹配。您仍然会遇到 p/invoke 的体系结构问题,但它们更容易处理。此外,C++/CLI 不能很好地与 Reflector 等自省工具配合使用。最后,您构建的库不能移植到 Mono。我不认为这最终会成为一个问题。但最后,我不得不从头开始,改用 p/invoke 在 C# 中重新完成整个项目。

一方面,我很高兴我完成了 C++/CLI 项目,因为我学到了很多关于在一个项目中处理托管和非托管代码和内存的知识。但另一方面,我确实有很多时间可以花在其他事情上。

【讨论】:

【解决方案2】:

我会考虑使用ATL 创建一个 COM 服务器。不过,它不会是一个简单的端口。您必须创建 COM 兼容接口,以公开您尝试包装的库的功能。最终,您将拥有更多控制权和完全受支持的 COM 互操作接口。

【讨论】:

  • 我没有看到你建议的使用 p/invoke 的优势。需要详细说明吗?
  • 当然,这种方法使您能够创建可由 .NET 使用的组件,但不涉及托管和非托管代码的任何混合。 IDL 提供了定义与 COM 兼容并因此与 .NET 兼容的数据结构和接口的能力。有缺点:您必须作为 COM 组件运行,这可能会造成安装麻烦,并且您必须完全重新创建组件的接口。核心好处是您在纯非托管代码中运行,因此没有复杂性。由于 COM,这是一个仅限 Windows 的解决方案。
  • 除非他喜欢惩罚,否则我强烈建议不要特别使用 ATL,一般不要使用 COM。尤其是前者,几乎可以肯定会造成比其价值更多的问题。
  • ATL 确实有它的弱点;我想说最大的一个是它隐藏了很多代码并且可能会令人困惑,特别是如果您没有使用 C++ 模板或 COM 的经验。也就是说,我发现它是一个可靠的框架,并且比用 C++ 编写自定义 COM 接口更可取。
  • COM 的问题在于 MSFT 没有为 C++ 开发人员提供使用它的高级工具。我曾经做过很多 ATL 工作,并且在阅读“ATL Internals”时流血,这可能是关于该主题的事实上的书。是的,它完成了这项工作,但至少可以说是晦涩难懂的(我是委婉的),而且对于大多数程序员来说,很难理解。大多数 C++ 开发人员已经受到了足够的挑战。如果程序稳定性和维护成本意味着什么,那么大多数组织应该像避免瘟疫一样避免它。
【解决方案3】:

如果您准备使用 P/Invoke,SWIG 软件可能会帮助您:http://www.swig.org/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-12
    相关资源
    最近更新 更多