//
you're reading...
.NET, SEHException

Using Structured Exception Handling to Handle Exceptions from C++ in C#.

1. Introduction.

1.1 At some point, every developer who has ever worked with a mix of managed and unmanaged code would wonder whether it is possible to handle exceptions thrown from unmanaged code in managed code.

1.2 In this blog, I shall expound on one way this can be achieved. The unmanaged code that I shall use to throw exceptions from will be written using Visual C++. The managed code that will handle the exception will be based on C#.

2. C++ Exceptions and Structured Exception Handling (SEH).

2.1 By default, it is possible to handle C++ exceptions in managed code.

2.2 All exceptions thrown from sources external to the CLR can be handled in managed code and are supported via the ExternalException class which is the base class for the COMException and SEHException classes.

2.3 The “SEH” in SEHException is an acronym for “Structured Exception Handling”. A C++ exception will end up being an SEHException by default.

2.4 SEH is a generic exception handling mechanism developed by Microsoft for use by the entire OS. It is programming language independent. Structured Exception Handling (SEH) is supported in Visual C++ and C#. An interesting note is that under the covers the Visual C++ compiler uses SEH for implementing C++ exception handling.

2.5 However, note that for a default SEHException instance received as a result of a C++ throw statement, no application-specific exception information is provided in managed code even if the C++ throw statement includes a throw parameter.

2.6 This is because C++’s exception handling syntax (e.g. throw, catch) applies only to the C++ language. There is no intrinsic mechanism available that can translate a specific C++ exception parameter to a managed counterpart.

2.7 However, using SEH, it is possible to share exception information between unmanaged and managed code. This blog aims to demonstrate how this can be achieved.

3. Default SEHException Handling.

3.1 Let’s say we have a simple C++ API that throws a C++ exception as follows :

void __stdcall TestThrowCPPException()
{
  throw -1;
}

3.2 Let’s say we have the following C# client code that calls such an API :

private static void CallTestThrowCPPException()
{
    try
    {
        TestThrowCPPException();
    }
    catch (Exception ex)
    {
        Console.WriteLine("{0:S}", ex.Message);
    }
}

This code expects that at minimum, when the API is called and the C++ exception is thrown, the C# code will be notified of a managed exception of type Exception.

3.3 And it will be so. When this code is run, the exception thrown from the C++ code will be caught in the exception block and the following console output will be displayed :

External component has thrown an exception.

3.4 In the code of point 3.2, the caught exception is of type Exception which is the base class of all .NET exceptions. We can narrow this down further to :

try
{
    TestThrowCPPException();
}
catch (SEHException seh)
{
    Console.WriteLine("{0:S}", seh.Message);
}
catch (Exception ex)
{
    Console.WriteLine("{0:S}", ex.Message);
}

Our C++ exception can be directly handled by a SEHException class instance as expounded in section 2.

3.5 However, if we were to look closely at the contents of the SEHException instance via the Quick Watch dialog box, we will see no member data that can link this SEHException instance to the parameter of the C++ throw, i.e. the integer value -1.

3.6 The only member data that appears to be of some worth is “ErrorCode” :

However, this ErrorCode will always be set to 0x80004005 (which translates to “Unspecified error”) no matter what type and value was used in the C++ throw statement. Not very useful.

4. Useful SEHException Handling.

4.1 In order to meaningfully use SEH, we need to call the RaiseException() API instead of using the C++ throw statement.

4.2 The advantage of using the RaiseException() API over the use of the throw statement is that custom data can be transmitted from the code that raised the SEH exception to the recipient of the SEH exception.

4.3 This can come in the form of a simple exception code and/or pointers to more complex data structures. These are specified via the first and fourth parameters of the RaiseException() API. For more information, see the RaiseException() API on MSDN.

4.4 In the example code of section 7, I shall demonstrate how both forms of custom information may be used. Since we will be passing a pointer to custom data from the C++ API to the SEHException handler in C#, I shall first provide some information on this custom data. I shall also explain how this data can be made usable in managed code.

5. Custom Exception Data.

5.1 The custom data that we will be passing from C++ to C# comes in the form of a C++ structure :

#pragma pack(1)

struct MySEHExceptionStruct
{
  LPSTR m_lpszMessage1;
  char m_szMessage2[256];
};

This structure contains a pointer to a C-style string and an inline array of 256 characters.

5.2 When the SEH exception is raised, such a structure must first be allocated in memory using CoTaskMemAlloc(). A pointer to this structure is then passed as an unsigned long value among the array of unsigned long values of the 4th parameter to RaiseException().

5.3 It is important that CoTaskMemAlloc() be used to allocate the memory for this structure because it must later be freed from managed code using Marshal.FreeCoTaskMem(). Note that the structure cannot be allocated using the C++ new keyword nor can it be allocated using malloc().

5.4 The new keyword is compiler-dependent and the malloc() function is C-library dependent. Both issues mean that they are not standardized. CoTaskMemAlloc(), however, is a standard Windows API which has a conjugate CoTaskMemFree() API which is called by the Marshal.FreeCoTaskMem() function.

5.5 When a SEHException is received in managed code, a pointer to the MySEHExceptionStruct structure can be retrieved by using the Marshal.GetExceptionPointers() function (see section 6 for more details).

5.6 After being retrieved, it needs to be converted to a managed structure in order to be usable in managed code. The following is the definition of the managed counterpart of the MySEHExceptionStruct structure :

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
struct MySEHExceptionStruct
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string m_strMessage1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
    public string m_strMessage2;
}

The conversion is done by using the Marshal.PtrToStructure() function.

5.7 The definition of the counterpart managed structure for MySEHExceptionStruct is very important not only because it enables us to translate the unmanaged MySEHExceptionStruct into a managed version, but also because the specifications for its field members (in the form of MarshalAsAttributes) will enable the interop marshaler to deallocate the field contents of the unmanaged MySEHExceptionStruct structure.

5.8 Please note the difference between the freeing of the structure from the freeing of the field contents of the same structure. The first refers to the deallocation of the memory occupied by the structure itself. The second refers to the freeing of the field members of the structure which are references to other memory locations (e.g. m_lpszMessage1 – a pointer to a C-style string). Examples of this will be provided in the example codes of section 7 later.

5.9 The freeing of the memory occupied by the structure itself is done via Marshal.FreeCoTaskMem() and the freeing of the field members is done by calling Marshal.DestroyStructure().

6. How to Obtain the Custom Data in Managed Code.

6.1 As mentioned in point 5.5, when a SEHException is being handled in managed code, the custom data supplied by the caller of RaiseException() can be obtained using Marshal.GetExceptionPointers().

6.2 This method returns an IntPtr to an EXCEPTION_POINTERS structure (defined in winnt.h). It is listed below :

typedef struct _EXCEPTION_POINTERS {
    PEXCEPTION_RECORD ExceptionRecord;
    PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

As documented in MSDN, this structure contains an exception record (the ExceptionRecord field) with a machine-independent description of an exception and a context record (the ContextRecord field) with a machine-dependent description of the processor context at the time of the exception.

Please refer to EXCEPTION_POINTERS Structure on MSDN for more details on this structure.

6.3 Because we will be receiving a pointer to this structure when we call Marshal.GetExceptionPointers(), we will need to define this structure in C# so that we can work with it. The following is a definition of such a structure in C# :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct EXCEPTION_POINTERS
{
    public IntPtr pExceptionRecord; // Points to a EXCEPTION_RECORD record.
    public IntPtr pContext; // Points to a CONTEXT record. This structure contains processor-specific register data.
}

6.4 Among the two fields of the EXCEPTION_POINTERS structure, we do not need to concern ourselves with “ContextRecord” which is useful only if one is developing debuggers. What is important to us is the ExceptionRecord member (type PEXCEPTION_RECORD) which points to an EXCEPTION_RECORD structure (also defined in winnt.h) :

typedef struct _EXCEPTION_RECORD {
    DWORD    ExceptionCode;
    DWORD ExceptionFlags;
    struct _EXCEPTION_RECORD *ExceptionRecord;
    PVOID ExceptionAddress;
    DWORD NumberParameters;
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
    } EXCEPTION_RECORD;

Please refer to EXCEPTION_RECORD Structure on MSDN for more information on this structure.

6.5 The EXCEPTION_RECORD structure is the entry point to the custom data which originates from the call to RaiseException(). The following is a definition of such a structure in C# :

const Int32 EXCEPTION_MAXIMUM_PARAMETERS = 15; // maximum number of exception parameters

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct EXCEPTION_RECORD
{
    public UInt32 ExceptionCode;
    public UInt32 ExceptionFlags;
    public IntPtr pExceptionRecord; // Points to another EXCEPTION_RECORD structure.
    public IntPtr ExceptionAddress;
    public UInt32 NumberParameters;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = EXCEPTION_MAXIMUM_PARAMETERS)]
    public UInt32[] ExceptionInformation;
}

Note the following :

  • As will be seen in the example codes of section 7, the “NumberParameters” field will match the 3rd parameter to the RaiseException() API which will be called in the C++ code,
  • The “ExceptionInformation” field will also match the array of pointers supplied as part of the 4th parameter in the call to RaiseException().
  • Hence all the information which originate from the caller of RaiseException() are obtained from this structure.

6.6 Armed with the above C# definitions for the EXCEPTION_POINTERS and the EXCEPTION_RECORD structures, we can now proceed to discuss the details of handling a SEHException in C# (next section).

7. Example SEH Exception Handling.

7.1 Listed below is a sample C++ code that demonstrates a call to RaiseException() ;

void __stdcall TestRaiseException()
{
  // First allocate space for a MySEHExceptionStruct instance.
  MySEHExceptionStruct* pMySEHExceptionStruct
    = (MySEHExceptionStruct*)::CoTaskMemAlloc(sizeof(MySEHExceptionStruct));
  // Zero out all bytes inside pMySEHExceptionStruct.
  memset (pMySEHExceptionStruct, 0, sizeof(MySEHExceptionStruct));

  // Assign value to the m_lpszMessage member.
  LPCTSTR lpszMessage1 = "SEH Exception Message 1.";
  LPCTSTR lpszMessage2 = "SEH Exception Message 2.";

  pMySEHExceptionStruct -> m_lpszMessage1 = (LPTSTR)::CoTaskMemAlloc(strlen(lpszMessage1) + 1);
  strcpy(pMySEHExceptionStruct -> m_lpszMessage1, lpszMessage1);
  strcpy(pMySEHExceptionStruct -> m_szMessage2, lpszMessage2);

  // Raise the SEH exception, passing along a ptr to the MySEHExceptionStruct
  // structure. Note that the onus is on the recipient of the exception to free
  // the memory of pMySEHExceptionStruct as well as its contents.
  RaiseException(100, 0, 1, (const ULONG_PTR *)(&pMySEHExceptionStruct));
}

The following are the pertinent points concerning the code above :

  • We allocate a MySEHExceptionStruct structure and set its fields properly.
  • Note that the m_lpszMessage1 field is a pointer to a C-style string and the memory for this is also allocated by using CoTaskMemAlloc().
  • The RaiseException() API is called with an exception code of 100 (the first parameter).
  • A value of 1 is passed as the 3rd parameter indicating that we will be passing only one pointer to custom data (see the notes in point 6.5 above).
  • The pointer to the MySEHExceptionStruct structure is passed as an unsigned long value in the 4th parameter (see the notes in point 6.5 above).

7.2 Listed below is a C# function which calls the TestRaiseException() API and handles the ensuing SEHException :

private static void CallTestRaiseException()
{
    try
    {
        TestRaiseException();
    }
    catch (SEHException seh)
    {
        // Marshal.GetExceptionCode() returns the exception code which was passed
        // by as the first parameter to RaiseException().
        int iExceptionCode = Marshal.GetExceptionCode();
        // Get a pointer to the unmanaged EXCEPTION_POINTERS structure.
        IntPtr pExceptionPointers = Marshal.GetExceptionPointers();

        // Convert the unmanaged EXCEPTION_POINTERS structure into its managed version
        // using Marshal.PtrToStructure().
        EXCEPTION_POINTERS exception_pointers
          = (EXCEPTION_POINTERS)Marshal.PtrToStructure(pExceptionPointers, typeof(EXCEPTION_POINTERS));
        // Convert the unmanaged EXCEPTION_RECORD structure into its managed version
        // using Marshal.PtrToStructure().
        EXCEPTION_RECORD exception_record
          = (EXCEPTION_RECORD)Marshal.PtrToStructure
            (
              exception_pointers.pExceptionRecord,
              typeof(EXCEPTION_RECORD)
            );

        // Check the exception code. If it is one we recognize, we proceed
        // to extract our customized exception information, e.i. a pointer
        // to an MyException structure.
        if (((UInt32)iExceptionCode == 100) && (exception_record.NumberParameters > 0))
        {
            // The exception_record.ExceptionInformation[] array contains user-defined
            // data. The first item is a pointer to the unmanaged MySEHExceptionStruct
            // C++ structure.
            // We must convert it into a managed version of the MySEHExceptionStruct
            // structure.
            MySEHExceptionStruct my_seh_exception_structure
              = (MySEHExceptionStruct)Marshal.PtrToStructure
                (
                  (IntPtr)(exception_record.ExceptionInformation[0]),
                  typeof(MySEHExceptionStruct)
                );
            // Display info on exception.
            Console.WriteLine("Exception code : {0:D}.", iExceptionCode);
            Console.WriteLine("Exception Message 1 : {0:S}", my_seh_exception_structure.m_strMessage1);
            Console.WriteLine("Exception Message 2 : {0:S}", my_seh_exception_structure.m_strMessage2);
            // It is the responsibility of the recipient of the exception to free
            // the memory occupied by the unmanaged MySEHExceptionStruct as well
            // as members of the unmanaged MySEHExceptionStruct which are references
            // to other memory. We do this by calling Marshal.DestroyStructure().
            //
            // Marshal.DestroyStructure() requires adequate information for the fields
            // of the target structure to destroy (in the form of MarshalAsAttributes).
            Marshal.DestroyStructure((IntPtr)(exception_record.ExceptionInformation[0]), typeof(MySEHExceptionStruct));
            // Finally, free the unmanaged MySEHExceptionStruct structure itself.
            Marshal.FreeCoTaskMem((IntPtr)(exception_record.ExceptionInformation[0]));
        }
    }
}

The following are the significant points concerning the function above :

  • The TestRaiseException() API is called and the SEH Exception is raised.
  • The C# function uses Marshal.GetExceptionCode() to obtain the exception code which was passed as the first parameter to RaiseException().
  • This exception code is important because it will help to identify the source of the SEH Exception. In our example, we use the number 100 to uniquely identify TestRaiseException() as the source and MySEHExceptionStruct as the custom data that will accompany the exception.
  • We then use Marshal.GetExceptionPointers() to obtain a pointer to the unmanaged EXCEPTION_POINTERS structure.
  • We then use Marshal.PtrToStructure() to convert this pointer into a managed EXCEPTION_POINTERS structure.
  • We further convert the pExceptionRecord field of the EXCEPTION_POINTERS structure (which points to an unmanaged EXCEPTION_RECORD structure) into a managed version.
  • If the exception code and the number of custom data as reported in exception_record.NumberParameters all checked out correct, we proceed to reference the unmanaged MySEHExceptionStruct structure using the exception_record.ExceptionInformation[] array.
  • The first item of this array is a pointer to our structure. We use Marshal.PtrToStructure() to convert it into a managed version of the MySEHExceptionStruct.
  • Once this is done, we are free to use the information contained in the custom structure as we like. In our example, we display its field values.
  • Note that it is the responsibility of the recipient of the SEH Exception to free the memory occupied by the unmanaged MySEHExceptionStruct as well as members of the unmanaged MySEHExceptionStruct which are references to other memory. This is done by calling Marshal.DestroyStructure() on the unmanaged structure.
  • Marshal.DestroyStructure() uses the structure information of the managed MySEHExceptionStruct structure (mostly from the MarshalAsAttributes applied to the field members) to determine how to free the field contents of the unmanaged structure.
  • The “m_lpszMessage1” field member of the unmanaged MySEHExceptionStruct structure corresponds to the “m_strMessage1” field of the managed version which has been attributed with “[MarshalAs(UnmanagedType.LPStr)]”.
  • This indicates to the interop marshaler that the “m_lpszMessage1” field is a pointer to a C-style string buffer. Marshal.DestroyStructure() will thus free this buffer by calling Marshal.FreeCoTaskMem() which will internally call CoTaskMemFree().
  • This why we must use CoTaskMemAlloc() when we allocate memory for this C-style string buffer (see point 7.1).
  • The “m_szMessage2” field member corresponds to the “m_strMessage2” field of the managed structure which has been attributed with “[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]”.
  • This indicates to the interop marshaler that the “m_szMessage2” field is an inline character array embedded as part of the unmanaged MySEHExceptionStruct structure itself. Marshal.DestroyStructure() will thus not free this field. This field will be freed when the entire structure itself is freed.
  • Finally, the unmanaged MySEHExceptionStruct structure itself will need to be free. This is done using Marshal.FreeCoTaskMem() on the pointer to the unmanaged structure.
  • Note that the managed MySEHExceptionStruct structure itself will be subject to garbage collection and so we need not concern ourselves with its deallocation.

8. Some Advise on the Custom Data.

8.1 It is important to keep the custom data (which is allocated on the side that calls RaiseException()) as simple as possible so that it can be represented easily in managed code.

8.2 This custom data need not be a structure. It may be a C-style string allocated by CoTaskMemAlloc() and then translated into a managed string using Marshal.PtrToStringAnsi(). And then freed by using Marshal.FreeCoTaskMem().

8.3 Or it may be a BSTR allocated by SysAllocString() and translated into a managed string using Marshal.PtrToStringBSTR(). And then freed by using Marshal.FreeBSTR().

8.4 The important point to note is that the custom data should be representable in managed code. It must also be possible to free the custom data from managed code.

8.5 If a structure is used, make sure that its field contents can be freed using Marshal.DestroyStructure(). Avoid specifying IntPtr for field types in the managed structure. These cannot be freed by Marshal.DestroyStructure().

8.6 If a C++ class is used, make sure to treat it as if it is a structure to be passed to managed code. It would be a bad idea to include virtual functions for the class. This will result in v-tables and this would make the task much more complicated. Avoid this.

9. In Conclusion.

9.1 I hope the reader has benefitted from this article on using Structured Exception Handling to handle exceptions from C++ in C#.

9.2 I certainly have enjoyed the research work leading up to this writeup. Through articles like this one, I hope to shed more light on the internal architecture of the CLR and show that there is so much common ground between managed and unmanaged code.

 

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

One thought on “Using Structured Exception Handling to Handle Exceptions from C++ in C#.

  1. Thank you very much for this article. It seems to be the only one that I can find on the net that addresses using RaiseException with custom data. I appreciate the effort that you have put in to providing a very clear explanation with easy to follow code.

    Thanks again.

    Posted by Franco | April 13, 2016, 8:19 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: