英文:
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()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论