C# and IStream.Read
I'm trying to use System.Runtime.InteropServices.ComTypes.IStream from C#, but I'm having some trouble. According to MSDN, the C# definition looks like this:
void Read(
byte[] pv,
int cb,
IntPtr pcbRead
)
Basically, I can read data from the stream, but the above "pcbRead" value is always "0" (even though the byte array contains my data). Doing some reading, it seems as if the pcbRead argument is somewhat tricky to set up properly (though I'm fairly new to C#).
Anyway, my code basically looks like this:
myPtr = (IntPtr)0;
int buffSize = 8192;
byte[] buffer = new byte[buffSize];
while (true)
{
strm.Read(buffer, buffSize, myPtr);
fs.Write(buffer, 0, myPtr.ToInt32());
if (myPtr.ToInt32() < buffSize) break;
}
Again, the problem is that "myPtr" still contains "0" afte开发者_如何转开发r the read, though "buffer" seems to contain valid data.
You are supposed to pass a pointer for that argument. The IStream::Read() function will write the number of bytes that were actually read to the pointed-to location. This requires unsafe code in C#, for example:
unsafe static int Read(System.Runtime.InteropServices.ComTypes.IStream strm,
byte[] buffer) {
int bytesRead = 0;
int* ptr = &bytesRead;
strm.Read(buffer, buffer.Length, (IntPtr)ptr);
return bytesRead;
}
Doing it without the unsafe keyword is possible too:
private static IntPtr ReadBuffer;
static int Read(System.Runtime.InteropServices.ComTypes.IStream strm,
byte[] buffer) {
if (ReadBuffer == IntPtr.Zero) ReadBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)));
strm.Read(buffer, buffer.Length, ReadBuffer);
return Marshal.ReadInt32(ReadBuffer);
}
If you use this method only occasionally you ought to use Marshal.CoTaskMemFree() to release the memory.
Here is a solution that builds off of Hans' answer to give you a class you can just drop in your project. It provides an extension method for all IStream objects.
This will transfer the data to a .net memory stream, but you could change that to be a file if you prefer.
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace YourProject
{
public static class IStreamExtensions
{
private const int bufferSize = 8192;
public static MemoryStream ReadToMemoryStream(this IStream comStream)
{
var memoryStream = new MemoryStream();
var amtRead = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)));
Marshal.WriteInt32(amtRead, bufferSize);
var buffer = new byte[bufferSize];
while (Marshal.ReadInt32(amtRead) > 0)
{
comStream.Read(buffer, buffer.Length, amtRead);
memoryStream.Write(buffer, 0, Marshal.ReadInt32(amtRead));
}
memoryStream.Position = 0;
return memoryStream;
}
}
}
usage:
IStream istream = (IStream) someCOMclass.giveMeAStream();
MemoryStream netStream = istream.ReadToMemoryStream();
I am not experienced with IStream, but looking at your code I see some potential error.
Variable myPtr is set to zero at the beginning. IntPtr works like pointers in C++, so I think that this method expects that it write value into location which myPtr points.
Can you try to do this?
unsafe
{
int pcbRead = 0;
int buffSize = 8192;
byte[] buffer = new byte[buffSize];
while (true)
{
// taking address of pcbRead
strm.Read(buffer, buffSize, new IntPtr(&pcbRead));
fs.Write(buffer, 0, pcbRead);
if (pcbRead < buffSize) break;
}
}
精彩评论