C# и IStream.Read

Я пытаюсь использовать System.Runtime.InteropServices.ComTypes.IStream из C#, но у меня возникают проблемы. Согласно MSDN, определение C# выглядит так:

void Read(
    byte[] pv,
    int cb,
    IntPtr pcbRead
)

По сути, я могу читать данные из потока, но указанное выше значение «pcbRead» всегда равно «0» (даже несмотря на то, что массив байтов содержит мои данные). Прочитав немного, кажется, что аргумент pcbRead несколько сложно правильно настроить (хотя я довольно новичок в C#).

Во всяком случае, мой код в основном выглядит так:

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

Опять же, проблема в том, что "myPtr" по-прежнему содержит "0" после чтения, хотя "buffer", кажется, содержит достоверные данные.


person Jeff Godfrey    schedule 24.12.2009    source источник


Ответы (3)


Вы должны передать указатель на этот аргумент. Функция IStream::Read() запишет количество байтов, которые были фактически прочитаны, в указанное место. Для этого требуется небезопасный код на C#, например:

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

Можно сделать это и без ключевого слова unsafe:

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

Если вы используете этот метод только изредка, вам следует использовать Marshal.CoTaskMemFree() для освобождения памяти.

person Hans Passant    schedule 24.12.2009

Вот решение, основанное на ответе Ганса, чтобы дать вам класс, который вы можете просто добавить в свой проект. Он предоставляет метод расширения для всех объектов IStream.

Это перенесет данные в поток памяти .net, но вы можете изменить его на файл, если хотите.

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

Применение:

IStream istream = (IStream) someCOMclass.giveMeAStream();
MemoryStream netStream = istream.ReadToMemoryStream();
person Bill Tarbell    schedule 25.05.2016

У меня нет опыта работы с IStream, но, глядя на ваш код, я вижу потенциальную ошибку.
Переменная myPtr вначале установлена ​​в ноль. IntPtr работает как указатели в C++, поэтому я думаю, что этот метод ожидает, что он запишет значение в место, на которое указывает myPtr.

Можете ли вы попробовать сделать это?

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;
    }
}
person Filip Kunc    schedule 24.12.2009