Protobuf-net与Java UUID和C#Guid互操作

时间:2014-10-17 02:50:27

标签: java c# guid uuid protobuf-net

所以protobuf-net将这个原型内容用于.NET指南:

message Guid {
  optional fixed64 lo = 1; // the first 8 bytes of the guid
  optional fixed64 hi = 2; // the second 8 bytes of the guid
}

...当我将该proto编译成Java类并创建这个Java UUID实例时:

UUID uid = UUID.fromString("2ac9f438-f555-40b0-8880-02226b81285c");

...然后我使用uid.getMostSignificantBits()或uid.getLeastSignificantBits()作为Guid.setHi()(或Guid.setLo())的参数并不重要。

无论我选择哪种组合,当C#反序列化包含Java生成的guid的类型,并且我测试guid的值时,我得到的似乎是字节排序问题:

expected:<2ac9f438-f555-40b0-8880-02226b81285c>
 but was:<f55540b0-f438-2ac9-5c28-816b22028088>

...或:

expected:<2ac9f438-f555-40b0-8880-02226b81285c>
 but was:<6b81285c-0222-8880-b040-55f538f4c92a>

这个问题有一个简单的解决方法吗?

应该指出我是protobuf的新手,所以对这里的可能性有点朦胧。

修改

为了它的价值,在将结果提供给Guid.setLo / Hi之前,我还尝试了一个或两个uid.getLeast / MostSignificantBits()的结果的Long.reverseBytes(),并交换这些命令,但是唉......

编辑,第二个: 难怪两个长片上的简单字节顺序交换不起作用(摘自here):

  

开始的四字节组和接下来的两个双字节的顺序   组被颠倒,而最后两个字节组的顺序和   关闭的六字节组是相同的。

有关一种可能性,请参阅此问题的答案。如果两种语言都在应用程序代码中使用其本机二进制guid / uuid类型,那么不确定是否还有其他方法。任何其他建议(除了将guid作为字符串发送)?

1 个答案:

答案 0 :(得分:2)

好的,所以解决这个问题的方法是首先定义包含Guid的C#类,使Guid类型不参与序列化/反序列化,而是使用字节数组。这可能看起来像这样:

[ProtoContract]
public class Entity
{
    private bool _idInitialized = false;
    private Guid _id;

    public Guid id
    {
        get
        {
            if ( !_idInitialized )
            {
                _id = new Guid( idBytes );
                _idInitialized = true;
            }

            return _id;
        }
    }

    [ProtoMember( 1, IsRequired = true )]
    public byte[] idBytes;

    [ProtoMember( 2, IsRequired = true )]
    public String name;

    // For application code, sending side
    public Entity( Guid theId, String theName )
    {
        if ( String.IsNullOrEmpty( theName ) )
        {
            throw new ArgumentNullException( "theName" );
        }

        idBytes = theId.ToByteArray();
        _id = theId;
        _idInitialized = true;
        name = theName;
    }

    // For protobuf-net, receiving side
    public Entity() { }
}

然后使用GetProto生成该类型的proto文件:

Serializer.GetProto<Entity>()

产生这个:

message Entity {
   required bytes idBytes = 1;
   required string name = 2;
}

使用protoc将其编译为Java类。我整理了一个包装类,它在内部使用生成的protobuf类。这个包装类使用下面的转换方法来代表包装类重新排序进出的字节。 ByteBuffer和Apache Commons的ArrayUtils.reverse()完成了所有工作。

如果需要与另一种语言互操作,可以使用类似的方法。通过该语言的一些等效实用程序,每种语言都符合.NET字节排序方案。

import org.apache.commons.lang3.ArrayUtils;

public class Utilities {

    public static UUID getUuidFromDotNetGuidBytes(byte[] guidBytes) {
        ByteBuffer bb = ByteBuffer.wrap(guidBytes);

        byte[] first4 = new byte[4];
        bb.get(first4);
        ArrayUtils.reverse( first4 );

        byte[] second2 = new byte[2];
        bb.get(second2);
        ArrayUtils.reverse( second2 );

        byte[] third2 = new byte[2];
        bb.get(third2);
        ArrayUtils.reverse( third2 );

        long lsb = bb.getLong();

        bb = ByteBuffer.wrap(new byte[8]);
        bb.put( first4 );
        bb.put( second2 );
        bb.put( third2 );

        bb.rewind();
        long msb = bb.getLong();

        return new UUID(msb, lsb);
    }

    public static byte[] getDotNetGuidBytes(UUID theUuid) {

        ByteBuffer first8 = ByteBuffer.allocate(8);
        first8.putLong(theUuid.getMostSignificantBits());
        first8.rewind();

        byte[] first4 = new byte[4];
        first8.get(first4);
        ArrayUtils.reverse( first4 );

        byte[] second2 = new byte[2];
        first8.get(second2);
        ArrayUtils.reverse( second2 );

        byte[] third2 = new byte[2];
        first8.get(third2);
        ArrayUtils.reverse( third2 );

        ByteBuffer converted16 = ByteBuffer.allocate(16);
        converted16.put(first4);
        converted16.put(second2);
        converted16.put(third2);

        ByteBuffer last8 = ByteBuffer.allocate(8);
        last8.putLong(theUuid.getLeastSignificantBits());        
        last8.rewind();

        converted16.put(last8);

        return converted16.array();
    }

}