简体   繁体   中英

Returning IWICStream / IStream from unmanaged C++ DLL to managed C# and reading it

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.

  1. 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 ?

  2. 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?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM