2

I am developing an COM library that uses the IStream interface to read and write data. My MIDL code looks like this:

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

Since IStream and it's base interface ISequentialStream are not defined inside a type library, they get defined in mine. So far so good. However, when I view my type library with OLEView, ISequentialStream only defines the members RemoteRead and RemoteWrite, while I expected Read and Write, since they are what I am actually calling. Even more strange is, that the the MSDN lists those two members (additionally to the original ones), but states they are not supported.

The question

So what are those members and how do I use them from a client (e.g. a managed application to create a managed Stream wrapper for IStream)?

The long story

I want to implement a wrapper on the client side, that forwards IStream calls to .NET streams, like System.IO.FileStream. This wrapper could inherit from IStream like so:

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

And then, I want to call my server with this wrapper:

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

The problem is, that Lib.Stream in the previous example only provides RemoteRead and RemoteWrite, so that server calls to stream->Read() would end up in no mans land. As far as I understood, there is System.Runtime.InteropServices.ComTypes.IStream for managed COM servers, but in my example I have a unmanaged COM server and a managed client that should provide IStream instances.

Carsten
  • 11,287
  • 7
  • 39
  • 62
  • It is a long story. Read() is optimized only for non-marshaled calls, RemoteRead() is the fallback for remoted calls. This is an implementation detail for the proxy, client code only ever uses Read(). What actually goes wrong? – Hans Passant Nov 06 '13 at 20:10
  • 1
    `IStream` is not automation-compatible; it is unwise to try and define it in a type library. If you need `IParser` to be automation-compatible, take `IUnknown*` as the first parameter and query it for `IStream` in the implementation. Alternatively, remove `Load` method from `IParser`, have the object implement `IPersistStreamInit` (together with its `Load` method), and add a property that returns `IParsable`. – Igor Tandetnik Nov 06 '13 at 20:19
  • @IgorTandetnik: Besides of design reasons, I cannot use `IPersistStreamInit` in this here - it also uses `IStream` as parameter, which leads to the same result as described above (`IStream` gets added to my type library). As far as I can tell, this is default behaviour of MIDL, isn't it?! – Carsten Nov 07 '13 at 08:01
  • @HansPassant: I want to implement a wrapper (that forwards the server's `Read`/`Write` calls to `System.IO.FileStream`, e.g.) on the client side, but I do not have `Read`/`Write` there. The `IStream` interface (the proxy, you mean?!) from my type library only defines `RemoteRead` and `RemoteWrite`. – Carsten Nov 07 '13 at 08:05
  • I've updated the question with more explanations of what I'm trying to achieve. – Carsten Nov 07 '13 at 08:20
  • possible duplicate of [Does a wrapper class for a COM interop IStream already exist?](http://stackoverflow.com/questions/2586159/does-a-wrapper-class-for-a-com-interop-istream-already-exist) – Hans Passant Nov 07 '13 at 08:22
  • @HansPassant: The question answers exactly the opposite of what I am trying to do. I want to write a wrapper `System.IO.Stream`, not `IStream`. Also my actual problem is different from the one in the question, as described above. – Carsten Nov 07 '13 at 08:52
  • You simply do not mention `IPersistStreamInit` in your IDL. Not every interface your object implements must or should be spelled out in the IDL - you only need those you want to be scriptable, and those you want universal marshaling for. – Igor Tandetnik Nov 07 '13 at 13:20
  • @IgorTandetnik: Doesn't this imply, that I cannot call `Load`/`Save` from client code? – Carsten Nov 07 '13 at 13:42
  • No it does not. You can query any interface pointer for any interface, whether or not that interface is described in any type library. If QueryInterface succeeds, you can then call methods on that interface. – Igor Tandetnik Nov 07 '13 at 13:58
  • @IgorTandetnik: Of course! Sorry for missing that. I was to much fixed on the interop view on the type library, so I completely forgot about `QueryInterface`. Thank you :) – Carsten Nov 07 '13 at 14:07
  • Small addition: the mechanism is described here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366748(v=vs.85).aspx : MIDL call_as attribute – peterchen Aug 12 '14 at 07:47

1 Answers1

3

Actually, there is no RemoteRead and RemoteWrite in ISequentialStream v-table layout. They exist only in ObjIdl.Idl, as an aid for RPC proxy/stub code generator. Have a look at ObjIdl.h from 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;

};

It's hard to guess why your type library ends up with RemoteRead/RemoteWrite names, instead of Read/Write. You may want to upload your IDL somewhere and post a link to it, if you need help with that.

However, as long as the v-table layout, the method signatures and the GUID of the interfaces from your typelib match those of ISequentialStream and IStream from ObjIdl.h, the method names do not matter.

Anyway, I would do as Igor suggested in his comment. Do not expose IStream at all in the type library. Use IUnknown in the IDL, and just cast it to System.Runtime.InteropServices.ComTypes.IStream inside the C# client's method implementation, when you actually do read/write, i.e.:

IDL:

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(...);
    // ...
} 

[UPDATE] I guess I see what's going on with the method names. Your situation is exactly like this:

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

Once more, I suggest to not drag non-automation compatible interfaces into the type library, and I'm not alone here with this advice. You actually drag a lot more unneeded stuff into your typlib, which projects to the C# side too. Stick with IUnknown and make your typelib neat. Or, at last, define your own binary/GUID compatible versions of them from scratch.

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Okay, well that sounds like a step into the right direction. However I would have liked to enjoy the comfort of having `IStream` in the interface definition, because it's easier for client development, getting things right. But from what I've read so far, I think I'll end up best "boxing" the streams. Thank you! :) Btw: The Guid of the interface is equal to the expected one, but I don't know if the method signatures are correct (from what I remember; can't check that atm). – Carsten Nov 07 '13 at 11:15
  • @Aschratt, it's generally not a good idea to expose [non-automation compatible interfaces](http://msdn.microsoft.com/en-us/library/cc237800.aspx) in a type library. Anyhow, COM is about binary compatibility, so as long as IID/v-table/signatures do match, you can name your methods any way you like :) – noseratio Nov 07 '13 at 11:22
  • I pasted my midl source [here](http://pastebin.com/Y2YZnyvV), just for interest. Maybe this helps explaining, why my type library ends up with those additional interface definitions. From what I can tell so far, I did not do anything special in there. I will accept this answer as soon as I could validate everything this afternoon ;) – Carsten Nov 07 '13 at 11:31
  • @Aschratt, check the update to my answer. Basically, this is as far as we need to take it. – noseratio Nov 07 '13 at 12:25
  • 1
    Thank you for your effort, I've accepted the answer. I will try defining my own interface with binary/guid compatibility. If it doesn't work, I know why and where to continue. That's what I expected and thus answered my question! :) – Carsten Nov 07 '13 at 13:40
  • Finally I also figured out, why those names get renamed. `ObjIdl.idl` imports a file called `ObjIdlBase.idl`. `IStream` get's defined there with `[call_as(Write)] RemoteWrite`. So those calls appear to get forwarded to `Write`. – Carsten Nov 07 '13 at 18:40
  • 1
    @Aschratt, I just came across this: http://support.microsoft.com/kb/307713, you may want to marshal your unmanaged stream as managed stream. Also, Adam Nathan has some sample code doing exactly that in [his book](http://books.google.com.au/books/about/NET_and_COM.html?id=x2OIPSyFLBcC&redir_esc=y). – noseratio Nov 08 '13 at 23:47