dmck
dmck

Reputation: 7861

Protobuf-Net fails on Monodroid with InvalidOperationException in release mode

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

Upvotes: 3

Views: 826

Answers (1)

poupou
poupou

Reputation: 43553

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).

Upvotes: 2

Related Questions