通过CLI从c#向c++传递一个fstream(或等价)

本文关键字:一个 CLI fstream c++ 通过 | 更新日期: 2023-09-27 18:01:35

如何通过CLI将fstream或等效的c#传递到非托管的c++ DLL?

应用概要:

    c#应用程序从数据库读取二进制文件
  • 非托管c++ dll用于"解码"此文件并返回其中包含的信息
  • 我可以修改任何c#代码。CLI包装器是c++端唯一可以修改的部分。

我目前将二进制文件保存到磁盘,并将其路径传递给CLI包装器,在那里它作为fstream打开。这对于测试目的来说很好,但由于显而易见的原因,它不适用于生产。

我还研究了传递字节数组到DLL,但我无法找到一种方法将其转换为除GlobalAlloc之外的fstream,我不希望使用。

任何帮助或想法都将不胜感激。

谢谢。

通过CLI从c#向c++传递一个fstream(或等价)

您可以将托管二进制数组传递给c++/CLI DLL。固定阵列。然后可以将其转换为STL字符串对象。然后,您可以将STL字符串传递给继承自iostream的STL stringstream对象。把stringstream看作一个。net MemoryBuffer对象。将stringstream传递给非托管c++。这可能可以在<10行代码。缺点是数据将被复制到内存中,这是低效的。对于许多应用程序,我怀疑这将是一个问题。

或者,您可以编写自己的类,继承stream_buffer来包装。net流对象。(最好从这个继承,而不是像其他人建议的那样从iostream继承)。这将是最有效的方法,因为没有内存会被不必要地复制,但如果第一个方法足够快,我就不会麻烦这样做了。

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

您不能通过CLI传入Memorystream。你能做的最好的事情就是传递一个"指针"(IntPtr)到字节缓冲区。

请参阅如何使用p/Invoke将MemoryStream数据传递给非托管的c++ DLL ?查看更多详细信息

我能够得到一个基于这篇文章(PInvoke和IStream)工作的例子。基本上你需要用c#实现IStream接口。然后,您可以将自定义MemoryStream作为c++端的LPSTREAM传递。下面是一个代码示例,它接受流并获取大小(只是一个简单的示例来展示它是如何完成的):

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

您的c++/CLI层可以为c#端提供一个简单的接口,也许可以将字节数组对象传递到流库中。

基本上是句柄/体的习惯用法,c++/CLI层包装流并传递一个不透明的句柄给c#使用。

创建临时文件。让操作系统为您分配一个临时名称来解决多个应用程序(linux可以做到,我希望windows可以)。
临时文件对于业务应用程序中的多工具链是可以接受的,并且用于解决您的问题。如果文件不是太大,它们会留在缓存中,如果你关闭和删除它的速度足够快,它们甚至不会被写入磁盘。