Что такое RemoteRead и RemoteWrite члены ISequentialStream?

Я разрабатываю библиотеку COM, которая использует IStream интерфейс для чтения и записи данных. Мой код MIDL выглядит так:

interface IParser : IUnknown
{
    HRESULT Load([in] IStream* stream, [out, retval] IParsable** pVal);
};

Поскольку IStream и его базовый интерфейс ISequentialStream не определены внутри библиотеки типов, они определены в моей. Все идет нормально. Однако, когда я просматриваю свою библиотеку типов с помощью OLEView, ISequentialStream определяет только элементы RemoteRead и RemoteWrite, в то время как я ожидал Read и Write, поскольку именно их я вызываю. Еще более странно то, что MSDN перечисляет эти два члена (в дополнение к исходным), но заявляет, что они не поддерживаются.

Вопрос

Итак, что это за элементы и как их использовать из клиента (например, управляемое приложение для создания управляемой оболочки Stream для IStream)?

Долгая история

Я хочу реализовать оболочку на стороне клиента, которая перенаправляет вызовы IStream в потоки .NET, например System.IO.FileStream. Эта оболочка может наследоваться от IStream следующим образом:

public class Stream : Lib.IStream
{
    public System.IO.Stream BaseStream { get; private set; }

    public Stream(System.IO.Stream stream)
    {
        this.BaseStream = stream;
    }

    // All IStream members in here...
    public void Read(byte[] buffer, int bufferSize, IntPtr bytesReadPtr)
    {
         // further implementation...
         this.BaseStream.Read();
    }
}

И затем я хочу вызвать свой сервер с помощью этой оболочки:

var wrapper = new Stream(baseStream);
var parsable = parser.Load(wrapper);

Проблема в том, что Lib.Stream в предыдущем примере предоставляет только RemoteRead и RemoteWrite, так что вызовы сервера к stream->Read() окажутся в ничейной зоне. Насколько я понял, для управляемых COM-серверов существует System.Runtime.InteropServices.ComTypes.IStream, но в моем примере у меня есть неуправляемый COM-сервер и управляемый клиент, который должен предоставлять IStream экземпляров.


person Carsten    schedule 06.11.2013    source источник
comment
Это длинная история. Read() оптимизирован только для немаршалированных вызовов, RemoteRead() является запасным вариантом для удаленных вызовов. Это деталь реализации прокси, клиентский код всегда использует только Read(). Что на самом деле идет не так?   -  person Hans Passant    schedule 07.11.2013
comment
IStream не совместим с автоматизацией; неразумно пытаться определить его в библиотеке типов. Если вам нужно, чтобы IParser был совместим с автоматизацией, возьмите IUnknown* в качестве первого параметра и запросите его для IStream в реализации. Либо удалите метод Load из IParser, реализуйте объект IPersistStreamInit (вместе с его методом Load) и добавьте свойство, возвращающее IParsable.   -  person Igor Tandetnik    schedule 07.11.2013
comment
@IgorTandetnik: Помимо соображений дизайна, здесь я не могу использовать IPersistStreamInit - он также использует IStream в качестве параметра, что приводит к тому же результату, что описан выше (IStream добавляется в мою библиотеку типов). Насколько я могу судить, это поведение MIDL по умолчанию, не так ли?!   -  person Carsten    schedule 07.11.2013
comment
@HansPassant: я хочу реализовать оболочку (которая перенаправляет вызовы сервера Read/Write, например, на System.IO.FileStream) на стороне клиента, но у меня там нет Read/Write. Интерфейс IStream (вы имеете в виду прокси?!) из моей библиотеки типов определяет только RemoteRead и RemoteWrite.   -  person Carsten    schedule 07.11.2013
comment
Я обновил вопрос, добавив больше объяснений того, чего я пытаюсь достичь.   -  person Carsten    schedule 07.11.2013
comment
возможный дубликат Является ли класс-оболочка для COM Interop IStream уже существует?   -  person Hans Passant    schedule 07.11.2013
comment
@HansPassant: ответ на вопрос прямо противоположен тому, что я пытаюсь сделать. Я хочу написать обертку System.IO.Stream, а не IStream. Также моя реальная проблема отличается от той, что указана в вопросе, как описано выше.   -  person Carsten    schedule 07.11.2013
comment
Вы просто не упоминаете IPersistStreamInit в своем IDL. Не каждый интерфейс, реализуемый вашим объектом, должен или должен быть описан в IDL — вам нужны только те, для которых вы хотите использовать скрипты, и те, для которых вы хотите универсальное маршалирование.   -  person Igor Tandetnik    schedule 07.11.2013
comment
@IgorTandetnik: Разве это не означает, что я не могу вызывать Load/Save из клиентского кода?   -  person Carsten    schedule 07.11.2013
comment
Нет. Вы можете запросить любой указатель интерфейса для любого интерфейса, независимо от того, описан ли этот интерфейс в какой-либо библиотеке типов. Если QueryInterface завершается успешно, вы можете вызывать методы этого интерфейса.   -  person Igor Tandetnik    schedule 07.11.2013
comment
@IgorTandetnik: Конечно! Извините, что пропустил это. Я был слишком занят интероп-представлением в библиотеке типов, поэтому совершенно забыл о QueryInterface. Спасибо :)   -  person Carsten    schedule 07.11.2013
comment
Небольшое дополнение: механизм описан здесь: msdn.microsoft.com/en-us/library/windows/desktop/ : атрибут MIDL call_as   -  person peterchen    schedule 12.08.2014
comment
@peterchen: Спасибо, что поделились!   -  person Carsten    schedule 12.08.2014


Ответы (1)


На самом деле в макете ISequentialStream v-таблицы нет RemoteRead и RemoteWrite. Они существуют только в ObjIdl.Idl в качестве вспомогательного средства для генератора кода прокси/заглушки RPC. Взгляните на ObjIdl.h из SDK:

MIDL_INTERFACE("0c733a30-2a1c-11ce-ade5-00aa0044773d")
ISequentialStream : public IUnknown
{
public:
    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Read( 
        /* [annotation] */ 
        __out_bcount_part(cb, *pcbRead)  void *pv,
        /* [in] */ ULONG cb,
        /* [annotation] */ 
        __out_opt  ULONG *pcbRead) = 0;

    virtual /* [local] */ HRESULT STDMETHODCALLTYPE Write( 
        /* [annotation] */ 
        __in_bcount(cb)  const void *pv,
        /* [in] */ ULONG cb,
        /* [annotation] */ 
        __out_opt  ULONG *pcbWritten) = 0;

};

Трудно догадаться, почему ваша библиотека типов заканчивается именами RemoteRead/RemoteWrite вместо Read/Write. Вы можете загрузить свой IDL куда-нибудь и опубликовать ссылку на него, если вам нужна помощь в этом.

Однако если макет v-таблицы, сигнатуры методов и GUID интерфейсов из вашей библиотеки типов совпадают с ISequentialStream и IStream из ObjIdl.h, имена методов не имеют значения.

В любом случае, я бы сделал так, как предложил Игорь в своем комментарии. Вообще не выставляйте IStream в библиотеке типов. Используйте IUnknown в IDL и просто приведите его к System.Runtime.InteropServices.ComTypes.IStream внутри реализации метода клиента С#, когда вы фактически выполняете чтение/запись, т.е.:

ИДЛ:

interface IParser : IUnknown
{
    HRESULT Load([in] IUnknown* stream, [out, retval] IParsable** pVal);
};

C#:

IParsable Load(object stream)
{
    // ...
    var comStream = (System.Runtime.InteropServices.ComTypes.IStream)stream;
    comStream.Read(...);
    // ...
} 

[ОБНОВЛЕНИЕ] Кажется, я вижу, что происходит с именами методов. Ваша ситуация точно такая:

https://groups.google.com/forum/#!topic/microsoft.public.vc.atl/e-qj0xwoVzg/discussion

Еще раз предлагаю не перетаскивать несовместимые с автоматизацией интерфейсы в библиотека типов, и я не одинок здесь с этим советом. На самом деле вы перетаскиваете гораздо больше ненужных вещей в свою типибиб, который также проецируется на сторону C#. Придерживайтесь IUnknown и сделайте свою библиотеку типов аккуратной. Или, наконец, определите с нуля свои собственные версии, совместимые с двоичным кодом/GUID.

person noseratio    schedule 07.11.2013
comment
Ладно, это звучит как шаг в правильном направлении. Тем не менее, мне бы хотелось насладиться удобством наличия IStream в определении интерфейса, потому что это проще для разработки клиента, чтобы все было правильно. Но из того, что я прочитал до сих пор, я думаю, что в конечном итоге я лучше всего буду боксировать потоки. Спасибо! :) Кстати: Guid интерфейса соответствует ожидаемому, но я не знаю, верны ли сигнатуры методов (насколько я помню, не могу проверить этот банкомат). - person Carsten; 07.11.2013
comment
@Aschratt, как правило, не рекомендуется раскрывать несовместимые с автоматизацией интерфейсы в библиотеке типов. В любом случае, COM — это бинарная совместимость, поэтому, если IID/v-table/signatures совпадают, вы можете называть свои методы как угодно :) - person noseratio; 07.11.2013
comment
Я вставил свой средний источник здесь, просто для интереса. Возможно, это помогает объяснить, почему моя библиотека типов заканчивается этими дополнительными определениями интерфейса. Из того, что я могу сказать на данный момент, я ничего особенного там не делал. Я приму этот ответ, как только смогу проверить все сегодня днем;) - person Carsten; 07.11.2013
comment
@Aschratt, проверьте обновление моего ответа. В принципе, это то, что нам нужно. - person noseratio; 07.11.2013
comment
Спасибо за ваши усилия, я принял ответ. Я попытаюсь определить свой собственный интерфейс с совместимостью двоичных файлов / guid. Если не получится, я знаю, почему и куда продолжать. Это то, что я ожидал и таким образом ответил на мой вопрос! :) - person Carsten; 07.11.2013
comment
Наконец я также понял, почему эти имена переименовываются. ObjIdl.idl импортирует файл с именем ObjIdlBase.idl. IStream определяется там с [call_as(Write)] RemoteWrite. Таким образом, кажется, что эти звонки перенаправляются на Write. - person Carsten; 07.11.2013
comment
@Aschratt, я только что наткнулся на это: support.microsoft.com/kb/307713, вы может захотеть маршалировать ваш неуправляемый поток как управляемый поток. Кроме того, у Адама Натана есть пример кода, который делает именно это в его книга. - person noseratio; 09.11.2013