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.
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.
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:
return
new
Foo()
; ).Example 1:
Assume there is an interface IFoo
derived from mi::base::IInterface, and an API method that creates instances of IFoo
, e.g.,
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().
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.
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.
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.
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.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.,
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().
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.
Further assume that the implementation of IRegistry
needs to reference the registered instance of IFoo
after the method register_foo()
returned.
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.
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.
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.