Material Definition Language API nvidia_logo_transpbg.gif Up
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Library Design

In addition to the introduction given here, the Tutorial and Example Programs explain many topics about the MDL API design.

A list of such topics can be found in the Index of Topics Explained in the Example Programs.

Interfaces

The MDL API follows conventional modern C++ library design principles for component software to achieve binary compatibility across shared library boundaries and future extensibility. The design provides access to the shared library through interfaces, abstract base classes with pure virtual member functions.

The main interface mi::neuraylib::INeuray can be retrieved by calling the global function mi_factory(). mi::neuraylib::INeuray allows access to the whole library. From this interface other interfaces of the library can be requested with the mi::neuraylib::INeuray::get_api_component() member function.

Reference counting

Interfaces implement reference counting for life-time control.

Whenever a function returns a pointer to mi::base::IInterface or a subclass thereof, the corresponding reference counter has already been increased by 1. That is, you can use the interface pointer without worrying whether the pointer is still valid. Whenever you do not need an interface any longer, you have to release it by calling its release() method. Omitting such calls leads to memory leaks.

In more detail, the rules for reference counting are as follows:

  • A method that returns an interface increments the reference count for the caller. The caller needs to release the interface when it is done. See Example 1 below.
  • When a caller passes an interface as method argument the caller has to guarantee that the interface is valid for the runtime of that method, and the callee can safely use the interface for that time without the need to change the reference count.
  • If the callee wants to reference an interface passed as an argument later (typically via a member variable) then it has to use reference counting to ensure that the interface remains valid. See Example 2 below.
  • The initial reference count after construction is 1 (such that one can implement methods returning an interface pointer by writing return new Foo(); ).
  • Interfaces passed as out arguments of methods are treated similar to return values. The callee decrements the reference count for the value passed in and increments it for the value passed back.

Example 1:

Assume there is an interface IFoo derived from mi::base::IInterface, and an API method that creates instances of IFoo, e.g.,

class IFactory : public mi::base::Interface_declare...>
{
public:
virtual IFoo* create_foo() = 0;
};

Let factory be an instance of IFactory. As described by the rules above the implementation of create_foo() increments the reference count for the caller. When you are done with your reference to the instance of IFoo, you need to decrement its reference count again via mi::base::IInterface::release().

IFoo* foo = factory->create_foo();
// use "foo"
foo->release();
// must no longer use "foo" here
Note
This example demonstrates the rules above for reference counting. We strongly recommend not to call retain() and release() manually. See the variant of this example in Handle class which is simpler, obtain simpler, less error-prone and exception-safe.

Example 2:

Assume you want to implement the following interface.

class IRegistry : public mi::base::Interface_declare...>
{
public:
virtual void register_foo( IFoo* foo) = 0;
};

Further assume that the implementation of IRegistry needs to reference the registered instance of IFoo after the method register_foo() returned. This can be done as follows. Checking foo and m_foo for NULL has been omitted for brevity.

class Registry_impl : public mi::base::Interface_implement<IRegistry>
{
public:
Registry_impl( IFoo* foo);
Registry_impl( const Registry_impl& other);
Registry_impl& operator=( const Registry_impl& rhs);
~Registry_impl();
void register_foo( IFoo* foo);
private:
IFoo* m_foo;
};
Registry_impl::Registry_impl( IFoo* foo)
{
m_foo = foo;
m_foo->retain();
}
Registry_impl::Registry_impl( const Registry_impl& other)
: mi::base::Interface_implement<IRegistry>( other)
{
m_foo = other.m_foo;
m_foo->retain();
}
Registry_impl& Registry_impl::operator=( const Registry_impl& rhs)
{
m_foo->release();
m_foo = rhs.m_foo;
m_foo->retain();
return *this;
}
Registry_impl::~Registry_impl()
{
m_foo->release();
}
void Registry_impl::register_foo( IFoo* foo)
{
m_foo->release();
m_foo = foo;
m_foo->retain();
}

As described by the rules above you need to increment and decrement the reference count of the IFoo instance if you keep a reference that exceeds the lifetime of the called method.

Note
This example demonstrates the rules for reference counting. We strongly recommend not to call retain() and release() manually. See the variant of this example in Handle class which is much simpler and where copy constructor, assignment operator, destructor, and NULL handling are for free.

Handle class

To simplify your life and to relieve you from keeping track of release() calls, we offer a simple handle class mi::base::Handle. This handle class maintains a pointer semantic while supporting reference counting for interface pointers. For example, the -> operator acts on the underlying interface pointer. The destructor calls release() on the interface pointer, copy constructor and assignment operator take care of retaining and releasing the interface pointer as necessary. Note that it is also possible to use other handle class implementations, e.g., std::tr1::shared_ptr<T> (or boost::shared_ptr<T>). See Example for Starting and Shutting Down the MDL API for details.

Example 1:

Assume there is an interface IFoo derived from mi::base::IInterface, and an API method that creates instances of IFoo, e.g.,

class IFactory : public mi::base::Interface_declare...>
{
public:
virtual IFoo* create_foo() = 0;
};

Let factory be an instance of IFactory. As described by the rules in Reference counting the implementation of create_foo() increments the reference count for the caller. Since the default constructor of mi::base::Handle leaves the reference count unchanged, you can simply use it to capture the return value. The destructor or the reset() method of mi::base::Handle decrement the reference count again via mi::base::IInterface::release().

mi::base::Handle<IFoo> foo( factory->create_foo());

Using mi::base::Handle (or similar helper classes) instead of manually calling retain() and release() is strongly recommended for exception-safe code.

Example 2:

Assume you want to implement the following interface.

class IRegistry : public mi::base::Interface_declare...>
{
public:
virtual void register_foo( IFoo* foo) = 0;
};

Further assume that the implementation of IRegistry needs to reference the registered instance of IFoo after the method register_foo() returned.

class Registry_impl : public mi::base::Interface_implement<IRegistry>
{
public:
Registry_impl( IFoo* foo);
void register_foo( IFoo* foo);
private:
};
Registry_impl::Registry_impl( IFoo* foo)
: m_foo( foo, mi::base::DUP_INTERFACE) { } // or: m_foo( make_handle_dup( foo))
void Registry_impl::register_foo( IFoo* foo)
{
m_foo = make_handle_dup( foo);
// or: m_foo = mi::base::Handle<IFoo>( foo, mi::base::DUP_INTERFACE);
}

In this case you cannot use the default constructor of mi::base::Handle since you need to increment the reference count as well. This is done by the constructor that takes mi::base::DUP_INTERFACE as second argument. Alternatively, you can use the inline function mi::base::make_handle_dup() which does that for you.

Note that the default implementation of the copy constructor, assignment operator, and destructor of mi::base::Handle just do the right thing, and therefore, there is no need to implement them for Registry_impl. Also NULL handling for foo and m_foo is for free.

Resources

In general, you should aim for minimal resource usage. This implies releasing interface pointers as soon as you no longer need them. When using the mi::base::Handle class, it can be beneficial to introduce additional { ... } blocks to enforce the destruction of the handle, and release of the corresponding interface pointer, at the end of the block. Alternatively, you can also call mi::base::Handle::reset() or assign 0 to the handle.

Strings

There is an interface mi::IString representing strings. However, some methods return constant strings as a pointer to const char for simplicity. These strings are managed by the MDL API and you must not deallocate the memory pointed to by such a pointer. These pointers are valid as long as the interface from which the pointer was obtained is valid. Exceptions from this rule are documented with the corresponding method.