通过CLI从C#传递一个fstream(或等效项)到C++

8
我该如何将一个fstream或等效的对象从C#通过CLI传递给未托管的C++ DLL?
应用程序概述:
- C#应用程序从数据库中读取二进制文件 - 未托管的C++ DLL用于“解码”此文件并返回其中包含的信息 - 我可以修改任何C#代码。 CLI包装器是我可以修改的C++端的唯一部分。
我目前正在将二进制文件保存到磁盘,并将其路径传递给CLI包装器,然后作为fstream打开。 这对测试目的来说很好,但由于明显的原因,在生产环境中不适用。
我还尝试了将字节数组传递给DLL,但我找不到将其转换为fstream的方法,除了使用GlobalAlloc,但我不想使用它。
欢迎提供任何帮助或想法。
谢谢。

你的非托管库能够接受iostream对象吗?(fstream继承自iostream。) - James Johnston
它应该能够。在我的搜索中是否有我错过的C#等效于iostream的东西? - AaronN
5个回答

2

你无法通过CLI传递Memorystream。你能做的最好的事情就是传递一个指向字节缓冲区的“指针”(IntPtr)。

更多详细信息请参见如何使用P/Invoke将MemoryStream数据传递给非托管C++ DLL?

我能够根据这个帖子 (PInvoke和IStream) 得到一个示例。基本上,您需要在C#中实现IStream接口。然后,您可以将自定义的MemoryStream作为LPSTREAM传递到C++端。以下是一个代码示例,它获取流并获取大小(只是一个简单的示例,以显示如何完成):

C++ LpWin32Dll.h

#ifndef LPWINDLL_H 
#define LPWINDLL_H

extern "C" {
 __declspec(dllexport) int SizeOfLpStream(LPSTREAM lpStream);
}

#endif

C++ LpWin32Dll.cpp

#include "stdafx.h"
#include <ocidl.h>
#include "LpWin32Dll.h"

// Provides DllMain automatically
[module(dll, name = "LpWin32Dll")];

 __declspec(dllexport) int SizeOfLpStream(LPSTREAM lpStream)
{
   STATSTG stat_info;
   lpStream->Stat(&stat_info, STATFLAG_NONAME);
   return stat_info.cbSize.LowPart;
}

C# PInvoke定义

[DllImport("LpWin32Dll.dll", CallingConvention=CallingConvention.StdCall)]
public static extern int SizeOfLpStream(IStream iStream);

C# IStream实现(必须实现IStream接口)。我刚刚为MemoryStream类创建了一个包装类。

[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class IMemoryStream : MemoryStream, IStream {
  public IMemoryStream() : base() { }

  public IMemoryStream(byte[] data) : base(data) { }

  #region IStream Members

  public void Clone(out IStream ppstm) { ppstm = null; }

  public void Commit(int grfCommitFlags) { }

  public void CopyTo(
     IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) { }

  public void LockRegion(long libOffset, long cb, int dwLockType) { }

  public void Read(byte[] pv, int cb, IntPtr pcbRead)
  {
     long bytes_read = base.Read(pv, 0, cb);
     if (pcbRead != IntPtr.Zero) 
        Marshal.WriteInt64(pcbRead, bytes_read);
  }

  public void Revert() { }

  public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
  {
     long pos = base.Seek(dlibMove, (SeekOrigin)dwOrigin);
     if (plibNewPosition != IntPtr.Zero)
        Marshal.WriteInt64(plibNewPosition, pos);
  }

  public void SetSize(long libNewSize) { }

  public void Stat(
     out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, 
     int grfStatFlag)
  {
     pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();
     pstatstg.cbSize = base.Length;
  }

  public void UnlockRegion(long libOffset, long cb, int dwLockType) { }

  public void Write(byte[] pv, int cb, IntPtr pcbWritten)
  {
     base.Write(pv, 0, cb);
     if (pcbWritten != IntPtr.Zero) 
        Marshal.WriteInt64(pcbWritten, (long)cb);
  }

  #endregion
}

C# 使用

IMemoryStream ms = new IMemoryStream(new byte[] { 0x45, 0x23, 0x67, 0x34 });
int size = LpTest.SizeOfLpStream(ms);

2

如果你的C++ DLL接收通用的iostream对象(而不仅仅是fstreams),创建一个包装System.IO流的iostream实现并将其传递给DLL。然后非托管端可以直接使用托管流。


2
您可以将托管二进制数组传递给C++/CLI DLL。将该数组固定在内存中,然后将其转换为STL字符串对象。接下来,您可以将STL字符串传递到STL stringstream对象中,它继承自iostream。将stringstream传递给您的非托管C++代码,这可能只需要不到10行代码就能完成。缺点是数据会在内存中被复制,这样效率不高。但对于许多应用程序而言,我认为这不是问题。
或者,您可以编写自己的类,该类继承自stream_buffer并包装.NET流对象。(最好继承自stream_buffer,而不是iostream,正如其他人所建议的那样)。这将是最有效的方法,因为不会不必要地复制任何内存,但如果第一种方法足够快,则不必费心去做这件事。

1

你的C++/CLI层可以为C#端提供一个简单的接口,用于传递字节数组对象到流库中进行流式处理。

基本上是使用句柄/体模式,其中C++/CLI层包装了流并将不透明句柄传递回C#端使用。


-1
创建一个临时文件。让操作系统为您分配一个临时名称,以解决多个应用程序(Linux可以这样做,希望Windows也可以)。 临时文件可用于商业应用程序中的多工具链,并用于解决您的问题。 如果文件不太大,它们将保留在缓存中,如果您关闭并快速删除它们,则甚至不会写入磁盘。

我的性能指标显示,平均文件写入时间为10-15毫秒,这是在我的测试机器上使用SSD独有的。考虑到这个过程将会发生数千万次,我不愿意承受如此大的性能损失。 - AaronN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接