How can I pass an fstream or equivalent from C# through CLI to an unmanaged C++ DLL?
如何将fstream或等效的c#通过CLI传递到非托管的c++ DLL?
Rough application outline:
粗糙的应用概述:
- C# application reads a binary file from a database
- 应用程序从数据库读取二进制文件。
- Unmanaged C++ dll is used to "decode" this file and return the information contained therein
- 非托管的c++ dll用于“解码”这个文件并返回其中包含的信息
- I can modify any of the C# code. The CLI wrapper is the only portion of the C++ side that I can modify.
- 我可以修改任何c#代码。CLI包装器是c++方面中惟一可以修改的部分。
I'm currently saving the binary file to disk and passing the path of it to the CLI wrapper where it is opened as an fstream. This is fine for test purposes, but won't work for production for obvious reasons.
我目前正在将二进制文件保存到磁盘,并将其路径传递给CLI包装器,在那里它作为fstream打开。对于测试目的来说,这是可以的,但是由于明显的原因不能用于生产。
I've also looked into passing a byte array to the DLL but I was not able to find a way to convert that to an fstream other than with GlobalAlloc, which I would prefer not to use.
我也考虑过将一个字节数组传递给DLL,但是我无法找到一种方法来将它转换为一个fstream而不是GlobalAlloc,我宁愿不使用它。
Any help or ideas would be appreciated.
如有任何帮助或想法,我们将不胜感激。
Thanks.
谢谢。
5 个解决方案
#1
2
You can pass a managed binary array to the C++/CLI DLL. Pin the array. This can then be converted to an STL string object. You can then pass the STL string into an STL stringstream object, which inherits from iostream. Think of stringstream as a .NET MemoryBuffer object. Pass stringstream to your unmanaged C++. That can probably be done in < 10 lines of code. The downside is the data will get copied in memory which is inefficient. For many applications I doubt it would be an issue.
您可以将托管二进制数组传递给c++ /CLI DLL。针数组。然后可以将其转换为STL字符串对象。然后可以将STL字符串传递给STL stringstream对象,该对象继承自iostream。将stringstream看作。net MemoryBuffer对象。将stringstream传递给非托管的c++。这可以在< 10行代码中完成。缺点是数据将被复制到内存中,这是低效的。对于许多应用程序,我怀疑这是一个问题。
Alternatively, you could write your own class inheriting from stream_buffer that wraps a .NET stream object. (Better to inherit from this instead of iostream, as others propose). That would be the most efficient way because no memory would be needlessly copied but I wouldn't bother doing it if the first method is fast enough.
或者,您可以编写自己的类继承自stream_buffer,它封装. net流对象。(与其像其他人建议的那样继承iostream,不如继承它。)这将是最有效的方法,因为没有内存被不必要地复制,但是如果第一个方法足够快的话,我就不会麻烦这么做了。
#2
2
If your C++ DLL accepts generic iostream objects (instead of just fstreams), create an iostream implementation that wraps System.IO streams and pass it to the DLL. Then the unmanaged side can work directly with the managed stream.
如果您的c++ DLL接受一般的iostream对象(而不是fstreams),那么创建一个iostream实现来包装系统。IO流并将其传递给DLL。然后,非托管端可以直接与托管流一起工作。
#3
1
Your C++/CLI layer can expose a simple interface for the C# side to use, perhaps to pass in byte array objects to stream into the stream library.
您的c++ /CLI层可以公开一个简单的接口,以便c#端使用,也许可以将字节数组对象传入流库中。
Basically the handle/body idiom where the C++/CLI layer wraps the stream and passes an opaque handle back to C# to use.
基本上,c++ /CLI层封装了流,并将一个不透明的句柄返回到c#使用。
#4
1
You wont be able to pass in a
Memorystream
throught CLI. The best you will be able to do is to pass a 'pointer' (IntPtr) to the byte buffer.
您不能通过CLI传入一个Memorystream。您将能够做的最好的事情是向字节缓冲区传递一个“指针”(IntPtr)。
See
How can I pass MemoryStream data to unmanaged C++ DLL using P/Invoke? for more details
看看如何使用P/Invoke将MemoryStream数据传递给非托管的c++ DLL ?为更多的细节
I was able to get an example working based on this post (PInvoke and IStream). Basically you need to implement the IStream interface in C#. Then you can pass the custom MemoryStream
as an LPSTREAM
on the C++ side. Here is a code example that takes the stream and gets the size (just a trivial example to show how its done):
我能够得到一个基于这篇文章的示例(PInvoke和IStream)。基本上,您需要在c#中实现IStream接口。然后,您可以将定制的MemoryStream作为一个LPSTREAM传递到c++端。下面是一个代码示例,它获取流并获取大小(只是一个简单的示例,展示如何完成它):
C++ LpWin32Dll.h
c++ LpWin32Dll.h
#ifndef LPWINDLL_H
#define LPWINDLL_H
extern "C" {
__declspec(dllexport) int SizeOfLpStream(LPSTREAM lpStream);
}
#endif
C++ LpWin32Dll.cpp
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 definition
段c# PInvoke定义
[DllImport("LpWin32Dll.dll", CallingConvention=CallingConvention.StdCall)]
public static extern int SizeOfLpStream(IStream iStream);
C# IStream implementation (Must implement the IStream interface). I've just created a wrapper class for the MemoryStream
class.
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# Use
c#使用
IMemoryStream ms = new IMemoryStream(new byte[] { 0x45, 0x23, 0x67, 0x34 });
int size = LpTest.SizeOfLpStream(ms);
#5
-1
Create a temporary file. let the operating system allocate a temporary name for you to solve multiple applications (linux can do that, I hope windows can).
Temporary files are acceptable for multi-tool chains in business applications and used to solve just your problem. If the files aren't too big they will stay in cache and if you close and delete it fast enough they won't be even written to disk.
创建一个临时文件。让操作系统为您分配一个临时名称来解决多个应用程序(linux可以做到这一点,我希望windows可以)。对于业务应用程序中的多工具链,临时文件是可以接受的,并且只用于解决您的问题。如果文件不太大,它们会留在缓存中,如果你关闭和删除的速度足够快,它们甚至不会写到磁盘上。
#1
2
You can pass a managed binary array to the C++/CLI DLL. Pin the array. This can then be converted to an STL string object. You can then pass the STL string into an STL stringstream object, which inherits from iostream. Think of stringstream as a .NET MemoryBuffer object. Pass stringstream to your unmanaged C++. That can probably be done in < 10 lines of code. The downside is the data will get copied in memory which is inefficient. For many applications I doubt it would be an issue.
您可以将托管二进制数组传递给c++ /CLI DLL。针数组。然后可以将其转换为STL字符串对象。然后可以将STL字符串传递给STL stringstream对象,该对象继承自iostream。将stringstream看作。net MemoryBuffer对象。将stringstream传递给非托管的c++。这可以在< 10行代码中完成。缺点是数据将被复制到内存中,这是低效的。对于许多应用程序,我怀疑这是一个问题。
Alternatively, you could write your own class inheriting from stream_buffer that wraps a .NET stream object. (Better to inherit from this instead of iostream, as others propose). That would be the most efficient way because no memory would be needlessly copied but I wouldn't bother doing it if the first method is fast enough.
或者,您可以编写自己的类继承自stream_buffer,它封装. net流对象。(与其像其他人建议的那样继承iostream,不如继承它。)这将是最有效的方法,因为没有内存被不必要地复制,但是如果第一个方法足够快的话,我就不会麻烦这么做了。
#2
2
If your C++ DLL accepts generic iostream objects (instead of just fstreams), create an iostream implementation that wraps System.IO streams and pass it to the DLL. Then the unmanaged side can work directly with the managed stream.
如果您的c++ DLL接受一般的iostream对象(而不是fstreams),那么创建一个iostream实现来包装系统。IO流并将其传递给DLL。然后,非托管端可以直接与托管流一起工作。
#3
1
Your C++/CLI layer can expose a simple interface for the C# side to use, perhaps to pass in byte array objects to stream into the stream library.
您的c++ /CLI层可以公开一个简单的接口,以便c#端使用,也许可以将字节数组对象传入流库中。
Basically the handle/body idiom where the C++/CLI layer wraps the stream and passes an opaque handle back to C# to use.
基本上,c++ /CLI层封装了流,并将一个不透明的句柄返回到c#使用。
#4
1
You wont be able to pass in a
Memorystream
throught CLI. The best you will be able to do is to pass a 'pointer' (IntPtr) to the byte buffer.
您不能通过CLI传入一个Memorystream。您将能够做的最好的事情是向字节缓冲区传递一个“指针”(IntPtr)。
See
How can I pass MemoryStream data to unmanaged C++ DLL using P/Invoke? for more details
看看如何使用P/Invoke将MemoryStream数据传递给非托管的c++ DLL ?为更多的细节
I was able to get an example working based on this post (PInvoke and IStream). Basically you need to implement the IStream interface in C#. Then you can pass the custom MemoryStream
as an LPSTREAM
on the C++ side. Here is a code example that takes the stream and gets the size (just a trivial example to show how its done):
我能够得到一个基于这篇文章的示例(PInvoke和IStream)。基本上,您需要在c#中实现IStream接口。然后,您可以将定制的MemoryStream作为一个LPSTREAM传递到c++端。下面是一个代码示例,它获取流并获取大小(只是一个简单的示例,展示如何完成它):
C++ LpWin32Dll.h
c++ LpWin32Dll.h
#ifndef LPWINDLL_H
#define LPWINDLL_H
extern "C" {
__declspec(dllexport) int SizeOfLpStream(LPSTREAM lpStream);
}
#endif
C++ LpWin32Dll.cpp
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 definition
段c# PInvoke定义
[DllImport("LpWin32Dll.dll", CallingConvention=CallingConvention.StdCall)]
public static extern int SizeOfLpStream(IStream iStream);
C# IStream implementation (Must implement the IStream interface). I've just created a wrapper class for the MemoryStream
class.
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# Use
c#使用
IMemoryStream ms = new IMemoryStream(new byte[] { 0x45, 0x23, 0x67, 0x34 });
int size = LpTest.SizeOfLpStream(ms);
#5
-1
Create a temporary file. let the operating system allocate a temporary name for you to solve multiple applications (linux can do that, I hope windows can).
Temporary files are acceptable for multi-tool chains in business applications and used to solve just your problem. If the files aren't too big they will stay in cache and if you close and delete it fast enough they won't be even written to disk.
创建一个临时文件。让操作系统为您分配一个临时名称来解决多个应用程序(linux可以做到这一点,我希望windows可以)。对于业务应用程序中的多工具链,临时文件是可以接受的,并且只用于解决您的问题。如果文件不太大,它们会留在缓存中,如果你关闭和删除的速度足够快,它们甚至不会写到磁盘上。