C # и IStream.Read - PullRequest
       20

C # и IStream.Read

4 голосов
/ 24 декабря 2009

Я пытаюсь использовать 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» после чтения, хотя «буфер», кажется, содержит действительные данные.

Ответы [ 3 ]

7 голосов
/ 24 декабря 2009

Вы должны передать указатель на этот аргумент. Функция 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 () для освобождения памяти.

1 голос
/ 25 мая 2016

Вот решение, основанное на ответе Ганса, чтобы дать вам класс, который вы можете просто добавить в свой проект. Он предоставляет метод расширения для всех объектов 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();
0 голосов
/ 24 декабря 2009

У меня нет опыта работы с 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;
    }
}
...