Within a C# program I would like to receive image data (probably in the form of IStream
), returned from a function imported from an unmanaged C++ DLL.
I have read several similar questions and msdn docs on this general topic but so far have been unable to figure out a complete working solution.
C++ function exported for consumption in managed C#:-
extern "C" __declspec(dllexport) IStream* GetImage(){
IWICBitmap *pBitmap = NULL;
//... Code emitted for clarity
//... Bitmap creation and Direct2D image manipulation
//...
IWICStream *piStream = NULL;
IStream *stream;
CreateStreamOnHGlobal(NULL, true, &stream);
HRESULT hr = piStream->InitializeFromIStream(stream);
//... pBmpEncoder is IWICBitmapEncoder, piBitmapFrame is IWICBitmapFrameEncode
//... pFactory is IWICImagingFactory
hr = pFactory->CreateEncoder(GUID_ContainerFormatTiff, NULL, &pBmpEncoder);
hr = pBmpEncoder->Initialize(piStream, WICBitmapEncoderNoCache);
//...
hr = pBmpEncoder->CreateNewFrame(&piBitmapFrame, &pPropertybag);
//..
piBitmapFrame->WriteSource(pBitmap, &rect);
//...
piBitmapFrame->Commit();
pBmpEncoder->Commit();
return stream;
}
I have emitted code for the sake of clarity. What is important is that an IWICBitmap
is encoded as a .tiff
to an IWICStream
. The IWICStream
was initialized from an IStream
. Then the function returns *IStream
.
C# Imported function declaration:-
using System.Runtime.InteropServices.ComTypes.IStream;
[DllImport(@"D:\mydlls\getimagedll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IStream GetImage();
C# code consuming GetImage()
function and attempting to read IStream
:-
public ActionResult DeliverImage()
{
IStream stream = GetImage();
unsafe
{
byte[] fileBytes = new byte[4000];
int bytesRead = 0;
int* ptr = &bytesRead;
stream.Read(fileBytes, fileBytes.Length, (IntPtr)ptr);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Image.Tiff, "image.tiff");
}
}
There are three things I need help with.
Currently the C++ function is returning *IStream
although the imported C# declaration has System.Runtime.InteropServices.ComTypes.IStream
as return type. Obviously an IStream
pointer and System.Runtime.InteropServices.ComTypes.IStream
do not match. I have tried returning an IStream object from the function but intellisense warns 'function returning abstract class is not allowed'. How can an IStream
be returned and correctly marshalled to be a managed C# System.Runtime.InteropServices.ComTypes.IStream
?
When reading the IStream
in C#, how can the length of image data in the stream be discerned so that the read buffer can be set to the correct length?
What is the correct way to release the IStream in C++ or C# or both so that there isn't a memory leak? Can the stream be released in C++ as soon as it is returned or does there need to be a second function exported to C# that is called after the C# has finished using the stream?
Thank you for your help!
Your current code to return IStream*
works fine. Here is a simple demonstration that shows that it works fine:
C++
extern "C" __declspec(dllexport) IStream* __stdcall GetStream()
{
IStream *stream;
CreateStreamOnHGlobal(NULL, true, &stream);
int i = 42;
stream->Write(&i, sizeof(int), NULL);
return stream;
}
C#
[DllImport(dllname)]
private static extern IStream GetStream();
....
IStream stream = GetStream();
IntPtr NewPosition = Marshal.AllocHGlobal(sizeof(long));
stream.Seek(0, STREAM_SEEK_END, NewPosition);
Console.WriteLine(Marshal.ReadInt64(NewPosition));
stream.Seek(0, STREAM_SEEK_SET, IntPtr.Zero);
byte[] bytes = new byte[4];
stream.Read(bytes, 4, IntPtr.Zero);
Console.WriteLine(BitConverter.ToInt32(bytes, 0));
How do you find the size of the stream? Well the code above shows how to do that using the Seek
method. You'd want to wrap that up. For instance:
static long GetStreamSize(IStream stream)
{
IntPtr ptr = Marshal.AllocCoTaskMem(sizeof(long));
try
{
stream.Seek(0, STREAM_SEEK_CUR, ptr);
long pos = Marshal.ReadInt64(ptr);
stream.Seek(0, STREAM_SEEK_END, ptr);
long size = Marshal.ReadInt64(ptr);
stream.Seek(pos, STREAM_SEEK_SET, IntPtr.Zero);
return size;
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
Or a bit more simple with unsafe code if you prefer:
unsafe static long GetStreamSize(IStream stream)
{
long pos;
stream.Seek(0, STREAM_SEEK_CUR, new IntPtr(&pos));
long size;
stream.Seek(0, STREAM_SEEK_END, new IntPtr(&size));
stream.Seek(pos, STREAM_SEEK_SET, IntPtr.Zero);
return size;
}
Finally, you ask how to release the IStream
interface in C#. You don't need to. The C# compiler takes care of the reference counting automatically. It inserts appropriate calls to AddRef
and Release
.