Sunday, October 4, 2009

Notes on C++/CLI

(Cross post from IRefactor)
If you asked for my opinion whether to develop an application using (unmanaged) C++, I would strongly advise you to reconsider.

Unless you deal with real time applications (or near real time applications), you should better utilize the managed world. Sure, there are times for old good C++; especially when the application's memory footprint is an issue, but needless to say, you have better chances in productivity, ease of development and maintainability in the managed applications (C#, Java and etc...) than in the unmanaged world.

Sometimes, obviously, the above advice is impractical. When dealing with complex algorithmic issues, which already have pretty sound support in various unmanaged C++ toolkits and frameworks, you are forced to choose what exists over reinventing the wheel.

C++/CLI can bridge the gap.
The common practice is to wrap the unmanaged libraries with the C++/CLI, thus exposing the unmanaged functionality through the managed C++ layer. In the example below, Taxes project contains various algorithms for tax calculations written in (unmanaged) C++. The Taxes project is wrapped with TaxesMixed project which is a C++/CLI project. Finally, TaxesApp project, which is written in C#, uses the TaxesMixed wrapper to utilize the complex unmanaged tax calculations.


Tipical Solution

When dealing with unmanaged, mixed (C++/CLI) and managed projects, there are some notes to keep in mind:

Debugging

If you want to debug the unmanaged code (e.g.Taxes above) from the managed (e.g. TaxesApp above), don't forget to mark the option enable unmanaged code debugging on the debug tab of the managed project's settings.


Enable unmanaged debugging tab

Implementing Destructor/Finalizer

C++/CLI classes that use unmanaged C++ classes should implement Dispose\Finalize pattern (look for: "Deterministic finalization template" title). Apparently, implementing the pattern is much easier using C++/CLI than C#. All you need, is to provide a deterministic destructor (~ syntax) and a finalize method (! syntax) and everything else will be generated by the C++/CLI.


TaxesMixed::TaxCalculatorWrapper::~TaxCalculatorWrapper()
{
this->!TaxCalculatorWrapper();
}

TaxesMixed::TaxCalculatorWrapper::!TaxCalculatorWrapper()
{
if(nullptr != m_Calculator)
{
delete m_Calculator;
}
}
C++/CLI will generate all the additional Dispose, Dispose(bool) and Finalize methods in order to complete the pattern.


Here is a quick peek into the Dispose(bool) method, using Reflector.
As you can see, the method calls the deterministic Dispose method (~) when the TaxWrapper.Dispose() method is called and calls the Finalize method (!) when the TaxWrapper is garbage collected.



To pin or not to pin?

When a pointer to a managed object (or its part) needs to be passed to the unmanaged code, there is one important issue to be aware of:
Managed objects (on CLR Small Object Heap) don't remain at the same location for their lifetime as they are moved during the GC heap compaction cycles.
As a consequence,a native pointer that points to a CLI object becomes garbage once the object has been relocated. In order to ensure a correct behavior, C++/CLI introduces a pin_ptr pointer which pins a CLI object in order to prevent its relocation while a garbage collection cycle occurs.

(If you need a pointer semantic in C++/CLI you can use an interior_ptr pointer which is updated automatically when a garbage collection cycle occurs and remains a valid pointer even after the compaction.)

You declare a pin_ptr as follows:

pin_ptr<type> = &initializer;
Example:

public class Person
{
public string Id {get; protected set;}
public double[] Taxes { get; protected set;}

public Person()
{
Id = "SSN:12345"
Taxes = taxesDal.Load(this.Id);
}
}

void TaxesMixed::TaxCalculatorWrapper::CalculateAnnualTaxes(Person^ person)
{
//Pinning a whole object.
pin_ptr<Person^> ptr = &person;
//Passing the object or one of its value members to a native code.
. . .

//Pinning an array (by pinning the first element).
pin_ptr<double> arrPtr = &person->Taxes[0];
//Assigning to a native pointer
double* arr = arrPtr;
. . .
}
When dealing with pin_ptr you should remember:

  • Pinning a sub-object defined in a managed object has the effect of pinning the entire object.
  • Pinning an object also pins its value fields (that is, fields of primitive or value type). However, fields declared by tracking handle are not pinned.
    (To avoid redundant mistakes, my suggestion is to always pin the required reference type member, thus pinning the whole object.)
  • A pinning pointer can point to a reference handle, value type or boxed type handle, member of a managed type or to an element of a managed array. It cannot point to a reference type.
In any case, if you are interested in reading more about C++/CLI, you can find here very good tutorials on the topic.

3 comments:

  1. I'd say the answer may be the following - if, technologically, the main purpose of your application is to manager a database, use C#, whatsoever but do not complicate the task using the unmanaged code. If, in addition, the application is going have a rich GUI or interact with the OS on low-level - make only this part in C++ in a separate module.
    If the application is mainly about the GUI and has almost nothing about the database - C++ is a good choice. Especially if you are going to sell it.

    ReplyDelete
  2. What do you think about creating a COM wrapper around the an unmanaged c++ dll and interfacing with .Net using COM objects? the c++/cli technique can be a lot of work.

    ReplyDelete
  3. Well, usually my answer is that it depends :)

    However, in most cases I would say that C++/CLI is the most powerful language on the .NET stack. It minimizes the interop with a managed code in more effective way than a COM wrapper, allows to control memory layout (if you insist) and enables deterministic destruction. Especially if you plan to have a thin(!) layer over a native code, I would say it's better than utilizing a COM wrapper.

    ReplyDelete