tags:

views:

553

answers:

2

Hi All,

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" after the read, though "buffer" seems to contain valid data.

A: 

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;
    }
}
Filip Kunc
+2  A: 

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.

Hans Passant
Thanks nobugz - your suggestions work perfectly.
Jeff Godfrey