views:

674

answers:

1

In the ProtoBuf-Net implementation, what does the ProtoInclude attribute mean, and what does it do?

An example would be appreciated.

I saw it in this post and I'm not sure what it does. The example was:

[Serializable,
ProtoContract,
ProtoInclude(50, typeof(BeginRequest))]
abstract internal class BaseMessage
{
  [ProtoMember(1)]
  abstract public UInt16 messageType { get; }
}

[Serializable,
ProtoContract]
internal class BeginRequest : BaseMessage
{
    [ProtoMember(1)]
    public override UInt16 messageType
    {
        get { return 1; }
    }
}

Also, is there a way to generate such inheritance using the protogen tool?

+2  A: 

Sorry, I didn't mean to miss this one - alas, I don't see everything.

Given the specifics in the question, I'm going to assume that you are at least passingly familiar with .proto; correct me if I am wrong.

[ProtoInclude] works a lot like [XmlInclude] for XmlSerializer - or [KnownType] for DataContractSerializer - it allows it to recognise subclasses of a type during (de)serialization. The only additional thing is that it needs a tag (number) to identify each sub-type (that must be unique, and not clash with any of the fields from the parent type).

Re protogen: nope; the underlying spec (by google) makes no provision for inheritance at all, so protogen (via .proto) has no mechanism to express this. protobuf-net provides inheritance support as an extension, but does it in a way that still leaves the messages wire-compatible with the other implementations. At a push, maybe I could add protogen support via the new extension properties in the google spec, but I haven't done this yet.

So; to look at the example; that expresses an inheritance relationship between BaseMessage and BeginRequest; regardless of whether you do:

Serialize<BaseMessage>(...)
Serialize<BeginRequest>(...)
  • either way, it will start at the base (BaseMessage) and work upwards; which isn't exactly true - it writes the data starting with BeginRequest (so that it knows we have a BeginRequest as early as possible during deserialization). The important thing is that the fields from any parent contract types is included, and the serializer looks at the actual object passed in - not just the type you say it is.

Likewise, during deserilaization, regardless of whether you use:

Deserialize<BaseMessage>(...)
Deserialize<BeginRequest>(...)

you will get the type you actually serialized (presumably a BeginRequest).

Under the bonnet, for compatibility purposes (with the wide protocol buffers specification), this is similar to writing something like (forgive any errors, my .proto is rusty):

message BaseMessage {
    optional BeginRequest beginRequest = 50;
    optional uint32 messageType = 1;   
}
message BeginRequest {        
}

(the override probably shouldn't specify [ProtoMember], btw.

Normally, it would write fields in ascending tag order, but to make for efficient deserialization the engine cheekily chooses to write the subclass data first (which is explicitly allowed by the spec) - i.e. it writes something like (you'll have to imagine the binary...):

[tag 50, string][length of sub-message][body of sub-message][tag 1, int][value]

(in this case, the body of the sub-message is empty)

Does that cover it?

Marc Gravell
Yes, I think it pretty much wraps it up. Thanks for the clarification. So from what I gather, one would pretty much have to know in advance all the inheriting messages (or at least their tags) if one wanted to provide a "BaseMessage", am I right?.. I'm interested in this because I'm writing an RPC engine based on ProtocolBuffers and I was looking for a way to allow users of the RPC engine to provide their own protocol buffers messages, on top of the base RPC messages that I'm using..
Miky Dinescu
At the moment, yes; but I am working on a version that allows runtime specification, and I have an idea to provide unknown object support - but it won'y be portable between platforms. Re RPC - I already have a lot work done in the RPC direction - it isn't complete, but if you want to help? ;-p To avoid code-gen and runtime type creation, I'm using interfaces and Expression - http://marcgravell.blogspot.com/2009/03/explaining-expression.html (although a code-gen or runtime type creation approach is also valid). There is a working http RPC client/server, for example.
Marc Gravell
Sounds really cool. I would love to be able to help and maybe there are some concepts we could use from what I have. I'll definitely have to check out your work and if you're interested I could share my ideas/implementation..
Miky Dinescu
I'm pretty good at the reflection / expression / etc side of things, but writing an RPC stack from scratch is new to me, so if you have experience in that area? In particular, I've shied away from the raw TCP side of things due to the inherent complexity of ensuring everything is released etc.
Marc Gravell
Apparently I completely missed out on your last comment and thought you lost interest. I apologize! I sent an email out to you.
Miky Dinescu