【问题标题】:Wrapping an Unmanaged C++ Class Library with C++/CLI - Question 1 - Project/Code Organization使用 C++/CLI 包装非托管 C++ 类库 - 问题 1 - 项目/代码组织
【发布时间】:2011-01-27 17:34:32
【问题描述】:

注意:这篇文章代表了我询问的问题 #1。在两个问题中重复介绍部分(直到达到数字为止的所有文本),因为它是回答问题可能需要的背景信息。


问题简介

我有一个非托管 C++ 库,其中包含几个“更高级别”库共有的类和函数。我现在需要为 C#/.Net 应用程序提供对公共库的访问权限。为此,我必须使用 C++/CLI 包装类来包装公共库。

公共库中包含的类可以是包含嵌套类定义和成员变量的复杂类,这些成员变量是其他类对象的集合。集合变量是用于管理集合的自定义列表类的 typedef 实例。公共库还包括表示使用 FLEX/BISON 创建的自定义脚本文件语法的解析结构的类。公共库和“更高级别”库都以允许跨平台(Linux 和 GCC)编译和使用的方式编写。我所做的任何更改都必须允许这样做。

C++/CLI 包装类最初只需要读取能力。但随着项目的推进,我最终也需要能够创建和修改对象。

我了解 C++/CLI,并为其他非托管 C/C++ 项目创建了多个包装器,并为同一个通用库提供了抽象功能。所以我已经掌握了基础知识(和一些高级知识)。

我有两个与执行此任务相关的问题,因为它们都可以产生自己的讨论和解决方案,所以我将我的问题分成不同的帖子。我将在每个帖子中包含指向另一个问题的链接。


实际问题

  1. 如何在项目中构建我的文件?

    • 非托管项目和 C++/CLI 项目之间的命名空间和类名不会冲突。由于非托管项目使用“C”前缀作为类名,而 C++/CLI 没有。所以非托管类CWidget 将变成Widget。它们使用不同的根命名空间名称。

    • 在涉及文件名时会出现问题。我的默认命名模式是对非托管和 C++/CLI 使用 Widget.hWidget.cpp

    • 项目当前已设置,项目的所有文件都位于项目文件夹的根目录中。头文件的包含仅作为头文件的名称完成(例如#include "Widget.h")。为了正确解析不同项目的文件的包含,将另一个项目的路径添加到使用项目的Additional Include Directories 属性中。

    • 1234563非托管标头。因此,使用该选项需要我更改 all 包含语句来为其项目目录添加前缀。我知道其他项目
  2. 解决重复文件名问题的最明显/最快的解决方案是更改其中一个库的命名模式。所以对于 C++/CLI 项目,不要使用Widget.hWidget.cpp 来前缀或后缀m(托管)或w(包装器)。因此 C++/CLI 文件将是 mWidget.hwWidget.hWidgetM.hWidgetW.h。然后我就可以一直重复我现有的模式并做好。

  3. 但是有没有更好的方法来组织文件,这样我就可以在对现有代码进行最少更改的情况下保留我的前/后缀文件名?

  4. Wrapping an Unmanaged C++ Class Library with C++/CLI - Question 2 - Collections

【问题讨论】:

  • 在包含标题时,我总是会包含项目。
  • +1 - 写得好,深思熟虑的问题。

标签: .net visual-c++ c++-cli wrapper code-organization


【解决方案1】:

在包装非托管 C++ api 时,我做了一些非常相似的事情。就我而言,甚至类名也是相同的。以下是我对项目所做的:

  • 我的 C++/CLI 解决方案位于 C:\Foo
  • 非托管 C++ api 位于 C:\Foo\api
  • 每个 .h 文件 (bar.h) 都以 #include "api/bar.h" 开头
  • 所有非托管类的使用都是通过Api::Bar

在我的例子中,花费在项目上的大部分时间都花在了自动创建托管 C++ 文件上。我手工做了几个,意识到这样做需要花费多少时间,然后开始自动化这个过程。

我的图书馆仍然是分开的。我有一个非托管 dll 和一个托管 dll。我只是将非托管项目存储在托管项目下。

至于自动化,它将读取每个非托管 .h 文件并创建我的托管 .h 文件,如下所示:

  • 添加我的#include "api/bar.h" 行。
  • 复制所有#include 行。
  • 添加我的托管命名空间。
  • 将该类复制为托管类,包括基类。
  • 添加指向非托管类的受保护指针。
  • 添加函数GetInner()获取非托管类指针。
  • 添加终结器以删除非托管类。
  • 当函数具有默认参数时添加多个函数。

然后像这样创建托管的 .cpp 文件:

  • 用对非托管函数的调用填充大多数函数
  • 在将类作为函数参数包含时使用GetInner()
  • 执行 marshal_as 在字符串和 std::wstrings 之间进行转换

这适用于大多数课程,只需进行一点手动调整。它不起作用的一个领域是集合类。现在,我想我很幸运,因为每个集合类基本上都是std::vector 的包装器。我的所有托管版本都基于CollectionBase,构造函数采用非托管集合类,方法具有以下签名:

void ToFoo(Api::Foo& v);

所以从非托管集合转到托管集合只是gcnew,而另一种方式是:

Foo* foo; //most likely a function parameter, already assigned.
Api::Foo apifoo; //name just "api" + the name of the managed Foo.
foo->ToFoo(apifoo);

这种东西也内置在非集合类的 .cpp 类生成中。

ToFoo 的代码只会清除非托管集合,遍历托管集合并通过 GetInner() 添加每个非托管项。我的收藏并没有那么大,所以效果很好。

如果我必须重新进行收集类,我现在很可能不会将它们基于CollectionBase。我更有可能将它们基于List<T>,或者做一些类似于发布到您的问题 #2 的当前答案中讨论的事情。我的代码是从 .Net 1.1(无泛型)开始的,所以当时CollectionBase 对我来说最有意义。

它是使用正则表达式手动滚动的。由于库的编写非常一致,我能够读取整个文件,使用 Regex 删除 cmets 和其他可能导致问题的东西(内联函数),然后只需阅读每一行和 Regex 以找出其中的内容。我有一个所有集合的列表(知道何时进行上面的集合调用)、异常类(string/std::wstring)以及其他我不记得的东西。我将不得不看看它是否进入了我们的新源代码控制,因为该项目几年前就被放弃了。

【讨论】:

  • 由于我需要将我的库分开,我无法将非托管嵌入到托管项目中。但也许我确实需要将项目路径添加到每个包含文件中。你为你的自动化流程做了什么?你对我的问题 #2 有什么建议吗(见上面的链接)?
  • 感谢乔尔提供的附加信息。我有多个非托管库,所以从一个组织中我认为我需要将它们都放在同一个根目录下。从我最初的游戏开始,看起来我只是将后缀“W”添加到包装类的文件名中(似乎是最简单的集成方法)。看来您可以模板化一个 ref 类,所以我可以包装一些基本功能来处理那里的非托管对象指针。但似乎编写所有包装器需要一段时间。你用什么工具来自动化?它是使用 RegExes 手卷的吗?
  • @CuppM - 尝试使用模板化的 ref 类进行测试(查看结果类在 VS 中的 Intellisense 中在纯托管项目中的外观)。至少在 VS 2008 中,我们注意到在使用托管 DirectX 包装器时,将模板引入 C++/CLI 类层次结构会导致 C# 编辑器永远无法发现有关该类层次结构的任何 Intellisense 信息——这至少可以说很烦人。不确定这是否是全部原因,但值得一试,这对您来说可能是个问题。
  • @Josh - 我正在使用 VS 2010,就 C# 而言,它似乎与模板化 ref 类一起工作得很好。至少按照我的方法。我将模板化的 ref 类用作存储非托管指针的最终包装器的基类,管理类是否“拥有”它,如果它确实拥有它,则在 finalize 时将其删除。 VS 2010 和任何带有 C++/CLI 的 Intellisense 似乎都存在问题。常规 C++ 可以正常工作,但对于 C++/CLI 则没有,即使 MS 博主说它应该可以工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-26
  • 1970-01-01
  • 2011-10-06
  • 1970-01-01
  • 2011-02-27
相关资源
最近更新 更多