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

3
所以protobuf-net使用这个proto内容来表示.NET的GUID:
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不是很熟悉,所以在这方面可能有些模糊。

编辑:

值得一提的是,在将uid.getLeast/MostSignificantBits()中的一个或两个结果反转字节顺序之后再将结果提供给Guid.setLo/Hi,并交换这些顺序,我也尝试过,但可惜没有成功...

第二次编辑: 难怪仅仅对两个长整型进行简单的字节序交换不起作用(摘自这里):

开始四字节组和下一个两个双字节组的顺序被颠倒了,而最后两个双字节组和结束六字节组的顺序是相同的。

请参考我在此问题上发布的答案。如果两种语言都要在应用程序代码中使用本机二进制guid/uuid类型,我不确定是否还有其他方法。除了将guid作为字符串发送,还有其他建议吗?


2
我太晚才发现,Guid使用了我只能形容为“疯狂字节序”的方式。 - Marc Gravell
@MarcGravell - 就我而言,到目前为止,我更喜欢使用protobuf-net工具链,而不是针对Java的相应工具。往返转换简单、优雅且与应用程序集成无缝,这在我的经验中Java protobuf工具尚未做到。我想跨语言的互操作性要求在某个地方必然会出现问题。但总的来说,向protobuf-net致敬! - Hoobajoob
1个回答

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

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接