8
votes

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

  1. 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 just Widget. 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 and Widget.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 the Additional 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 projects

    • The 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 and Widget.cpp to prefix or suffix m (managed) or w (wrapper). So the C++/CLI files would be mWidget.h, wWidget.h, WidgetM.h, or WidgetW.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?

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

1
I would always include the project when including header.mmmmmmmm
+1 - Well written and well thought out question.Glenn McAllister

1 Answers

2
votes

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:

  • My C++/CLI solution was at C:\Foo
  • The unamanaged C++ api was at C:\Foo\api
  • Every .h file (bar.h) started with #include "api/bar.h"
  • All usage of unmanaged classes was via 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:

  • Add my #include "api/bar.h" line.
  • Copy all #include lines.
  • Add my managed namespace.
  • Duplicate the class as a managed class, including the base class.
  • Add a protected pointer to the unmanaged class.
  • Add a function GetInner() to get the unmanaged class pointer.
  • Add a finalizer to delete the unmanaged class.
  • Add multiple functions when a function had default parameters.

Then create the managed .cpp files like this:

  • Fill in most functions with calls to the unmanaged functions
  • Use GetInner() any time a class was included as a function paramters
  • Do marshal_as to convert between strings and std::wstrings

This 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 on CollectionBase, with a constructor taking the unmanaged collection class, and a method with the following signature:

void ToFoo(Api::Foo& v);

So to go from an unmanaged collection to a managed collection was just a gcnew, and to go the other way was:

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

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, via GetInner(). 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 on List<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.