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

标签 c# c++ pinvoke dllexport

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

我已经阅读了几个关于这个一般主题的类似问题和 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 被编码为 .tiffIWICStreamIWICStream 是从 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# 声明将 System.Runtime.InteropServices.ComTypes.IStream 作为返回类型,但 C++ 函数目前正在返回 *IStream。显然 IStream 指针和 System.Runtime.InteropServices.ComTypes.IStream 不匹配。我尝试从函数返回一个 IStream 对象,但智能感知警告“不允许函数返回抽象类”。如何返回 IStream 并将其正确编码为托管 C# System.Runtime.InteropServices.ComTypes.IStream

  2. 在C#中读取IStream时,如何判断流中图像数据的长度,从而将读取缓冲区设置为正确的长度?

  3. 在 C++ 或 C# 或两者中释放 IStream 以防止内存泄漏的正确方法是什么?流返回后是否可以在 C++ 中立即释放,或者是否需要导出到 C# 的第二个函数在 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接口(interface)。你不需要。 C# 编译器自动处理引用计数。它插入对 AddRefRelease 的适当调用。

关于c# - 将 IWICStream/IStream 从非托管 C++ DLL 返回到托管 C# 并读取它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22898086/

相关文章:

c# - DbContext 对象看不到它的属性 - DbSets

c++ - 将 unsigned char 转换为 unsigned short (big endian)

c# - 从 C# 而不是 C++ 调用时,非托管库函数失败

c# - 如果结构中的字符串长于或短于所使用的p/Invoked签名,会发生什么?

c# - PInvoke、指针和数组复制

c# - 这是测试 ActionResult 的正确方法吗

c# - 使用相同字母的排列

c# - 将 for 循环与 if 和 else 结合使用

c++ - 在 c 中的二维数组的末尾添加一个额外的数据列

c++ - qt #include <vector> 错误