DiCE API nvidia_logo_transpbg.gif Up
Library Design

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

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

Interfaces

The DiCE 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 global function mi_factory() returns the main interface mi::neuraylib::INeuray that allows access to the whole library. From this interface other interfaces of the library can be accessed 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;
};
Mixin class template for deriving new interface declarations.
Definition: interface_declare.h:43

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();
}
Mixin class template for deriving interface implementations.
Definition: interface_implement.h:41
Interface_implement<I> & operator=(const Interface_implement<I> &other)
Assignment operator.
Definition: interface_implement.h:66
Common namespace for APIs of NVIDIA Advanced Rendering Center GmbH.
Definition: dice.h:89

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 DiCE 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());
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113

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);
}
Handle<Interface> make_handle_dup(Interface *iptr)
Converts passed-in interface pointer to a handle, without taking interface over.
Definition: handle.h:439
const Dup_interface_helper * Dup_interface
Type for a symbolic constant to trigger a special constructor in the Handle class.
Definition: handle.h:32

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 DiCE 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.