如何使用反射获取未导出的类型?

huangapple go评论93阅读模式
英文:

How to get unexported types with reflect?

问题

假设有人使用client, _ := rpc.Dial('tcp', 'example.com:port')初始化了一个rpc客户端,并且只提供了rpc客户端实例。由于某种原因,您希望从客户端实例中获取地址example.com。然而,由于客户端类型高度抽象,您无法直接获取提供RemoteAddr()方法的net.Conn实例。在这种情况下,reflect包可能会有所帮助。

让我们从以下尝试开始:

func getRemoteAddr(client *rpc.Client) net.Addr {
  codec := reflect.Indirect(rpclient).FieldByName("codec")
  connV := codec.FieldByName("rwc")
  conn := connV.Interface().(net.Conn)
  return conn.RemoteAddr()
}

然而,第二行立即出现了一个错误:

panic: reflect: call of reflect.Value.FieldByName on interface Value

这是合理的,因为codec被声明为类型ClientCodec,它只是一个具有四个方法签名的接口。然而,从代码中我们知道,codec的详细类型可能是未导出的结构体gobClientCodec。不幸的是,Go编译器并不聪明到足以追踪到那么深,并找出实际类型。因此,是否有一种使用reflect包告诉编译器我对codec的类型有信心是未导出的gobClientCodec的方法?如果这不可能,我可能不得不计算地址偏移量并使用不安全指针,这是最后的选择。

附注:reflect方法的默认Type()方法不起作用,因为codec被声明为ClientCodec并且在NewClient方法中通过传递gobClientCodec结构体的地址进行初始化,如下所示:

func NewClient(conn io.ReadWriteCloser) *Client {
	encBuf := bufio.NewWriter(conn)
	client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
	return NewClientWithCodec(client)
}

此外,reflect.TypeOf(rpc.gobClientCodec)也不起作用,因为编译器拒绝使用未导出的类型。

英文:

Imagine someone has initialized a rpc client with client, _ := rpc.Dial('tcp', 'example.com:port') and provides you only the rpc client instance. For some reason, you'd like to get the address example.com from the client instance. However, as the client type is highly abstracted, you cannot directly get the net.Conn instance which serves a RemoteAddr() method. In this case, the reflect package might be helpful.

Lets start with the following attempt:

func getRemoteAddr(client *rpc.Client) net.Addr {
  codec := reflect.Indirect(rpclient).FieldByName("codec")
  connV := codec.FieldByName("rwc")
  conn := connV.Interface().(net.Conn)
  return conn.RemoteAddr()
}

However, an error jumped out immediately from the second line:

panic: reflect: call of reflect.Value.FieldByName on interface Value

This is reasonable as codec is declared as type ClientCodec which is just an interface with four methods' signatures. However, from the code, we know that the detailed type of codec might be the unexported struct gobClientCodec. Unfortunately, the Go compiler is not smart enough to trace down that deep and figure out the actual type. Therefore, is there a way with the reflect package to tell the compiler that I'm confident with the type of codec to be the unexported gobClientCodec? If this is not possible, I'll probably have to calculate the address offset and play with the unsafe pointers, which is the last choice.

P.S. The default Type() method from the reflect method won't work as codec is declared as ClientCodec and initialized by passing the address of a gobClientCodec struct in the NewClient method as follow:

func NewClient(conn io.ReadWriteCloser) *Client {
	encBuf := bufio.NewWriter(conn)
	client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
	return NewClientWithCodec(client)
}

Also, reflect.TypeOf(rpc.gobClientCodec) won't work as well as the compiler refuses unexported type usages.

答案1

得分: 2

使用.Elem()方法来获取接口中包含的值。

Elem方法返回接口v包含的值或指针v指向的值。如果v的类型不是Interface或Ptr,则会引发panic。如果v为nil,则返回零值。

对于你特定的用例,你还需要使用unsafe包来访问未导出的字段。在使用之前,请确保阅读unsafe的文档。

func getRemoteAddr(client *rpc.Client) net.Addr {
  rv := reflect.ValueOf(client)
  rv = rv.Elem()               // 解引用 *rpc.Client
  rv = rv.FieldByName("codec") // 获取 rpc.Client 的 "codec" 字段
  rv = rv.Elem()               // 获取 ClientCodec 包含的值
  rv = rv.Elem()               // 解引用 *gobClientCodec
  rv = rv.FieldByName("rwc")   // 获取 gobClientCodec 的 "rwc" 字段

  conn := *(*net.Conn)(unsafe.Pointer(rv.UnsafeAddr()))
  return conn.RemoteAddr()
}

https://go.dev/play/p/cHHdwQJ7DE7

英文:

Use .Elem() to get the value contained in the interface.

> Elem returns the value that the interface v contains or that the
> pointer v points to. It panics if v's Kind is not Interface or Ptr. It
> returns the zero Value if v is nil.

And for your specific use-case you'll also need to use the unsafe package to get access to unexported fields. Make sure to read unsafe's documentation before you use it.

func getRemoteAddr(client *rpc.Client) net.Addr {
  rv := reflect.ValueOf(client)
  rv = rv.Elem()               // deref *rpc.Client
  rv = rv.FieldByName("codec") // get "codec" field from rpc.Client
  rv = rv.Elem()               // get the value that ClientCodec contains
  rv = rv.Elem()               // deref *gobClientCodec
  rv = rv.FieldByName("rwc")   // get "rwc" field from gobClientCodec

  conn := *(*net.Conn)(unsafe.Pointer(rv.UnsafeAddr()))
  return conn.RemoteAddr()
}

https://go.dev/play/p/cHHdwQJ7DE7

huangapple
  • 本文由 发表于 2022年2月8日 20:22:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/71033840.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定