Archive for June, 2011

It is simple to wrap unmanaged C++ with C++/CLI by holding a native pointer in the ref class and using it as a proxy. See http://www.codeproject.com/KB/mcpp/quickcppcli.aspx#A8. But what happens when a function in your native class returns an object encapsulated with a std::shared_ptr? (Developers should be using shared_ptr’s in most cases now)

Let’s consider a trivial example. We have a native C++ library that defines what a book is. 1 Book can contain at least one page. And for the sake of simplicity, you can retrieve the number of words for a particular page.

In UML:

In code:


#pragma once

class Page {

public:

     Page(int words);
     ~Page(void);

     int getNumberOfWords() { return m_numberOfWords; }

private:
     int m_numberOfWords;
};

and the book defined as:


#pragma once
#include <vector>

class Page;

class Book {

public:

     Book(void);
     ~Book(void);

     std::shared_ptr<Page> getPage(int pageNumber);

private:

     std::vector<std::shared_ptr<Page> > m_pages;
};

Notice the getPage function returns a std::shared_ptr<Page>. Following the normal pattern of wrapping unmanaged C++ objects, if you wanted to create a wrapper for Page, you would hold a member variable that holds a pointer to the page object. Eg.


public ref class PageRef {

public:

     PageRef(Page* page) : { m_page(page); }
     ~PageRef(void){}

     System::Int32 getNumberOfWords() { return m_page->getNumberOfWords(); }

private:
     Page* m_page;
};

In the above UML diagram, a Book object contains at least 1 page and the pages are managed by a private member variable std::vector<std::shared_ptr<Page>> and you can access a page through getPage which also returns a Page object whose lifetime is managed by a shared_ptr. So how do you return a shared_ptr<Page> object? You can’t make PageRef hold a std::shared_ptr<Page> member variable as the /clr will not allow that. It will only allow holding a pointer to a native object.

We could hold a pointer to a shared_ptr or we could wrap the the shared_ptr with a C++/CLI smart pointer (http://codereview.stackexchange.com/questions/1695/scoped-ptr-for-c-cli-ensure-managed-object-properly-frees-owned-native-object). The link provides an implementation of one such C++/CLI scoped pointer. The CLR scoped pointer ensures the shared_ptr is appropriately released by allocating a new shared_ptr on the heap. The scoped pointer does not allow for transfer of ownership. It holds a pointer to a shared_ptr as a member variable and deletes the resource when it is no longer needed.

Using the clr_scoped_ptr we can now modify the PageRef class:


#pragma once
#include "clr_scoped_ptr.h"

#include <memory>

class Page;

public ref class PageRef {

public:

     PageRef(std::shared_ptr<Page>& page) : m_page(new std::shared_ptr<Page>(page)){}
     ~PageRef(void){}

     System::Int32 getNumberOfWords();

private:
     clr_scoped_ptr< std::shared_ptr<Page> > m_page;
};

The * and -> operators are overloaded in the clr_scoped_ptr so then you can do the following in the PageRef implementation, as per the normal C++/CLI wrapping of unmanaged code pattern:


#include "PageRef.h"
#include "Page.h"

System::Int32 PageRef::getNumberOfWords()
{
     return (*m_page)->getNumberOfWords();
}

Note that I have committed a sin by passing a shared_ptr by reference. However, the purpose of a shared_ptr is to guarantee that  as long as this shared_ptr is in scope, the object it points to will still exist, because its reference count will be at least 1. In this case, we actually maintain that guarantee as we recreate the shared_ptr and allocate it to the heap.

We can then use our native C++ class in C# like so:


static void Main(string[] args)
{
     Library lib = new Library();

     BookRef book = lib.GetBook(0);

     PageRef page = book.getPage(0);

     System.Console.WriteLine(page.getNumberOfWords());
     System.Console.ReadLine(); // wait for user input before exiting console
}

Posted By:
calc June 24, 2011