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

Cup*_*ppM 9 .net c++-cli code-organization wrapper visual-c++

注意:这篇文章代表我的询问问题#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.他们使用不同的根命名空间名称.

    • 当涉及文件名时会出现问题.作为我的默认命名模式是使用Widget.hWidget.cpp两个非托管C++/CLI.

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

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

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

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

  2. 使用C++/CLI包装非托管C++类库 - 问题2 - 集合

Joe*_*eau 2

我在包装非托管 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);
Run Code Online (Sandbox Code Playgroud)

因此,从非托管集合到托管集合只需 a 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);
Run Code Online (Sandbox Code Playgroud)

这种事情也被内置到非集合类的 .cpp 类生成中。

的代码ToFoo只会清除非托管集合,迭代托管集合并通过GetInner(). 我的收藏不是那么大,所以这个效果非常好。

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

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