如何在运行时发现所有包类型?

时间:2015-08-21 03:48:47

标签: reflection go

据我所知(请参阅hereherereflect package中没有类型发现机制,它预计您已经拥有该类型的实例或者你想要检查的价值。

有没有其他方法来发现正在运行的go包中的所有导出类型(特别是结构)?

这是我希望的(但它不存在):

import "time"
import "fmt"

func main() {
    var types []reflect.Type
    types = reflect.DiscoverTypes(time)
    fmt.Println(types)
}

最终目标是能够发现满足特定条件的包的所有结构,然后能够实例化这些结构的新实例。

顺便说一句,识别类型的注册函数是我用例的有效方法。

无论你认为这是一个好主意,这就是我想要这种能力的原因(因为我知道你会问):

我编写了一个code generation utility来加载源文件并构建一个AST来扫描嵌入指定类型的类型。该实用程序的输出是基于发现的类型的一组go测试函数。我使用go generate调用此实用程序来创建测试函数,然后运行go test来执行生成的测试函数。每次测试更改(或添加新类型)时,我必须重新运行go generate,然后重新运行go test。这就是注册功能不是有效选项的原因。我想避免go generate步骤,但这需要我的实用程序成为由正在运行的包导入的库。对于嵌入了预期库类型的类型,库代码需要在init()期间以某种方式扫描正在运行的命名空间。

5 个答案:

答案 0 :(得分:21)

在Go 1.5 中,您可以使用新包typesimporter来检查二进制包和源包。例如:

package main

import (
    "fmt"
    "go/importer"
)

func main() {
    pkg, err := importer.Default().Import("time")
    if err != nil {
        fmt.Printf("error: %s\n", err.Error())
        return
    }
    for _, declName := range pkg.Scope().Names() {
        fmt.Println(declName)
    }
}

您可以使用包go/build来解压缩所有已安装的软件包。或者,您可以配置Lookup导入程序以检查环境之外的二进制文件。

1.5之前,唯一没有问题的方法是使用包ast来编译源代码。

答案 1 :(得分:10)

(见2019年更新的底部)

警告:未经测试和hacky。每当发布新版Go时都会中断。

通过对Go的运行时进行一些攻击,可以获得运行时所知道的所有类型。在您自己的包中包含一个小的汇编文件,其中包含:

TEXT yourpackage·typelinks(SB), NOSPLIT, $0-0
    JMP reflect·typelinks(SB)

yourpackage中,声明函数原型(没有正文):

func typelinks() []*typeDefDummy

除了类型定义:

type typeDefDummy struct {
    _      uintptr           // padding
    _      uint64            // padding
    _      [3]uintptr        // padding
    StrPtr *string           
}

然后只需调用typelinks,遍历切片并读取每个StrPtr的名称。寻找以yourpackage开头的人。请注意,如果在不同的路径中有两个名为yourpackage的包,则此方法无法明确地工作。

  

我可以以某种方式挂钩到反射包来实例化这些名称的新实例吗?

是的,假设d*typeDefDummy类型的值(注意星号,非常重要):

t := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&d)))

现在treflect.Type值,您可以使用它来实例化reflect.Value

编辑:我成功测试并执行了此代码并拥有uploaded it as a gist

根据需要调整包名称并包含路径。

更新2019

自从我最初发布这个答案以来,已经发生了很多变化。这是对2019年Go 1.11如何完成同样的简短描述。

$GOPATH/src/tl/tl.go

package tl

import (
    "unsafe"
)

func Typelinks() (sections []unsafe.Pointer, offset [][]int32) {
    return typelinks()
}

func typelinks() (sections []unsafe.Pointer, offset [][]int32)

func Add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
    return add(p, x, whySafe)
}

func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer

$GOPATH/src/tl/tl.s

TEXT tl·typelinks(SB), $0-0
    JMP reflect·typelinks(SB)

TEXT tl·add(SB), $0-0
    JMP reflect·add(SB)

main.go

package main

import (
    "fmt"
    "reflect"
    "tl"
    "unsafe"
)

func main() {
    sections, offsets := tl.Typelinks()
    for i, base := range sections {
        for _, offset := range offsets[i] {
            typeAddr := tl.Add(base, uintptr(offset), "")
            typ := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&typeAddr)))
            fmt.Println(typ)
        }
    }
}

快乐的黑客攻击!

答案 2 :(得分:2)

不幸的是,我不认为这是可能的。包裹不是可操作的"在Go中,您无法调用函数"在上面。您也无法在类型上调用函数,但您可以在类型的实例上调用reflect.TypeOf并获取reflect.Type,这是类型的运行时抽象。包裹没有这样的机制,没有reflect.Package

话虽如此,你可以file an issue关于缺少(和添加的实用性)reflect.PackageOf等等。

答案 3 :(得分:2)

感谢@thwd和@icio,请按照您的指示进行操作,直到今天它仍适用于1.13.6。

按照步骤tl.s将是:

def redrawGameWindow():

    # background images for right to left moving screen
    screen.blit(background, (backgroundX, 0))
    screen.blit(background, (backgroundX2, 0))

    # AFTER BACKGROUND !!!!
    man.draw(screen)

    pygame.display.update()

是的,没有软件包名称,也没有“添加”功能。

然后按照@icio的方式将“添加”功能更改为:

TEXT ·typelinks(SB), $0-0
    JMP reflect·typelinks(SB)

然后一切正常。 :)

答案 4 :(得分:0)

没有。

如果你想“了解”你的类型,你必须注册它们。