使用C ++ / CLI包装非托管C ++类库 - 问题1 - 项目/代码组织

时间:2011-01-27 17:34:33

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

注意:这篇文章代表我的询问问题#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属性中。

    • 如果我将Additional Include Directories属性更改为解决方案的根(..\)并将我的包含作为#include "Unmanaged\Widget.h的非托管标头,我有一个新问题解析非托管标头中包含的标头。因此,使用该选项需要我将所有 include语句更改为其项目目录的前缀。我知道其他项目

    • 重复文件名问题最明显/最快的解决方案是更改其中一个库的命名模式。因此,对于C ++ / CLI项目,不要使用Widget.hWidget.cpp作为前缀或后缀m(托管)或w(包装器)。因此,C ++ / CLI文件将是mWidget.hwWidget.hWidgetM.hWidgetW.h。然后我可以在整个过程中重复我现有的模式并保持良好状态。

    • 但是有没有更好的方法来组织文件,以便我可以保留我的无/无后缀文件名,只需对现有代码进行最少的更改?

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

1 个答案:

答案 0 :(得分:3)

在包装非托管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手工卷制。由于库的编写非常一致,我能够读取整个文件,使用Regex删除注释和其他可能导致问题的内容(内联函数),然后只需读取每一行和Regex以找出其中的内容。我有类似所有集合的列表(知道何时进行上面的集合调用),异常类(string / std :: wstring)以及其他我不记得的事情。我将不得不看看它是否已成为我们新的源代码控制,因为该项目在几年前就被放弃了。