IPRO Memory Handling
IPRO is practically a COM-based object server that has two creatable objects:
ImageInfo does not create any further objects, but the Engine object is the parent of all the other IPRO objects (see IPRO object hierarchy). Every interface issued by Tungsten must be released in the client application. An object exists until there is a reference to it, i.e. (since all interfaces issued by Tungsten originate from the IUnknown) the IUnknown.Release method does not return with 0. It must also be considered that every object issued by Tungsten calls AddRef on its parent object and calls Release on it in its final Release calling.
Acquiring one leaf element of the object tree (a concrete IPRO object) keeps alive all parent objects upward. For instance, if the user gets a Page object, it keeps alive its parent Pages, Document, Documents, Engine objects even if the client does not hold explicit reference to these objects. A serious consequence of this is that if the user leaks the Pages object, they also leak the tree part above it. As a result, IPRO practically leaks all or nothing considering the COM objects. In regard to memory leak, it is not losing the COM object that causes significant memory loss, but losing the quite often horrible amount of system resource allocated by the object. So if the AddRef and Release calls are not balanced, then the user will run out of memory and handles in a matter of moments.
In C++, it is recommended to use the so called smart pointers for avoiding interface leak. In .NET such a phenomenon does not exist, but there is GarbageCollection meaning that the .NET automatically calls the Release functions whenever it senses shortage of memory. The real problem is that the .NET virtually never senses memory shortage because a COM object is small, and the .NET disregards native memory allocated by the object. Hence the resulting situation of the client's .NET Framework being all right, but the machine running out of memory. For this reason it is recommended for .NET programmers to keep the Engine object at most in a global variable and to keep every other object local. If a major task is accomplished, then the launch of GarbageCollection should be forced outside the scope of local variables:
GC.Collect();
GC.WaitForPendingFinalizers();
//KB827417
GC.Collect();
GC.WaitForPendingFinalizers();
The collector must be called twice, since the .NET maintains an indirection in the case of handling COM objects (see KB). Only the Engine object is alive after successfully running the collector, and the system resources are released.
There is another choice as well, namely calling the Marshal.ReleaseComObject method but since it means the immediate release of the wrapped COM object, its improper usage can be the source of loads of errors in a larger project.
IPRO is able to return with a "No more memory error message", if seemingly there is large amount of memory at hand. The reason is that there is a setting tree belonging to the Engine object and also there is a private setting tree belonging to every document.
For technical reasons, some further setting trees are allocated in the system. Workflows are executed on the documents with each workflow step creating a temporal setting tree for itself. These stay alive until the next running of the workflow or the destroying of the document. However, the number of setting trees is limited in 100 on KernelAPI level, so the creation of the 101st setting tree returns the E_OUTOFMEMORY error.
It is easy to check whether the user is only cyclically calling the IDocuments.Add method that keeps on creating a new document and does not release these documents; the user will receive an error message after 80-90 documents. Due to this setting limit, the interface leak is especially dangerous: if an interface gets stuck, its parent Document object also gets stuck and this way the setting tree belonging to it gets lost and the client gets closer to the 100 limit.
It is important to mention the opportunist collections as well. If anything is added to a collection, it remains there until removed explicitly. This is not true for the collections of the Engine, Documents, ImageFiles and MemoryBitmaps. The elements of these collections consume a lot of native resource, hence if the user does not hold explicit reference to an element of a collection, it will be immediately and implicitly removed by the collection. A valid scenario might be when every outer reference to a Document object is terminated, the Document closes automatically and disappears from the Documents collection as well. Therefore it is essential not to eliminate the work item until it is finished.