【问题标题】:Passing an fstream (or equivalent) from C# to C++ through CLI通过 CLI 将 fstream(或等效项)从 C# 传递到 C++
【发布时间】:2023-03-19 10:27:01
【问题描述】:

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

粗略的应用大纲:

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

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

我还研究过将字节数组传递给 DLL,但我无法找到一种方法将其转换为 fstream,而不是使用 GlobalAlloc,我不想使用它。

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

谢谢。

【问题讨论】:

  • 您的非托管库可以接受 iostream 对象吗? (fstream 继承自 iostream。)
  • 应该可以的。我在搜索中是否遗漏了与 iostream 等效的 c# 内容?

标签: c# c++ command-line-interface


【解决方案1】:

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

或者,您可以编写自己的类,继承自包装 .NET 流对象的 stream_buffer。 (正如其他人建议的那样,最好从这里继承而不是 iostream)。这将是最有效的方法,因为不会不必要地复制内存,但如果第一种方法足够快,我不会费心去做。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      您将无法通过 CLI 传递 Memorystream。您能做的最好的事情是将“指针”(IntPtr)传递给字节缓冲区。

      详情请见How can I pass MemoryStream data to unmanaged C++ DLL using P/Invoke?

      我能够根据这篇文章 (PInvoke and IStream) 获得一个工作示例。 基本上你需要在 C# 中实现 IStream 接口。然后,您可以在 C++ 端将自定义 MemoryStream 作为 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);
      

      【讨论】:

        【解决方案4】:

        您的 C++/CLI 层可以公开一个简单的接口供 C# 端使用,或许可以传入字节数组对象以流式传输到流库中。

        基本上是 C++/CLI 层包装流并将不透明的句柄传回给 C# 使用的句柄/主体习惯用法。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          • 我的性能指标显示平均文件写入时间为 10-15 毫秒,这是在 SSD 上,这是我的测试机器所独有的。考虑到这个过程会发生数千万次,我不愿意承受这么大的性能损失。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多