将IWICStream / IStream从非托管C++DLL返回到托管C#并读取它

本文关键字:读取 返回 C++DLL IStream IWICStream | 更新日期: 2023-09-27 17:56:54

在C#程序中,我希望接收图像数据(可能以IStream的形式),从从非托管C++DLL导入的函数返回。

我已经阅读了几个关于这个一般主题的类似问题和msdn文档,但到目前为止还无法找到一个完整的工作解决方案。

导出C++函数以在托管 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;
}

为了清楚起见,我发出了代码。重要的是,IWICBitmap被编码为IWICStream.tiffIWICStream是从IStream初始化的。然后函数返回*IStream

C# 导入的函数声明:-

using System.Runtime.InteropServices.ComTypes.IStream;
[DllImport(@"D:'mydlls'getimagedll.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IStream GetImage();

C# 代码消耗GetImage()函数并尝试读取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");
        }
    }

有三件事我需要帮助。

  1. 目前,C++ 函数返回*IStream尽管导入的 C# 声明已System.Runtime.InteropServices.ComTypes.IStream为返回类型。显然,IStream指针和System.Runtime.InteropServices.ComTypes.IStream不匹配。我尝试从函数返回 IStream 对象,但 intellisense 警告"不允许函数返回抽象类"。如何返回IStream并正确封送为托管 C# System.Runtime.InteropServices.ComTypes.IStream

  2. 在 C# 中读取IStream时,如何识别流中图像数据的长度,以便将读取缓冲区设置为正确的长度?

  3. C++ 或 C# 或两者中释放 IStream 的正确方法是什么,以免出现内存泄漏?流是否可以在返回后立即以 C++ 形式发布,或者是否需要导出到 C# 的第二个函数,该函数在 C# 使用完流后调用?

感谢您的帮助!

将IWICStream / IStream从非托管C++DLL返回到托管C#并读取它

您当前返回IStream*的代码工作正常。这是一个简单的演示,表明它工作正常:

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));

你如何找到流的大小?上面的代码显示了如何使用 Seek 方法执行此操作。你会想把它包起来。例如:

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);
    }
}

或者,如果您愿意,可以使用不安全的代码更简单一些:

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;
}

最后,您询问如何在 C# 中发布 IStream 接口。你不需要。C# 编译器会自动处理引用计数。它插入对AddRefRelease的适当调用。