7

I have a project that is largely written in C#. I need to define a class for all of the error number "defines" for the API of this project. I'm trying to avoid writing/altering one of my many code generators to achieve this.

What I would like to do is be able to #include content (like the error defiles) directly into a C/C++ project. I defined them in C# as follows, and I did not use an enum for things you will see here:

using System;

namespace ProjectAPI {

[Serializable]
public sealed class ProjectError {

    public enum ProjectErrorClass {
        None            = -1,
        Undefined       = 0,
        Login,
        Store,
        Transaction,
        Heartbeat,
        Service,
        HTTPS,
        Uploader,
        Downloader,
        APICall,
        AutoUpdate,
        General
    }

    public enum ProjectErrorLevel {
        Unknown = -1,
        Success = 0,
        Informational,
        Warning,
        Critical,
    };

    /// <summary>
    /// PROJECT_ERROR_BASE - This is the base for all Project defined errors in the API.  Project Errors are defined as follows:
    ///   ProjectAPI error values are 32 bit values defined as follows:
    ///   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
    ///   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
    ///  +---+---------------+-----------------------+------------------+
    ///  |Sev|Error Code Base| Error Class           |Unique Error Code |
    ///  +---+---------------+-----------------------+------------------+
    ///  where
    ///
    ///      Sev - is the severity code of the error (2 bits), and is defined as follows:
    ///          00 - Success (non-fatal)   0x00
    ///          01 - Informational         0x01
    ///          10 - Warning               0x02
    ///          11 - Error                 0x03
    ///
    ///      Error Code Base - is the starting point of all Project Errors, and is set at 0xA4 (8 Bits).
    ///
    ///      Error Class - is the error class, or API "Module" that caused the error (12 bits).
    ///
    ///      Code - the unique error code (10 bits). (0 - 1,023 (0x3FF)).
    /// </summary>

    private static readonly int ERR_SHIFT                       = 0x1E;
    private static readonly int BASE_SHIFT                      = 0x16;
    private static readonly int CLASS_SHIFT                     = 0x06;

    private static readonly int PROJECT_SEV_SUCCESS           = 0x00;
    private static readonly int PROJECT_SEV_INFO              = 0x01;
    private static readonly int PROJECT_SEV_WARN              = 0x02;
    private static readonly int PROJECT_SEV_ERROR             = 0x03;

    private static readonly int PROJECT_ERROR_BASE             = 0xA5;

    /// <summary>
    /// Project Error Class Constants:
    /// </summary>
    private static readonly int PROJECT_ERROR_CLASS_UNDEF     = 0x0010;   /// Undefined.
    private static readonly int PROJECT_ERROR_CLASS_LOGIN     = 0x0020;   /// LoginClass Error.
    private static readonly int PROJECT_ERROR_CLASS_STORE     = 0x0040;   /// Store Error.
    private static readonly int PROJECT_ERROR_CLASS_TRANS     = 0x0080;   /// Transaction Error.
    private static readonly int PROJECT_ERROR_CLASS_HEART     = 0x0100;   /// HeartBeat (Project Health Monitor) Error.
    private static readonly int PROJECT_ERROR_CLASS_SERV      = 0x0200;   /// Service Error.
    private static readonly int PROJECT_ERROR_CLASS_HTTP      = 0x0400;   /// HTTP/HTTPS Error.
    private static readonly int PROJECT_ERROR_CLASS_UPLOAD    = 0x0800;   /// Upload (Transactions) Error
    private static readonly int PROJECT_ERROR_CLASS_DOWNLOAD  = 0x1000;   /// Download (Transactions) Error
    private static readonly int PROJECT_ERROR_CLASS_APICALL   = 0x2000;   /// API Command/call error.
    private static readonly int PROJECT_ERROR_CLASS_UPDATE    = 0x4000;   /// Auto-Updater Errors.
    private static readonly int PROJECT_ERROR_CLASS_GEN       = 0x8000;   /// General Error.

    public static readonly int PROJECT_ERROR_UNKNOWN_ERROR    = ProjectErrCode(PROJECT_SEV_ERROR, PROJECT_ERROR_CLASS_GEN, 0x001);
    // Was...
    //    (((PROJECT_SEV_ERROR << ERR_SHIFT) | PROJECT_ERROR_BASE << BASE_SHIFT) | ((PROJECT_ERROR_CLASS_UNDEF << CLASS_SHIFT) | 0x0001));

    public static readonly int PROJECT_ERROR_UNKNOWN_HEARTBEAT_ERROR = ProjectErrCode(PROJECT_SEV_ERROR, PROJECT_ERROR_CLASS_HEART, 0x001);
...Snip...

...

I realize there are other things I could put into Enums, however my goal is to be able to compile this source with a C++ compiler as well. (There are functions missing in the sample above, namely the ProjectErrCode() which builds the final integer value of the error code OTF when called from the API.)

I was building the error constants as seen in the comments, and I can go back to that, but I'd rather write similar classes - one in C# one in C++ that can construct/deconstruct the error codes. My functions return the Severity of the error, the error class, and so on. The developer can ignore it, log it, pass it on to the UI, etc.

If I only had 5 or 10 error codes, this wouldn't be an issue. But I have well over 100 and really don't want to have to maintain a .cs and .h file with duplicate information. I can manage them in the .h file and have the CS code read them , but that is almost as much work as writing (modifying) a code generator.


How can I #define my way to a single source file, such that the C# compiler can compile it, just the same as the C/++ compiler could as well? I could just simply #include "ProjectErrors.cs" - Filename is not an issue there. I started off thinking I could do this by #define'ing stuff like the using System; but pretty much got hung up there.

Deanie
  • 2,164
  • 2
  • 14
  • 30
LarryF
  • 4,617
  • 4
  • 27
  • 40
  • 1
    Mostly it would be just take each word that is not in C++ and `#define` it to a blank string. I see no way to deal with the `[Serializable]`. – Dark Falcon Nov 11 '13 at 19:46
  • 2
    As you said, a code generator is a solution. I don't think there's another way – Matt Nov 11 '13 at 19:55
  • 1
    @DarkFalcon: `#ifndef CPP \\\ [Serializable] \\\ #endif \\\ class XYZ { .... }` and now `#define CPP` only in C++ side, and keep CPP undef'ed on C# side – quetzalcoatl Nov 11 '13 at 21:12
  • @quetzalcoatl: Sure, but that would require modifying his code generator. – Dark Falcon Nov 11 '13 at 21:16
  • Thanks Dark Falcon, Yea, in all reality, the [Serializable] could probably be done without, but since this is used over Remoting, I'm NOT sure... I really wish this wasn't an issue, and if the project was larger, I would have written it in Native C++, and then wrote C# wrappers.. Sigh... – LarryF Nov 11 '13 at 22:20
  • @DarkFalcon: You can't use #ifdef as C# will not recognize it as a keyword. #if CPP will work on both ends if you define it as 1 on the CPP side. –  Nov 13 '13 at 21:59

2 Answers2

4

1) Use preprocessor. Some ifdefs and defines should do the trick, but it would be extremely messy.

2) Use C++/CLI. C++/CLI is a variant of C++ which is compiled into .Net assemblies. While .Net types and native types are separate entities, conversions between them are possible.

For example, you make define the header as completely native code with native enums and constants; you can then include this header both into a 100% native project and into a C++/CLI project, which would also provide conversions (see this thread) of the enums to corresponding .Net types.

If you don't want to have this in-between conversion layer, C++/CLI also gives you full power of C++ macros, so you could make a file with macros like ENUM_HEADER and CONSTANT and evaluate these into appropriate managed or native forms in fairly clean and straightforward way (which you can't do with C#, because it has much weaker preprocessor). The resulting assembly would then be essentially this header and appropriate macro definitions and nothing else.

3) Have the values defined in some external file (XML, INI, whatever...) and only implement loading logic in both C# and C++ (this might actually be the cleanest solution).

Community
  • 1
  • 1
Matěj Zábský
  • 16,050
  • 14
  • 64
  • 110
4

One option is to use T4: thats a code generation language built into Visual Studio. It is mostly used in C#, but C++ apparantly works too.

One of the reasons why you will have a hard time with the preprocessor is that C# and C++ have the same syntax for comments: you wont be able to hide incompatible C# specific syntax for the C++ preprocessor using a comment. That said, you can try VB :).

Community
  • 1
  • 1