Documentation

    Table of Content
    • CodePorting.Native Product Family
      • CodePorting.Native Cs2Cpp
        • Getting Started
          • Product Overview
          • Installation
          • Licensing
          • How to use CodePorting.Native Cs2Cpp
            • How to Use Command line to port and build Projects
            • How to Use GUI to port and build Projects
        • Developer Guide
          • Porting Simple C# Projects
            • Porting Simple Console Application
            • Porting Simple Library
            • Porting Simple NUnit Test
          • Porting Dependent C# Projects
            • Porting Dependent Console Application
            • Porting Dependent Library
            • Porting Dependent NUnit Test
          • Porting Complex C# Projects
            • How to Convert Complex C# Console Application to C++
            • How to Convert Complex C# Library to C++
            • How to Convert Complex C# NUnit Test to C++
          • Qt support
          • What Converts to What
            • AbstractClasses
            • BreakStatements
            • ClassAutoProperties
            • ClassConstructors
            • ClassEvents
            • ClassFinalizers
            • ClassGenericMethods
            • ClassIndexers
            • ClassMethods
            • ClassProperties
            • ClassStaticConstructor
            • ClassStaticMethods
            • ClassStaticProperties
            • ClassVirtualMethods
            • ContinueStatements
            • Delegates
            • DoWhileStatements
            • Enums
            • EnumTypeCast
            • Exceptions
            • ExpectedException
            • ForeachStatements
            • ForeachStatementsStd
            • ForStatements
            • GenericClasses
            • GenericDelegates
            • GenericInterfaces
            • GenericStructs
            • IfStatements
            • LambdaExpressions
            • NestedClasses
            • ReturnStatements
            • SimpleClass
            • SimpleInterface
            • SimpleStruct
            • SimpleTest
            • StandardTypeCast
            • StaticClass
            • SwitchStatements
            • TestWithSetupMethods
            • ThrowStatements
            • TryCatchFinallyStatements
            • TryCatchStatements
            • TryFinallyStatements
            • VarExpressions
            • WhileStatements
          • CodePorting Native Cs2Cpp Attributes
          • CodePorting Native Cs2Cpp Configuration File
            • CodePorting.Native Cs2Cpp Configuration File Structure
            • Attributes in Configuration file
            • Configuration file Nodes
            • Configuration file Options
          • Memory Management Model
            • Memory Management Model Description
            • Using aliasing constructor to create a smart pointer
          • Cmake Support
          • C++ code injection
          • C++ user-defined exception classes
          • Limitations and Bugs
            • Porter Limitations and Bugs
            • Library Limitations and Bugs
            • Cpp Code Injection
        • Release Notes
          • 2022
            • CodePorting.Native Cs2Cpp 22.5
            • CodePorting.Native Cs2Cpp 22.4
            • CodePorting.Native Cs2Cpp 22.3
            • CodePorting.Native Cs2Cpp 22.2
            • CodePorting.Native Cs2Cpp 22.1
          • 2021
            • CodePorting.Native Cs2Cpp 21.12
            • CodePorting.Native Cs2Cpp 21.11
            • CodePorting.Native Cs2Cpp 21.10.1
            • CodePorting.Native Cs2Cpp 21.10
            • CodePorting.Native Cs2Cpp 21.9
            • CodePorting.Native Cs2Cpp 21.8
            • CodePorting.Native Cs2Cpp 21.7
            • CodePorting.Native Cs2Cpp 21.6
            • CodePorting.Native Cs2Cpp 21.5
            • CodePorting.Native Cs2Cpp 21.4
            • CodePorting.Native Cs2Cpp 21.3
            • CodePorting.Native Cs2Cpp 21.2
            • CodePorting.Native Cs2Cpp 21.1
          • 2020
            • CodePorting.Native Cs2Cpp 20.12
            • CodePorting.Native Cs2Cpp 20.11
            • CodePorting.Native Cs2Cpp 20.10
            • CodePorting.Native Cs2Cpp 20.9
            • CodePorting.Native Cs2Cpp 20.8
            • CodePorting.Native Cs2Cpp 20.7
            • CodePorting.Native Cs2Cpp 20.6
            • CodePorting.Native Cs2Cpp 20.5
            • CodePorting.Native Cs2Cpp 20.4
            • CodePorting.Native Cs2Cpp 20.3
            • CodePorting.Native Cs2Cpp 20.2
            • CodePorting.Native Cs2Cpp 20.1
          • 2019
            • CodePorting.Native Cs2Cpp 19.1
            • CodePorting.Native Cs2Cpp 19.2
            • CodePorting.Native Cs2Cpp 19.3
            • CodePorting.Native Cs2Cpp 19.4
            • CodePorting.Native Cs2Cpp 19.4.1
            • CodePorting.Native Cs2Cpp 19.5
            • CodePorting.Native Cs2Cpp 19.6
            • CodePorting.Native Cs2Cpp 19.7
            • CodePorting.Native Cs2Cpp 19.8
            • CodePorting.Native Cs2Cpp 19.9
            • CodePorting.Native Cs2Cpp 19.10
            • CodePorting.Native Cs2Cpp 19.11
            • CodePorting.Native Cs2Cpp 19.12
          • 2018
            • CodePorting.Native Cs2Cpp 18.9
            • CodePorting.Native Cs2Cpp 18.9.1
            • CodePorting.Native Cs2Cpp 18.10
            • CodePorting.Native Cs2Cpp 18.11
            • CodePorting.Native Cs2Cpp 18.12
    1. Home
    2. CodePorting.Native Product Family
    3. CodePorting.Native Cs2Cpp
    4. Developer Guide
    5. C++ user-defined exception classes

    C++ user-defined exception classes

    What's on this Page

      • Introduction
        • Problem description
      • Approach description
      • Declaring compatible exceptions
        • Compact definition
        • Cross-module exceptions

    Introduction

    Due to the fact that C++ allows to allocate an exception on stack, when in C# language exception is a reference type,  it was necessary to design a solution which allows to extend exception instance lifetime and preserve its type without losing an opportunity to use exception in the common for C++ way.

    Problem description

    Due to the fact that C# exceptions are represented as object type it can be stored or caught with exception type trimming. Let see sample code:

    // C# code
    Exception e = null;
    try
    {
        throw new ArgumentException("abc");
    }
    catch (Exception caught)
    {
        e = caught; // (1)
    }
    try
    {
        throw e; // (2)
    }
    catch (ArgumentException arg)
    {
        Console.WriteLine("Caught!");
    }

    If the most common approach for C++ language, when exception instance is stack-allocated, is used thrown exception from the second try catch block won’t be caught. To solve this exception type trimming new approach was designed.

    Approach description

    In order to accomplish defined goals C# exception classes were splitted in 2 instances.

    First one, from this point and below lets call it exception body, mostly serves utility functions. It defines exception class functions, contains logic and methods of C# exception classes (such as ToString() and Equals()) as well as user-defined logic. The names of such classes are usually prefixed with “Details_” string. Such classes inheritance represents C# exception class hierarchy, instances of such classes are always allocated on heap and are responsible for exception data owning, RTTI, instances comparison and so on. Derived exception body is always inherited from base exception body. All such classes must be inherited from System::Details_Exception directly or indirectly. It is prohibited to throw instances of exception body. It is also prohibited to create instances of exception body by calling its constructor.

    Second part is ExceptionWrapper template class and its instantiations. From architecture point of view, this classes only contain shared pointer to exception body and provide indirect access to it via calls of pointer dereferencing operator. Also, ExceptionWrapper template allows to make upcast and downcast of exception without exception type trimming. Inheritance tree is the same as for exception bodies, but it is not required to specialize ExceptionWrapper template for each class, as all required inheritance is being generated by the default body. Instances of ExceptionWrapper must be stack-allocated only. Also, only instances of ExceptionWrapper template can be used for throw syntax constructions. ExceptionWrapper class is also inherited from std::exception, which allows to use it in C++ notation.

    As an example, lets define new exception class UnknownValueException, which is inherited from System.ArgumentException, which is in turn inherited from System.Exception. When the developer wants to correctly define such exception, they need to define Details_UnknownValueException class, which is inherited from System::Details_ArgumentException, which is already inherited from System::Details_Exception. In the exception body, one must provide additional RTTI information (please, see Requiremens section below). ExceptionWrapper template class will automatically create necessary inheritance tree by using SFINAE approach. From this point, ExceptionWrapper<Details_UnknownValueException> is inherited from ExceptionWrapper<System::Details_ArgumentException>, which is already inherited from ExceptionWrapper<System::Details_Exception>.

    It is not allowed to create instances of exception body manually. It is only allowed to create ExceptionWrapper instances, which will create and own instances of required exception bodies. To throw exception, ExceptionWrapper::Throw() method must be called. Throwing ExceptionWrapper instances directly is not recommended, as the exception type will be trimmed to the one being thrown. Using ExceptionWrapper::Throw guarantees, that the type of contained exception body will be rethrown, even if the ExceptionWrapper instance was type-trimmed.

    Declaring compatible exceptions

    There are several macros available to simplify declaring the compatible excecption types. When there’s a single module involved, the compact declaration may be done at a single place. When the exception instances are to cross module border, one may use the syntax to define the exception class and its members separately, which also allows for neccessary export macros.

    All neccessary macros are defined at <system/exception.h> header in the ‘include’ subdirectory of CodePorting.Native Cs2Cpp package.

    Compact definition

    There are 3 macros to use when defining custom exception type in-place:

    1. CODEPORTING_USER_EXCEPTION_BEGIN which starts the exception class declaration with neccessary internal members. It also defines the required ExceptionWrapper specialization. After this macro, you’re effectively inside the exception body class and may define constructors and other members. This macro takes the following arguments:
      1. Full namespace the class is being defined into (e. g. ‘Living::FoodControl’);
      2. Name of the exception class (without ‘Details_’ prefix, e. g. ‘MealMissed’);
      3. Name of the parent exception body class (with ‘Details_’ prefix, e. g. ‘System::Details_Exception’).
    2. CODEPORTING_USER_EXCEPTION_CONSTRUCTOR which starts the exception constructor definition. Please note that this is the only valid way of adding constructors to exception classes (but the members of other kinds can be added normally). Constructor body must follow this macro. All constructors defined this way are protected which is the intended as only the ExceptionWrapper class is allowed to instantiate exception bodies. This macro takes the following arguments:
      1. Name of the exception class (without ‘Details_’ prefix);
      2. Arguments with types, wrapped into CODEPORTING_ARGS macro (e. g. ‘CODEPORTING_ARGS(String mealName, bool hungry)’ or ‘CODEPORTING_ARGS()’ for default constructor);
      3. Arguments without types, wrapped into CODEPORTING_ARGS macro (e. g. ‘CODEPORTING_ARGS(mealName, hungry)’ or ‘CODEPORTING_ARGS()).
    3. CODEPORTING_USER_EXCEPTION_END which closes the exception class declaration.

    The below example defines two exception classes, MealMissed and DinnerMissed, the later being the subclass and the former being the superclass. The code that follows illustrates that the aforementioned problem is solved.

    #include <system/exceptions.h>
    using namespace System;
    
    namespace Living
    {
        namespace FoodControl
        {
            CODEPORTING_USER_EXCEPTION_BEGIN(Living::FoodControl, MealMissed, System::Details_Exception)
                CODEPORTING_USER_EXCEPTION_CONSTRUCTOR(MealMissed, CODEPORTING_ARGS(String mealName, bool hungry), CODEPORTING_ARGS(mealName, hungry))
                    : ::System::Details_Exception(u"Missed a meal: " + mealName), m_hungry(hungry)
                {}
            public:
                bool isHungry() const
                {
                    return m_hungry;
                }
            private:
                bool m_hungry;
            CODEPORTING_USER_EXCEPTION_END
    
            CODEPORTING_USER_EXCEPTION_BEGIN(Living::FoodControl, DinnerMissed, Details_MealMissed)
                CODEPORTING_USER_EXCEPTION_CONSTRUCTOR(DinnerMissed, CODEPORTING_ARGS(), CODEPORTING_ARGS())
                    : Details_MealMissed(u"dinner", true)
                {}
            CODEPORTING_USER_EXCEPTION_END
        }
    }
    
    TEST(ExceptionsTest, UserDefinedExceptionTest)
    {
        Living::FoodControl::MealMissed exception(nullptr);
    
        try
        {
            throw Living::FoodControl::DinnerMissed();
        }
        catch (const Living::FoodControl::MealMissed &caught)
        {
            exception = caught;
        }
    
        try
        {
            exception.Throw(); // Unlike 'throw exception', won't trim the type
            FAIL();
        }
        catch (const Living::FoodControl::DinnerMissed &caught)
        {
            ASSERT_TRUE(caught->isHungry());
            ASSERT_EQ(caught->get_Message(), u"Missed a meal: dinner");
        }
        catch (const Living::FoodControl::MealMissed&)
        {
            FAIL();
        }
        catch (...)
        {
            FAIL();
        }
    }

    Cross-module exceptions

    There are the macros to use when defining custom exception type that is capable of crossing module’s borders.

    1. CODEPORTING_DECLARE_USER_EXCEPTION_BEGIN which starts the exported exception class declaration with neccessary internal members. It also defines the required ExceptionWrapper specialization. After this macro, you’re effectively inside the exception body class and may define constructors and other members. This macro takes the following arguments:
      1. Export/import macro for the class level (e. g. ‘attribute((visibility(“default”)))');
      2. Export/import macro for the method level (e. g. ‘__declspec(dllexport)');
      3. Full namespace the class is being defined into (e. g. ‘Living::FoodControl’);
      4. Name of the exception class (without ‘Details_’ prefix, e. g. ‘MealMissed’);
      5. Name of the parent exception body class (with ‘Details_’ prefix, e. g. ‘System::Details_Exception’).
    2. CODEPORTING_EXPORTED_USER_EXCEPTION_CONSTRUCTOR which makes the exception constructor declaration. Please note that this is the only valid way of adding constructors to exception classes (but the members of other kinds can be added normally). Constructor body must be defined separately. All constructors defined this way are protected which is the intended as only the ExceptionWrapper class is allowed to instantiate exception bodies. This macro takes the following arguments:
      1. Export/import macro for the method level (e. g. ‘__declspec(dllimport)');
      2. Name of the exception class (without ‘Details_’ prefix);
      3. Arguments with types, wrapped into CODEPORTING_ARGS macro (e. g. ‘CODEPORTING_ARGS(String mealName, bool hungry)’ or ‘CODEPORTING_ARGS()’ for default constructor);
      4. Arguments without types, wrapped into CODEPORTING_ARGS macro (e. g. ‘CODEPORTING_ARGS(mealName, hungry)’ or ‘CODEPORTING_ARGS()).
    3. CODEPORTING_USER_EXCEPTION_END which closes the exception class declaration.
    4. CODEPORTING_USER_EXCEPTION_IMPLEMENTATION which defines all internal members of the exception class. Unlike all other macros, this should only be used in a ‘cpp’ file.

    The below example defines the exception class in an exportable-importable way.

    // api_defs.h
    
    #pragma once
    
    #if defined(_MSC_VER)
        #if defined(MY_MODULE_SHARED_EXPORTS)
            #define MY_MODULE_SHARED_API __declspec(dllexport)
        #else
            #define MY_MODULE_SHARED_API __declspec(dllimport)
        #endif
        #define MY_MODULE_SHARED_CLASS
    #elif defined(__GNUC__)
        #if defined(MY_MODULE_SHARED_EXPORTS)
            #define MY_MODULE_SHARED_API __attribute__((visibility("default")))
            #define MY_MODULE_SHARED_CLASS __attribute__((visibility("default")))
        #else
            #define MY_MODULE_SHARED_API
            #define MY_MODULE_SHARED_CLASS
        #endif
    #else
        #define MY_MODULE_SHARED_CLASS
        #define MY_MODULE_SHARED_API
    #endif
    // cross_module_exception.h
    
    #pragma once
    
    #include "api_defs.h"
    #include <system/exceptions.h>
    
    
    namespace my_module
    {
    
        enum class ThrownFrom
        {
            ProjectA,
            ProjectB,
            ProjectC
        };
    
        CODEPORTING_DECLARE_USER_EXCEPTION_BEGIN(MY_MODULE_SHARED_CLASS, MY_MODULE_SHARED_API, my_module, CrossModuleException, ::System::Details_Exception)
            CODEPORTING_EXPORTED_USER_EXCEPTION_CONSTRUCTOR(MY_MODULE_SHARED_API, CrossModuleException, CODEPORTING_ARGS(ThrownFrom thrown_from), CODEPORTING_ARGS(thrown_from))
        public:
            MY_MODULE_SHARED_API ThrownFrom WhereThrown() const;
        private:
            ThrownFrom m_thrown_from;
        CODEPORTING_USER_EXCEPTION_END
    
    }
    // cross_module_exception.cpp
    
    #include "cross_module_exception.h"
    
    CODEPORTING_USER_EXCEPTION_IMPLEMENTATION(MY_MODULE_SHARED_CLASS, MY_MODULE_SHARED_API, my_module, CrossModuleException, ::System::Details_Exception)
    
    my_module::Details_CrossModuleException::Details_CrossModuleException(ThrownFrom thrown_from)
        : System::Details_Exception(u"CrossModule"), m_thrown_from(thrown_from)
    {}
    
    my_module::ThrownFrom my_module::Details_CrossModuleException::WhereThrown() const
    {
        return m_thrown_from;
    }