You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将descriptorpb.DescriptorProto转为protoreflect.MessageDescriptor?空指针报错

问题描述

尝试通过以下Go代码创建*dynamicpb.Message:

import (
    "github.com/stretchr/testify/assert"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/reflect/protoreflect"
    "google.golang.org/protobuf/types/descriptorpb"
    "google.golang.org/protobuf/types/dynamicpb"
    "testing"
)

func getDescriptorProto(fieldName string) protoreflect.MessageDescriptor {
    descProto := descriptorpb.DescriptorProto{
        Field: []*descriptorpb.FieldDescriptorProto{
            {
                Name: proto.String(fieldName),
                Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
            },
        },
    }
    return descProto.ProtoReflect().Descriptor()
}

func getDynamicMessage(descriptor protoreflect.MessageDescriptor, value int32) *dynamicpb.Message {
    dynamicMessage := dynamicpb.NewMessage(descriptor)
    dynamicMessage.Set(dynamicMessage.Descriptor().Fields().ByName("id"), protoreflect.ValueOf(value))
    return dynamicMessage
}

var (
    idDescriptorProto = getDescriptorProto("id")
)

var (
    validIdMessage = getDynamicMessage(idDescriptorProto, int32(2))
)

运行时触发invalid memory address or nil pointer dereference错误,调试发现message info为nil,请问问题出在哪里?

问题原因与解决方案

核心问题

你错误地将descriptorpb.DescriptorProto自身的消息描述符当作自定义消息的描述符来使用:

  • descProto.ProtoReflect().Descriptor()返回的是**descriptorpb.DescriptorProto这个Protobuf元数据类型本身的描述符**,而不是你通过descProto定义的自定义消息的描述符。
  • 这种错误的描述符无法用于创建dynamicpb.Message,后续调用Set时自然会触发空指针异常。

正确实现方式

要创建自定义消息的MessageDescriptor,必须将DescriptorProto包裹在FileDescriptorProto中,通过protodesc.NewFile构建合法的文件描述符,再从中提取消息描述符。修正后的代码如下:

import (
    "github.com/stretchr/testify/assert"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/reflect/protoreflect"
    "google.golang.org/protobuf/types/descriptorpb"
    "google.golang.org/protobuf/types/dynamicpb"
    "google.golang.org/protobuf/types/protodesc"
    "testing"
)

func getDescriptorProto(fieldName string) (protoreflect.MessageDescriptor, error) {
    // 构建文件描述符,包裹自定义消息的DescriptorProto
    fileDescProto := &descriptorpb.FileDescriptorProto{
        Name: proto.String("test.proto"),
        MessageType: []*descriptorpb.DescriptorProto{
            {
                Name: proto.String("TestMessage"),
                Field: []*descriptorpb.FieldDescriptorProto{
                    {
                        Name: proto.String(fieldName),
                        Number: proto.Int32(1), // 必须指定字段编号
                        Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
                    },
                },
            },
        },
    }

    // 构建文件描述符对象
    fileDesc, err := protodesc.NewFile(fileDescProto, nil)
    if err != nil {
        return nil, err
    }

    // 从文件描述符中获取自定义消息的描述符
    return fileDesc.Messages().ByName("TestMessage"), nil
}

func getDynamicMessage(descriptor protoreflect.MessageDescriptor, value int32) *dynamicpb.Message {
    dynamicMessage := dynamicpb.NewMessage(descriptor)
    dynamicMessage.Set(dynamicMessage.Descriptor().Fields().ByName("id"), protoreflect.ValueOf(value))
    return dynamicMessage
}

func TestDynamicMessage(t *testing.T) {
    idDescriptorProto, err := getDescriptorProto("id")
    assert.NoError(t, err)

    validIdMessage := getDynamicMessage(idDescriptorProto, int32(2))
    assert.NotNil(t, validIdMessage)

    // 验证字段值是否正确设置
    field := validIdMessage.Descriptor().Fields().ByName("id")
    assert.Equal(t, int32(2), validIdMessage.Get(field).Int())
}

关键修正点

  1. 添加文件描述符包裹:Protobuf的消息描述符必须属于某个文件,因此需要构建FileDescriptorProto来承载自定义消息的DescriptorProto。
  2. 指定字段编号:字段必须有合法的编号(如示例中的Number: proto.Int32(1)),否则无法通过描述符校验。
  3. 调整初始化时机:将全局变量初始化移到测试函数中,避免因错误的描述符导致程序启动时崩溃,同时方便处理错误。

内容的提问来源于stack exchange,提问作者Eagleye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:05:57