C# Memory Handling
This sections aims to clarify the importance of handling memory objects correctly and with best practice. This is important when you work with very large in-memory images, that Phase One SDK’s provides.
As a C# developer you would normally rely on the Garbage Collector to do memory handling for you. The objects you create with the new
operator, will automatically be disposed, i.e. freed, when they are no longer used.
In special cases you need explicit control over the object disposal. This is relevant when an object represents a system resource, such as a file handle or a network socket. In these cases C# provides you with the dispose pattern and the using
statement.
Tip
To learn more about how to use objects that utilize the dispose pattern, see Microsoft documentation on the subject: Using objects that implement IDisposable
Let us look at a quick example of opening a FileStream
in C#:
public void SomeMethod()
{
var file = new System.IO.FileStream("somefile.txt", System.IO.FileMode.Append);
// write some data to file
// I am done here, but forget to close the file handle!
}
Obviously, we forget to close the file handle. The garbage collector will eventually dispose and close our handle, when it collects our file
object. But we cannot know when this happens.
Worse, even if we remember to call the close method at the end of our function, we are still at risk. Exceptions thrown from our code or from code we call, will cause an early exit from our function, bypassing any close
call, at the bottom!
To mitigate this, C# has the using
statement.
The using
statement
This statement ensures that an object exists only inside a single context. That is, as soon as the defining scope is exited, the object is disposed immediately.
Let us modify the example from above, to use the using
statement:
public void SomeMethod()
{
using (var file = new System.IO.FileStream("somefile.txt", System.IO.FileMode.Append))
{
// write some data to file
// throw exceptions from here
} // I am done here, but forget to close the file handle!
}
Now we are certain that our file handle is closed and freed, as soon as the using-scope is exited. No matter if our function runs to completion, or if an early exit is caused by an exception.
When you work with objects returned to you from Image- and CameraSDK, you must always ensure these are properly disposed. Best practice is to utilize the using
statement.
Disposable Objects used in SDKs
Phase One SDK’s internals are written in C, and our C# API interfaces to these C functions. This means that data passed to you, like LiveView image frames or in-memory bitmap images, might be allocated by the C runtime memory allocator. In C# terms we say that these objects reside in unmanaged memory and are not subject to the C# garbage collector.
We could copy the objects from unmanaged memory into managed memory, that the garbage collector controls. However, that would have a significant performance and RAM usage penalty. Therefore, Camera- and ImageSDK provides you with C# objects, that references the raw unmanaged memory.
Note
All image objects returned by CameraSDK reside in unmanaged memory. However, in ImageSDK, only image objects of type HugeBitmapImage
have data that reside in unmanaged memory.
Even if a data object does not use unmanaged memory, it is still good practice to use the dispose pattern, if the object implements IDisposable
. E.g. a bitmap image returned by ImageSDK can be very large, and would hog memory if not freed immediately after use.
The NativeMemoryRef Class
The image data referenced in both IIQImageFile
, LiveViewImage
and HugeBitmapImage
classes, reside in unmanaged memory. You access this image data using the Data
accessor, on both classes. The Data
accessor is of type NativeMemoryRef
, which is the wrapper we use in our SDKs to access the unmanaged memory.
NativeMemoryRef
is a subclass of System.IO.Stream
, and you must use the .NET stream API’s to access the referenced data. By utilizing the stream functionality, we do not need to copy data into managed memory, but are able to work with it directly in unmanaged memory.
All these classes (IIQImageFile
, LiveViewImage
, HugeBitmapImage
and NativeMemoryRef
) implements the dispose pattern, meaning you should use them with the using
statement. This will ensure their referenced memory are correctly dereferenced, and can be deallocated by the C based internals.
Avoid using the ToArray Method
In your application, you would probably like to write captured image to disk. When we receive an IIQImageFile
object, a quick and dirty way to write its content to disk is:
using (IIQImageFile iiqImage = camera.WaitForImage())
{
System.IO.File.WriteAllBytes(iiqImage.FileName, iiqImage.Data.ToArray())
}
This code correctly disposes the image object as soon as it is written to disk. However, the ToArray
function implicitly copies all the image data from unmanaged memory into managed. This means we have a single image residing in memory twice!
This is probably not what you want. Using the stream API’s you can write directly from unmanaged memory to disk. This example uses the CopyTo
method to write the image to a file, without any extraneous copying:
using (IIQImageFile iiqImage = cameraData.mCamera.WaitForImage())
{
using (var file = new FileStream(iiqImage.FileName, FileMode.Create))
{
iiqImage.Data.CopyTo(file);
}
}
If you intent to write LiveView frames to disk, the exact same approach can be used.
Tip
In ImageSDK you would probably want to write your converted image to disk, in some popular format like Tiff.
The built-in TiffWriter class handles writing the converted bitmap image into a file. It accounts for both scenarios, where data might be in managed or unmanaged memory. It does not do any unnecessary copying.