Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Another use of SEH

0.00/5 (No votes)
17 Jun 2004 1  
How to use SEH to supply a parameter to a function.

Introduction

Suppose you have written a function with a specific set of parameters. However, this function is designed in such a way that sometimes it needs more parameters. What does that mean? Wait, I'll explain.

If this function is called by your own functions, which in turn are also called by your functions and so on, then you can simply supply all possible parameters to all functions from which our "problematic" function can ever be called. Thus our function will always receive all the parameters it may ever need. But the disadvantage of this solution is that you have to supply all possible parameters to every function in the chain. This takes more stack, and makes the program totally unreadable. And also, you have to modify dozens of your code to add one single parameter to a single function, because you'll have to add it to all your functions.

There can also be another situation, where your function is called from within another function, which is not yours. In fact - your function is a callback function with a predefined set of parameters, which can't be changed. Well, usually in such situations, smart design always allows you to specify at least one parameter to your callback function (a context), but, alas, not all designs are smart :). Also, sometimes you'd like to have more parameters anyway.

So, what can be done in such situations?

  1. Use of global variables. Before your function could be called, you set some values to global variables, which are accessible from anywhere. This is the most straight and stupid way. Use of global variables should be avoided as much as possible. What if you call this function in more than one situation? And what if you call this function from within another pending call to the same function? And what will you do in the multi-tasking environment? Well, all that can be solved, but in very ugly ways.
  2. We can improve a bit the previous solution by using TLS. We won't have to bother about multi-tasking synchronization. However, it still has almost the same disadvantages. In addition, you must initialize the TLS (allocate a TLS index), hence your code needs a global initialization/uninitialization.
  3. And, finally, there is a way to supply a parameter to a function without the use of global variables. It uses SEH (Windows structured exception handling). Take a look at the following sample:
const DWORD EXCEPTION_FETCHPARAM_B = 0x800A0001L;

void Func_Nested(int a)
{
    // do some job. At some situation we realize

    // that we need another parameter.

    // Let's fetch it.

    int b;

    ULONG_PTR nExceptionParam = (ULONG_PTR) &b;
    RaiseException(EXCEPTION_FETCHPARAM_B, 0, 1, &nExceptionParam);

    // By now 'b' is filled. Continue our work as usual.

}

void Func_Intermediate(int a)
{
    // do some job. At some point call nested function

    Func_Nested(a);
}

void Func_Top()
{
    // at this point we have all possible parameters.


    EXCEPTION_POINTERS* pException;
    __try
    {
        Func_Intermediate(12);
    }
    __except
        (
          pException = GetExceptionInformation(),
          EXCEPTION_FETCHPARAM_B == pException->ExceptionRecord->ExceptionCode ?
           (
             ASSERT(1 == pException->ExceptionRecord->NumberParameters),
             ASSERT(pException->ExceptionRecord->ExceptionInformation[0]),
             *((int*) pException->ExceptionRecord->ExceptionInformation[0]) = 14,
             EXCEPTION_CONTINUE_EXECUTION
           ) :
          EXCEPTION_CONTINUE_SEARCH
        )
    {
        // This point must not be reached,

        // since we never return EXCEPTION_EXECUTE_HANDLER

    }
}

In this sample, we start from Func_Top, where we have all possible parameters, from there we call Func_Intermediate, and this function eventually calls Func_Nested. At some point, it realizes that it needs one more parameter. It raises a SEH exception with one parameter, which is a pointer to a placeholder for a missing parameter. Next, the OS looks for a suitable handler for this exception. Our handler detects this exception by querying its code, writes the missing parameter, and then reports to the OS that the exception is dismissed by returning EXCEPTION_CONTINUE_EXECUTION code.

Next, this sample can be extended to an arbitrary number of 'missing' parameters. Take a look:

DWORD ParseException(EXCEPTION_POINTERS* pException)
{
    switch (pException->ExceptionRecord->ExceptionCode)
    {
    case EXCEPTION_FETCHPARAM_B:
        ASSERT(1 == pException->ExceptionRecord->NumberParameters);
        ASSERT(pException->ExceptionRecord->ExceptionInformation[0]);
        *((int*) pException->ExceptionRecord->ExceptionInformation[0]) = 14;
        return EXCEPTION_CONTINUE_EXECUTION;

    case EXCEPTION_FETCHPARAM_C:
        ASSERT(1 == pException->ExceptionRecord->NumberParameters);
        ASSERT(pException->ExceptionRecord->ExceptionInformation[0]);
        *((int*) pException->ExceptionRecord->ExceptionInformation[0]) = 16;
        return EXCEPTION_CONTINUE_EXECUTION;

        // and so on. Fill the needed parameter by querying exception code.

    }
    return EXCEPTION_CONTINUE_SEARCH;
}

void Func_Top()
{
    // at this point we have all possible parameters.

    __try
    {
        Func_Intermediate(12);
    }
    __except(ParseException(GetExceptionInformation()))
    {
        // This point must not be reached,

        // since we never return EXCEPTION_EXECUTE_HANDLER

    }
}

Performance

It is believed that exception-handling mechanism is very slow and heavy. This is not accurate. It becomes really heavy only if the exception has been issued (handler returns EXCEPTION_EXECUTE_HANDLER). In such a case takes place so-called unwind operation, which is a rapid return from all nested scopes, during which also guarded sections are executed (__finally blocks). In our case, the exception is not issued. We just use SEH mechanism to communicate between some nested point and the __except block. Such a trick can be compared to a function call by its complexity.

BTW: When you call RaiseException function and your program runs under a debugger - it may write in the console something like "First-chance exception blablabla...". If you do this too frequently - the debugger can consume too much CPU for those outputs, but that doesn't mean that this method is heavy.

Where to use

This method is very powerful. It allows you to fetch parameters in almost any place in your application from up to WinMain function.

General rule: you must be absolutely sure that the call to your function will take place in the same thread you put the __try - __except block, otherwise the exception will lead to a crash.

Another precaution: some people like to dismiss all exceptions in their code, to make buggy programs look more stable. So, if one of your nested functions uses such a dirty trick - this method won't be too useful. But I personally believe that is not a correct concept.

There seems to be a bit weird thing when raising such an exception from within a window procedure, where the handler resides out of the GetMessage -> DispatchMessage loop, or the DoModal functions. It seems to work ok, but the User32 issues a page error exception (which it dismisses too).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here