views:

161

answers:

3

I have a library that consists of three parts. First is native C++, which provides the actual functionality. Second is a C++/CLI wrapper/adaptor for the C++ library, to simplify the C# to C++ transition. Finally I have a C# library, which invokes the C++ library through the C++/CLI adaptor.

Right now there I have two sets of parallel enum definitions, one stored in a .cs file and the other in a .h file. This poses a double problem:

  1. I have dual maintenance. I must always synchronize changes of an enum in both file locations.
  2. The namespace used by both enums should be identical but the C++/CLI wrapper, which views both sets of enums and translates between them, incurs a naming collision.

Right now I'm not sure a solution such as this or that would solve both problems. Thoughts?

+2  A: 

Even if you include the C# enum in your native C++ (as suggested in your first link), both enums are not "the same", the C++ enum is nothing but a list of named integers, while the C# enum is derived from Enum. As a consequence, you get a collision in C++/CLI when trying to use them both.

A possible solution is to use the preprocessor so that your C++/CLI assembly sees both enums in different namespaces:

// shared_enum.h

#undef ENUMKEYWORD
#undef ENUMNAMESPACE

#ifdef MANAGED
#define ENUMKEYWORD public enum class
#define ENUMNAMESPACE EnumShareManaged
#else
#define ENUMKEYWORD enum
#define ENUMNAMESPACE EnumShare
#endif

namespace ENUMNAMESPACE
{
    ENUMKEYWORD MyEnum
    {
        a = 1,
        b = 2,
        c = 3,
    };
}

In your C++/CLI code, make an inclusion like that:

#undef MANAGED
#include "shared_enum.h"
#define MANAGED
#include "shared_enum.h"

This gives you the availability to distinguish between those two kind of enums EnumShare::MyEnum or EnumShareManaged::MyEnum in your C++/CLI code.

EDIT: just found this SO post showing the correct way to cast between unmanaged and managed enums, this surely will work here, too. For example, in the C++/CLI, the transition from managed to unmanaged enum can be done like this:

void MyWrapperClass::MyWrapperFunction(EnumShareManaged::MyEnum mx)
{
    EnumShare::MyEnum nx = static_cast<EnumShare::MyEnum>(mx);
    // call a native function "func"
    func(nx);
}
Doc Brown
How does the C# assembly see the "shared_enum.h" file? I'm guessing it is easier to have C++ #include a "shared_enum.cs" than it is to have C# somehow absorb a "shared_enum.h". Yes?
Brent Arias
@Mystagogue: if you include the "managed" shared_enum.h in one of your CPP files (of the C++/CLI project), the managed enum becomes part of the assembly, and if you reference this assembly from C#, it is visible there since it was declared 'public'. On the other hand, if you declare the managed enum in a "shared_enum.cs", it will not be visible in your C++/CLI project, since the C++/CLI does not reference your C# project, only vice versa.
Doc Brown
@Doc: Ah! Yes, but darn! Additional requirement: I need to support both x86 and x64 deployments. My "AnyCPU" C# client code currently references a middle-man C# assembly containing interfaces and enum definitions. The C++/CLI x86/x64, also referencing the middle-man, is loaded explicitly at run-time by the C# client, based on architecture. In short, I can't use a technique that depends on a C# "build-time" reference onto the C++/CLI assembly.
Brent Arias
@Mystagogue: yes, the idea of using an additional C# assembly for your enums was the first coming to my mind when I read the solution of your first reference you gave (the #include "shared_enum.cs"). But I thought that would be some avoidable overhead, so I worked out a solution that fits better to the situation you described first. Nevertheless, even if you use a middle-man C# assembly, the basic problem keeps the same - both type of enums must be in different namespaces. Cleanest thing then IMHO is to use a simple code generator (like Alex Farber suggested), since C# lacks a preprocessor.
Doc Brown
+1  A: 

Consider writing code generator program, which reads native h-file file with enumerations and generates another h-file, converting enum to C++/CLI enum class. Such code generator can be used in C++/CLI project on the Custom Build Step, producing required CLI enumerations.

I use this approach to generate native wrapper classes to get Enum::GetNames and Enum::GetName functions in unmanaged C++.

Alex Farber
Also used this approach when dealing with about 50 enums in a large project. Most important part was setting the managed enum enumerators to the unamanged enumerator values.
Joel Rondeau
Even with a code generator (which might be useful if you have many enums) you have to deal somehow with the naming collision issue. My suggestion was "live with different namespaces and handle them correctly". Any better idea?
Doc Brown
A: 

Just put your #include "Enum.cs" directive inside an outer namespace to resolve the naming collision.

EDIT: A variation suggested my Mystagogue is to use #define to substitute one of the namespaces (or even the enum name itself) declared in the .cs file. This also avoids the naming collision, without making the namespace hierarchy deeper.

Ben Voigt
Did you read my comment to Mystagogue on my first posting?
Doc Brown
Your method doesn't allow using the same source file directly in C# and native C++ projects. Mine requires no macros.
Ben Voigt
Your method does not allow access to the managed enum from the C++/CLI project, only to the unmanaged enum. So it does not fit to the requirement of the OP.
Doc Brown
Actually, this approach *does* allow access to the managed enum if: (1) I am using a C# middle-man project that merely contains the enum, (2) the C++/CLI project references the "middle-man" C# project, and (3) I use the #include "enum.cs" with the namespace around it. This approach would also handle my x86/x64 deployment requirements. But this approach does have two (small?) problems. First, it means that all my native C++ clients have a slightly goofy way of accessing the header file. Second, the native C++ clients won't have a uniform "company name" namespace prefix on the enum.
Brent Arias
Perhaps I can solve the "two small problems" by (1) offering a "header wrapper" to C++ clients and (2) I become evil and declare one of the nested namespaces to be a macro from the "header wrapper", so that it will be a different "child" namespace when compiled, rather than a different "parent" namespace.
Brent Arias
@Ben: I will mark your post as the answer if you extend it to say something like "...or to retain the root/parent namespace, have the enum itself embedded in a namespace hierarchy and define one of the child namespaces as a macro - when #included as a native declaration. In effect, you will modify a sub-namespace rather than adding a new top-most namespace layer." Basically that summarizes the discussion and achieves my goals for namespace conventions and x86/x64 deployment requirements.
Brent Arias