包装非托管 C++使用 C++/CLI 的类库 - 问题 1 - 项目/代码组织
注意:这篇文章代表了我的询问的问题#1。两个问题中都会重复介绍部分(达到数字之前的所有文本),因为它是回答问题可能需要的背景信息。
问题简介
我有一个非托管 C++ 库,其中包含多个“高级”库所共有和共享的类和函数。我现在需要向 C#/.Net 应用程序提供对公共库的访问。为此,我必须使用 C++/CLI 包装类来包装公共库。
公共库中包含的类可以是包含嵌套类定义和作为其他类对象集合的成员变量的复杂类。集合变量是用于管理集合的自定义列表类的 typedef 的实例。公共库还包括表示使用 FLEX/BISON 创建的自定义脚本文件语法的解析结构的类。公共库和“更高级别”库都是以允许跨平台(Linux 和 GCC)编译和使用的方式编写的。我所做的任何改变都必须考虑到这一点。
C++/CLI 包装类最初只需要读取功能。但随着项目的进展,我最终还需要能够创建和修改对象。
我了解 C++/CLI,并为其他非托管 C/C++ 项目创建了多个包装器,并为同一个公共库提供了抽象功能。所以我已经有了基础知识(和一些高级知识)。
我有两个与执行此任务相关的问题,因为它们都可以产生自己的讨论和解决方案,所以我将我的问题分成单独的帖子。我将在每篇文章中包含其他问题的链接。
实际问题
如何在项目中构建我的文件?
非托管项目和 C++/CLI 项目之间的命名空间和类名称不会发生冲突。由于非托管项目使用“C”前缀作为类名,而 C++/CLI 则不使用。因此,非托管类
CWidget
将变成Widget
。并且它们使用不同的根命名空间名称。当涉及文件名时就会出现问题。因为我的默认命名模式是对非托管和 C++/CLI 使用
Widget.h
和Widget.cpp
。项目当前已设置,项目的所有文件都位于项目文件夹的根目录中。头文件的包含仅作为头文件的名称完成(例如
#include "Widget.h"
)。为了正确解决不同项目文件的包含问题,另一个项目的路径将添加到使用项目的Additional Include Directory
属性中。如果我将
其他包含目录
属性更改为解决方案的根(..\
),并将非托管标头包含为< code>#include "Unmanaged\Widget.h,我遇到了一个新问题,需要解决非托管标头中包含的标头。因此,使用该选项需要我更改所有包含语句以为其添加前缀我知道其他项目解决重复文件名问题的最明显/最快的解决方案是更改其中一个库的命名模式。因此对于 C++/CLI 项目,不要使用
Widget.h
和Widget.cpp
来前缀或后缀m
(托管)或w
(包装器)。因此,C++/CLI 文件将为mWidget.h
、wWidget.h
、WidgetM.h
或WidgetW.h.然后我就可以重复我现有的模式并表现得很好。
但是有没有更好的方法来组织文件,以便我可以在对现有代码进行最少更改的情况下保留无前/后缀的文件名?
Note: This post represents Question #1 of my inquiry. The introduction block (all text until the numbers are reached) is repeated in both questions as it is background information that may be needed to answer the question.
Introduction to Question
I have an unmanaged C++ library that contains classes and functions that are common to and shared among several "higher level" libraries. I now have need to provide access to the common library to C#/.Net applications. To do this, I will have to wrap the common library with C++/CLI wrapper classes.
The classes contained in the common library can be complex classes containing nested class definitions and member variables that are collections of other class objects. The collection variables are instances of typedefs of a custom list class for managing the collection. The common library also includes classes that represent the parsed structure of a custom script file syntax created using FLEX/BISON. The common library and the "higher level" libraries are all written in a fashion that allows for cross platform (Linux and GCC) compiling and usage. Any changes I make must still allow for this.
The C++/CLI wrapper classes at first need only read capability. But as the project advances, I'll eventually need to be able to create and modify the objects as well.
I know C++/CLI and have created several wrappers for other unmanaged C/C++ projects as well as providing abstracted functionality to this same common library. So I have the basics (and some advanced knowledge) already.
I have two questions related to performing this task and since they could both spawn their own discussions and solutions, I'm splitting my questions into separate posts. I'll include the link to the other question in each post.
Actual Questions
How do I structure my files within the project?
The namespace and class names between the unmanaged and C++/CLI projects will not conflict. As the unmanaged project uses the "C" prefix for class names while the C++/CLI does not. So unmanaged class
CWidget
will become justWidget
. And they use different root namespace names.The issue comes when file names are concerned. As my default naming pattern would be to use
Widget.h
andWidget.cpp
for both unmanaged and C++/CLI.The projects are currently setup where all files of the project are in the root of the project folder. Includes for header files are done as just the name of the header (e.g.
#include "Widget.h"
). And to appropriately resolve includes for files of a different project, the path of the other project is added to theAdditional Include Directories
property of the using project.If I change my
Additional Include Directories
property to be the root of the solution (..\
) and do my include for the unmanaged header as#include "Unmanaged\Widget.h
, I have a new issue of resolving headers included in the unmanaged header. So using that option would require I change all include statements to prefix their project directory. I know other projectsThe most obvious/quickest solution to the issue of duplicate file names is to change the naming pattern for one of the libraries. So for the C++/CLI project, instead of using
Widget.h
andWidget.cpp
to prefix or suffixm
(managed) orw
(wrapper). So the C++/CLI files would bemWidget.h
,wWidget.h
,WidgetM.h
, orWidgetW.h
. Then I could just repeat my existing pattern throughout and be good.But is there a better way to organize the files so that I could keep my pre-/suffix-less file names with minimal changes to existing code?
Wrapping an Unmanaged C++ Class Library with C++/CLI - Question 2 - Collections
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我在包装非托管 C++ api 时做了一些非常类似的事情。就我而言,甚至类名都是相同的。以下是我对项目所做的操作:
C:\Foo
C:\Foo\api
>bar.h
) 都以#include "api/bar.h"
开头>Api::Bar
就我而言,项目上花费的大部分时间都花在自动创建托管 C++ 文件上。我手工做了一些,意识到这样做需要多少时间,并开始自动化该过程。
我的图书馆仍然是分开的。我有一个非托管 dll 和一个托管 dll。我只是将非托管项目存储在托管项目下。
至于自动化,它将读取每个非托管 .h 文件并创建我的托管 .h 文件,如下所示:
#include "api/bar.h"
行。#include
行。GetInner()
来获取非托管类指针。然后创建托管 .cpp 文件,如下所示:
这适用于大多数类,只需一点点手动调整。它不起作用的一个领域是集合类。现在,我认为我很幸运,因为每个集合类基本上都是
std::vector
的包装器。我的所有托管版本都基于CollectionBase
,其中的构造函数采用非托管集合类,并且方法具有以下签名:因此,从非托管集合转换为托管集合只需
gcnew
,另一种方式是:这种东西也内置到非集合类的 .cpp 类生成中。
ToFoo
的代码只会清除非托管集合,迭代托管集合并通过GetInner()
添加每个非托管项。我的收藏不是那么大,所以这个效果非常好。如果我必须重新创建集合类,我现在很可能不会将它们基于
CollectionBase
。我更有可能将它们基于List
或执行类似于针对您的问题 #2 发布的当前答案中讨论的内容。我的代码是从 .Net 1.1(无泛型)开始的,所以当时,CollectionBase
对我来说最有意义。它是使用正则表达式手工滚动的。由于该库的编写非常一致,我能够读取整个文件,使用 Regex 删除注释和其他可能导致问题的内容(内联函数),然后只需读取每一行和 Regex 来找出其中的内容。我有一些东西,比如所有集合的列表(以知道何时执行上面的集合调用)、异常类(string/std::wstring)以及其他我不记得的东西。我将不得不看看它是否已进入我们新的源代码管理中,因为该项目几年前已被放弃。
I have done something very similar when wrapping an unmanaged C++ api. In my case, even the class names were identical. Here's what I did with my project:
C:\Foo
C:\Foo\api
bar.h
) started with#include "api/bar.h"
Api::Bar
In my case, the majority of time spent on the project was on automating the creation of the managed C++ files. I did a couple by hand, realized how much time it would take to do everything that way, and started automating the process.
My libraries were still separate. I had one unmanaged dll and one managed dll. I just stored the unmanaged project under the managed one.
As for the automation, it would read each unmanaged .h file and create my managed .h file like this:
#include "api/bar.h"
line.#include
lines.GetInner()
to get the unmanaged class pointer.Then create the managed .cpp files like this:
GetInner()
any time a class was included as a function paramtersThis worked for the majority of the classes with only a little manual tweaking. The one area where it didn't work was with collection classes. Now, I think I was lucky, as every collection class was basically a wrapper around
std::vector
. I based all my managed versions onCollectionBase
, with a constructor taking the unmanaged collection class, and a method with the following signature:So to go from an unmanaged collection to a managed collection was just a
gcnew
, and to go the other way was:This sort of thing was also built into the .cpp class generation for the non-collection classes.
The code for
ToFoo
would just clear the unmanaged collection, iterate through the managed collection and add each unmanaged item, viaGetInner()
. My collections weren't that big, so this worked perfectly well.If I had to do the collection classes over again, I most likely wouldn't base them on
CollectionBase
now. I'd be much more likely to base them onList<T>
or do something similar to what was discussed in the current answers posted to your question #2. My code was started with .Net 1.1 (no generics), so at that time,CollectionBase
made the most sense to me.It was hand-rolled using Regex. As the library was written very consistently, I was able to read in the entire file, use Regex to remove comments and other things that might cause problems (inline functions), then just read each line and Regex to figure out what was in it. I had things like a list of all the collections (to know when to do the collection calls above), abnormal classes (string/std::wstring), and other things I'm not remembering. I'm going to have to see if it made it into our new source control, as the project was abandoned a couple years ago.