AddThis Social Bookmark Button

Print

Effective Interop with Managed C++
Pages: 1, 2

Another potential problem with ManagedClass1 is the lack of try/catch blocks. Unmanaged code can throw unmanaged exceptions, and if you let these propagate into managed code, not only will you break the illusion of your class being managed, the resulting managed SEHException probably won't be as informative as the original unmanaged one. A well-designed managed wrapper class should always catch known unmanaged exceptions and convert them to managed ones.



With these problems in mind, a better wrapper class implementation might be:

__gc class ManagedCustomer2
{
public:
  ManagedCustomer2() { m_pCustomer = new Customer(); }
  ~ManagedCustomer2() { delete m_pCustomer; }

  __property String* get_Name()
  {
    return m_pCustomer->GetName().c_str();
  }

  __property void set_Name(String* name)
  {
    m_pCustomer->SetName( ToStdString(name) );
  }

  double CalculateAccountValue(void)
  {
    double val = 0.0;

    try
    {
      val = m_pCustomer->CalculateAccountValue();
    }
    catch ( UnmanagedException& ue )
    {
      throw new ApplicationException( ue.Message().c_str() );
    }

    return val;
  }

private:
  Customer* m_pCustomer;
};

Ultimately, the goal is to present the illusion that the consumer is interacting with "real" managed objects. By doing this, not only will your unmanaged objects appear to be model .NET citizens, but it also makes it easier if you decide to later swap out your object's unmanaged implementation with a managed one.

Avoid Unnecessary Managed/Unmanaged Code Transitions

Minimizing the number of managed/unmanaged code transitions is crucial for good performance in any .NET application. Even C# and Visual Basic programmers aren't immune from this problem, as overuse of PInvoke or COM interop can turn an otherwise lightning-fast application into one that runs slower than molasses in winter.

While PInvoke and COM interop make it obvious where managed/unmanaged transitions occur in your code, it is not always straightforward to identify these transitions when using managed C++. In contrast to the explicit behavior of PInvoke and COM interop, the managed C++ compiler attempts to compile your code as managed, but quietly falls back to using unmanaged code as necessary. This behavior is key to the "It Just Works" (IJW) design philosophy of managed C++, and makes it possible to port legacy code to managed C++ quickly and easily. However, the downside is that it also makes it easy to lose track of all of the managed/unmanaged code transitions in your application.

Let's examine what happens if you compile a C++ function as managed C++ (i.e., using the /clr compiler option):

static std::wstring SayHello(const std::wstring& name)
{
  wchar_t buf[40];
  swprintf(buf, L"Hello %s!", name.c_str() );
  return buf;
}

(The purpose of this function is to explore what happens when it's compiled using managed C++. It certainly isn't going to win any programming excellence awards.)

Inspecting the compiled output of this code using ILDASM reveals that the SayHello() function compiles to managed code, as expected. However, internally, the function calls three other methods, none of which are managed:

  • Call to c_str().
  • Call to swprintf().
  • Implicit call to std::wstring's constructor for the function's return value.

Because each of these calls is to functionality that exists in an external STL or CRT library, managed C++ cannot generate managed code for these three functions. As a result, they remain unmanaged, causing an expensive managed/unmanaged transition (also called an "IJW thunk") to occur as each is executed.

To complicate matters further:

  • Not every call to an external library results in an IJW thunk. Functions that are implemented inline in a header file may still be compiled to managed code.

  • Not all functions can even be compiled to managed code. Managed C++ always generates unmanaged code when it encounters certain constructs.

As you can see, it's not always easy to know in advance how many IJW thunks will be generated when using managed C++. Because of this, compiling legacy applications using managed C++ often does "just work," but can also take a large toll in performance.

So how do you reduce the number of these thunks? There are several techniques, but the easiest is, whenever possible, to leave your legacy code as native C++. At first this might seem counterintuitive, but when performing interop work, there's no reason to recompile legacy applications wholesale using managed C++. (And, as you've seen, this usually does more harm than good.) Instead, do the opposite -- create a thin layer of managed code that wraps access to the unmanaged portion. Not only does this greatly reduce the number of IJW thunks, but it leaves the bulk of your code unmanaged (don't forget that unmanaged code is still faster than managed).

Other ways to reduce the number of IJW thunks are:

  • Learn to take manual control over what code is generated as managed vs. unmanaged. The C++ compiler allows individual files in projects to be compiled as managed C++ using the /clr switch, and within those files, you can further limit managed code generation by using the #pragma managed and #pragma unmanaged statements.

  • Follow Microsoft's recommendation of using chunky, not chatty, calls.

  • Use ILDASM to examine your managed output and find out where thunks are taking place. Armed with this knowledge, you're often able to identify other opportunities for reducing IJW thunks.

Keep in mind that these tips are only for improving your application's performance with managed C++. If you're happy with your managed C++ application's performance, then none of this may be necessary. However, if your application could use a boost, reducing the number of managed/unmanaged code transitions can make a dramatic improvement in performance.

Conclusion

There is a lot more to managed C++ interop than can be covered in a single article. However, I hope you've learned about some of the unique issues that affect managed C++ interop work and have picked up a few practical techniques to help you deal with them.

John Bush


Return to ONDotnet.com