3

Building a Monodroid application we've been using Protobuf-net for serialization. It works in debug mode when fast deply is turned on, however once we switch to release mode it fails with the following exception:

ex {System.InvalidOperationException: Cannot serialize property without a get accessor
at ProtoBuf.Serializers.PropertyDecorator.SanityCheck (System.Reflection.PropertyInfo property, IProtoSerializer tail, System.Boolean& writeValue, Boolean nonPublic) [0x00000] in <filename unknown>:0 
at ProtoBuf.Serializers.PropertyDecorator..ctor (System.Type forType, System.Reflection.PropertyInfo property, IProtoSerializer tail) [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.ValueMember.BuildSerializer () [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.ValueMember.get_Serializer () [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.MetaType.BuildSerializer () [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.MetaType.get_Serializer () [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.RuntimeTypeModel.Serialize (Int32 key, System.Object value, ProtoBuf.ProtoWriter dest) [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.TypeModel.SerializeCore (ProtoBuf.ProtoWriter writer, System.Object value) [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.TypeModel.Serialize (System.IO.Stream dest, System.Object value, ProtoBuf.SerializationContext context) [0x00000] in <filename unknown>:0 
at ProtoBuf.Meta.TypeModel.Serialize (System.IO.Stream dest, System.Object value) [0x00000] in <filename unknown>:0 
at ProtoBuf.Serializer.Serialize[UltraliteJCommandParameters] (System.IO.Stream destination, Company.Product.ProtocolBuffers.Android.UltraliteJCommandParameters instance) [0x00000] in <filename unknown>:0 
at Company.Product.UltraliteJ.DataCommand.SerializeParameters () [0x00261] in C:\DevSVN\Product\FS\Trunk\FSAndroidSolution\Company.Product.UltraliteJ\DataCommand.cs:109 
at Company.Product.UltraliteJ.DataCommand.LoadValue () [0x00001] in C:\DevSVN\Product\FS\Trunk\FSAndroidSolution\Company.Product.UltraliteJ\DataCommand.cs:44 
at Company.Product.Ultralite.Controlers.UltraliteDataServer.RunUpgradeScripts (System.String version, Boolean syncSuccessful) [0x0007e] in C:\DevSVN\Product\FS\Trunk\Company.Product.Ultralite.Controlers\UltraliteDataServer.cs:375 } System.InvalidOperationException

We are using the generated code from a .proto file, and it works in debug mode. So I know that that type we are trying to serialize has the get accessor, and that it does work.

As far as I can tell fast deploy changes a single setting called "embed assemblies in APK", when set to false (fast deploy mode) everything works.

Has anyone run into this issue, any workarounds?

EDIT:

Our .proto file:

package Company.ProductName.ProtocolBuffers.Android;

option java_package = "com.company.productname.protocolbuffers.android";
option java_outer_classname = "AndroidParametersProto";

message UltraliteJCommandParameters {
    required string command_text = 1;
    repeated UltraliteJCommandParameter parameters = 2;
}

message UltraliteJCommandParameter {
    required string field_name = 1;
    required string data_type = 2;
    required bool is_null = 3;
    optional double double_value = 4;
    optional float float_value = 5;
    optional int32 int32_value = 6;
    optional int64 int64_value = 7;
    optional uint32 uint32_value = 8;
    optional uint64 uint64_value = 9;
    optional sint32 sint32_value = 10;
    optional sint64 sint64_value = 11;
    optional fixed32 fixed32_value = 12;
    optional fixed64 fixed64_value = 13;
    optional sfixed32 sfixed32_value = 14;
    optional sfixed64 sfixed64_value = 15;
    optional bool bool_value = 16;
    optional string string_value = 17;
        optional bytes bytes_value = 18;
}

Code where the failure was occuring:

private byte[] SerializeParameters()
{
    byte[] paramBytes;

    Company.Product.ProtocolBuffers.Android.UltraliteJCommandParameters parameters = new Product.ProtocolBuffers.Android.UltraliteJCommandParameters();
    Company.Product.ProtocolBuffers.Android.UltraliteJCommandParameter parameter;

    parameters.command_text = CommandText;

    foreach (DataParameter param in Parameters)
    {

        parameter = new Product.ProtocolBuffers.Android.UltraliteJCommandParameter();
        parameter.field_name = param.Name;

        if (param.Value == null || param.Value == (object)DBNull.Value)
        {
            parameter.is_null = true;
        }
        else
        {

            parameter.is_null = false;
            parameter.data_type = param.Value.GetType().ToString().Replace("System.", string.Empty);

            switch (param.Value.GetType().ToString())
            {
                case "System.String":
                    parameter.string_value = (string)param.Value;
                    break;
                case "System.Int32":
                    parameter.sint32_value = (int)param.Value;
                    break;
                case "System.Int64":
                    parameter.sint64_value = (long)param.Value;
                    break;
                case "System.Boolean":
                    parameter.bool_value = (bool)param.Value;
                    break;
                case "System.DateTime":
                    parameter.double_value = (((DateTime)param.Value).ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0).ToUniversalTime()).TotalMilliseconds;
                    break;
                case "System.Single":
                    parameter.float_value = (float)param.Value;
                    break;
                case "System.Double":
                    parameter.double_value = (double)param.Value;
                    break;
            }
        }

        parameters.parameters.Add(parameter);

    }

    using (MemoryStream stream = new MemoryStream())
    {
        Serializer.Serialize<Company.Product.ProtocolBuffers.Android.UltraliteJCommandParameters>(stream, parameters);
        paramBytes = stream.ToArray();
    }

    return paramBytes;
}
dmck
  • 7,801
  • 7
  • 43
  • 79
  • Hi; I'm the author of protobuf-net, and as it happens I have Mono for Android available to me. I have tried a repro here on a Kindle Fire using a very basic example, and it works perfectly in both debug (with and without fast deployment) and release (fast deployment not available). I'm happy to help investigate, but: do you have anything I could use to repro? – Marc Gravell Feb 28 '12 at 21:41
  • The other thing I wonder... has some fancy bit of the Mono compiler optimized away an accessor that isn't "obviously" used. I could add the type/member name to the error message if it would be useful? – Marc Gravell Feb 28 '12 at 21:47
  • @MarcGravell Thanks for the response. Right now the issue is in a large project, I'm going to try and reproduce the issue on a smaller solution with the same structure so that I can send you something concrete. – dmck Feb 28 '12 at 21:50
  • @MarcGravell That additional information could be helpful, we are using r480. I've also posted the .proto and some code. This is the first time we have used protocol buffers for .NET or Java (using proto for both). So I'm not sure if prehaps our message structure isn't optimal or is causing the error. – dmck Feb 28 '12 at 22:03
  • if you are time-bound, just for info you might want to also try the "per-generate the serialization assembly as a static-compiled dll" - sound interesting? (I'm heading to bed now, but will see replies tomorrow) – Marc Gravell Feb 28 '12 at 22:06
  • @MarcGravell We were able to fix it by disabling linking for the project, our app is alot bigger now. However protobuf is required for database access, so it's a trade off we can accept for now. It looks like more of a monodroid issue than anything with protobuf-net. Thanks for the help! – dmck Mar 01 '12 at 14:13
  • did you try the `[Preserve]` thing? – Marc Gravell Mar 01 '12 at 14:18
  • @MarcGravell I just got it working with [Preserve], at first I just tried adding it to the class but that didn't work. It works now that I've added it to every property in the class. Maybe the [Preserve] attribute is something that could be added to the generated code as an option in the "Custom Tool Namespace" options? – dmck Mar 01 '12 at 14:42
  • I can certainly look at it as an option, yes; however, it would have to be optional since many frameworks don't have it. – Marc Gravell Mar 01 '12 at 14:49
  • to repeat what I said before, though; if you use the "pre-generate a static assembly" route, then the members *will* be clearly used, and the linker should have no reason to remove them? – Marc Gravell Mar 01 '12 at 14:50

1 Answers1

2

The (managed) linker used in Mono for Android will removed unused code to create smaller applications. This removal process (and selection) is based on static analysis and it won't find things that are only used by reflection.

Try setting your options to disable the linker, i.e. set it to "Don't link", and try again. If it works then this is likely the issue.

If that's the case you can either tell the linker to ignore the assembly, where the property resides (the command-line option should be named --linkskip) or you can add a [Preserve] attribute on your property (or type) to tell the linker not to remove the code (even if it looks unused).

poupou
  • 43,413
  • 6
  • 77
  • 174
  • Ah, nice - so my comment yesterday `The other thing I wonder... has some fancy bit of the Mono compiler optimized away an accessor that isn't "obviously" used.` wasn't far off. – Marc Gravell Mar 01 '12 at 09:17
  • @MarcGravell pretty close :) just the next (optional) step down the build pipeline. – poupou Mar 01 '12 at 12:46
  • @poupou Got it working with the [Preserve] attribute, however I had to add it to every property. Thanks for the suggestion! – dmck Mar 01 '12 at 14:43
  • `[Preserve (AllMembers = true)]` (from memory) should to it on the type itself. – poupou Mar 01 '12 at 14:51