first commit

This commit is contained in:
2025-08-22 17:42:23 -04:00
commit a6c09a5890
120 changed files with 11443 additions and 0 deletions

44
internal/cli/cli.go Normal file
View File

@ -0,0 +1,44 @@
package cli
import (
"flag"
"github.com/glerchundi/subcommands"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/saltosystems/winrt-go/internal/codegen"
)
const methodFilterUsage = `The filter to use when generating the methods. This option can be set several times,
the given filters will be applied in order, and the first that matches will determine the result. The generator
will allow any method by default. The filter uses the overloaded method name to discriminate between overloaded
methods.
You can use the '!' character to negate a filter. For example, to generate all methods except the 'Add' method:
-method-filter !Add
You can also use the '*' character to match any method, so if you want to generate only the 'Add' method, you can do:
-method-filter Add -method-filter !*`
// NewGenerateCommand returns a new subcommand for generating code.
func NewGenerateCommand(logger log.Logger) *subcommands.Command {
cfg := codegen.NewConfig()
fs := flag.NewFlagSet("winrt-go-gen", flag.ExitOnError)
_ = fs.String("config", "", "config file (optional)")
fs.BoolVar(&cfg.ValidateOnly, "validate", cfg.ValidateOnly, "validate the existing code instead of generating it")
fs.StringVar(&cfg.Class, "class", cfg.Class, "The class to generate. This should include the namespace and the class name, e.g. 'System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken'.")
fs.Func("method-filter", methodFilterUsage, func(m string) error {
cfg.AddMethodFilter(m)
return nil
})
fs.BoolVar(&cfg.Debug, "debug", cfg.Debug, "Enables the debug logging.")
return subcommands.NewCommand(fs.Name(), fs, func() error {
if cfg.Debug {
logger = level.NewFilter(logger, level.AllowDebug())
} else {
logger = level.NewFilter(logger, level.AllowInfo())
}
return codegen.Generate(cfg, logger)
})
}

1260
internal/codegen/codegen.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
package codegen
import (
"fmt"
)
// Config is the configuration for the code generation.
type Config struct {
Debug bool
Class string
ValidateOnly bool
methodFilters []string
}
// NewConfig returns a new Config with default values.
func NewConfig() *Config {
return &Config{}
}
// AddMethodFilter adds a method to the list of methodFilters to generate.
func (cfg *Config) AddMethodFilter(methodFilter string) {
cfg.methodFilters = append(cfg.methodFilters, methodFilter)
}
// MethodFilter creates and returns a new method filter for the current config.
func (cfg *Config) MethodFilter() *MethodFilter {
return NewMethodFilter(cfg.methodFilters)
}
// Validate validates the Config and returns an error if there's any problem.
func (cfg *Config) Validate() error {
if cfg == nil {
return fmt.Errorf("config is nil")
}
if cfg.Class == "" {
return fmt.Errorf("generated classes may not be empty")
}
return nil
}

View File

@ -0,0 +1,28 @@
package codegen
// MethodFilter is a filter for methods to be generated.
type MethodFilter struct {
filters []string
}
// NewMethodFilter creates a new MethodFilter.
func NewMethodFilter(filters []string) *MethodFilter {
return &MethodFilter{filters}
}
// Filter returns true if the method matches one of the filters.
// In case no filter matches the method, the method is allowed.
func (md *MethodFilter) Filter(method string) bool {
for _, filter := range md.filters {
result := true
if filter[0] == '!' {
filter = filter[1:]
result = false
}
if filter == "*" || filter == method {
return result
}
}
return true // everything matches by default
}

View File

@ -0,0 +1,311 @@
package codegen
import (
"embed"
"strings"
"text/template"
"github.com/saltosystems/winrt-go/internal/winmd"
)
type genDataFile struct {
Filename string
Data genData
}
type genData struct {
Package string
Imports []string
Classes []*genClass
Enums []*genEnum
Interfaces []*genInterface
Structs []*genStruct
Delegates []*genDelegate
}
func (g *genData) ComputeImports(typeDef *winmd.TypeDef) {
// gather all imports
imports := make([]*genImport, 0)
if g.Classes != nil {
for _, c := range g.Classes {
imports = append(imports, c.GetRequiredImports()...)
}
}
if g.Interfaces != nil {
for _, i := range g.Interfaces {
imports = append(imports, i.GetRequiredImports()...)
}
}
for _, i := range imports {
if typeDef.TypeNamespace != i.Namespace {
g.Imports = append(g.Imports, i.ToGoImport())
}
}
}
type genInterface struct {
Name string
GUID string
Signature string
Funcs []*genFunc
}
func (g *genInterface) GetRequiredImports() []*genImport {
imports := make([]*genImport, 0)
for _, f := range g.Funcs {
imports = append(imports, f.RequiresImports...)
}
return imports
}
type genClass struct {
Name string
Signature string
RequiresImports []*genImport
FullyQualifiedName string
ImplInterfaces []*genInterface
ExclusiveInterfaces []*genInterface
HasEmptyConstructor bool
IsAbstract bool
}
func (g *genClass) GetRequiredImports() []*genImport {
imports := make([]*genImport, 0)
if g.RequiresImports != nil {
imports = append(imports, g.RequiresImports...)
}
if g.ExclusiveInterfaces != nil {
for _, i := range g.ExclusiveInterfaces {
imports = append(imports, i.GetRequiredImports()...)
}
}
return imports
}
type genDelegate struct {
Name string
GUID string
Signature string
InParams []*genParam
ReturnParam *genParam // this may be nil
}
type genEnum struct {
Name string
Type string
Signature string
Values []*genEnumValue
}
type genEnumValue struct {
Name string
Value string
}
type genFunc struct {
Name string
RequiresImports []*genImport
Implement bool
FuncOwner string
InParams []*genParam
ReturnParams []*genParam // this may be empty
// ExclusiveTo is the name of the class that this function is exclusive to.
// The funcion will be called statically using the RoGetActivationFactory function.
ExclusiveTo string
RequiresActivation bool
InheritedFrom winmd.QualifiedID
}
type genImport struct {
Namespace, Name string
}
func (i genImport) ToGoImport() string {
if !strings.Contains(i.Namespace, ".") && i.Namespace != "Windows" {
// This is probably a built-in package
return i.Namespace
}
folder := typeToFolder(i.Namespace, i.Name)
return "github.com/saltosystems/winrt-go/" + folder
}
// some of the variables are not public to avoid using them
// by mistake in the code.
type genDefaultValue struct {
value string
isPrimitive bool
}
// some of the variables are not public to avoid using them
// by mistake in the code.
type genParamType struct {
namespace string
name string
IsPointer bool
IsGeneric bool
IsArray bool
IsPrimitive bool
IsEnum bool
UnderlyingEnumType string
defaultValue genDefaultValue
}
// some of the variables are not public to avoid using them
// by mistake in the code.
type genParam struct {
callerPackage string
varName string
Type *genParamType
IsOut bool
}
func (g *genParam) GoVarName() string {
return typeNameToGoName(g.varName, true) // assume all are public
}
func (g *genParam) GoTypeName() string {
if g.Type.IsPrimitive {
return g.Type.name
}
name := typeNameToGoName(g.Type.name, true) // assume all are public
pkg := typePackage(g.Type.namespace, g.Type.name)
if g.callerPackage != pkg {
name = pkg + "." + name
}
return name
}
func (g *genParam) GoDefaultValue() string {
if g.Type.defaultValue.isPrimitive {
return g.Type.defaultValue.value
}
pkg := typePackage(g.Type.namespace, g.Type.name)
if g.callerPackage != pkg {
return pkg + "." + g.Type.defaultValue.value
}
return g.Type.defaultValue.value
}
type genStruct struct {
Name string
Signature string
Fields []*genParam
}
//go:embed templates/*
var templatesFS embed.FS
func getTemplates() (*template.Template, error) {
return template.New("").
Funcs(funcs()).
ParseFS(templatesFS, "templates/*")
}
func funcs() template.FuncMap {
return template.FuncMap{
"funcName": funcName,
"concat": func(a, b []*genParam) []*genParam {
return append(a, b...)
},
"toLower": func(s string) string {
return strings.ToLower(s[:1]) + s[1:]
},
}
}
// funcName is used to generate the name of a function.
func funcName(m genFunc) string {
// There are some special prefixes applied to methods that we need to replace
replacer := strings.NewReplacer(
"get_", "Get",
"put_", "Set",
"add_", "Add",
"remove_", "Remove",
)
name := replacer.Replace(m.Name)
// Add a prefix to static methods to include the owner class of the method.
// This is necessary to avoid conflicts with method names within the same package.
// Static methods are those that are exclusive to a class and require activation.
prefix := ""
if m.ExclusiveTo != "" && m.RequiresActivation {
nsAndName := strings.Split(m.ExclusiveTo, ".")
prefix = typeNameToGoName(nsAndName[len(nsAndName)-1], true)
}
return prefix + name
}
func typeToFolder(ns, name string) string {
fullName := ns
return strings.ToLower(strings.Replace(fullName, ".", "/", -1))
}
func typePackage(ns, name string) string {
sns := strings.Split(ns, ".")
return strings.ToLower(sns[len(sns)-1])
}
func enumName(typeName string, enumName string) string {
return typeName + enumName
}
func typeDefGoName(typeName string, public bool) string {
name := typeName
if isParameterizedName(typeName) {
name = strings.Split(name, "`")[0]
}
if !public {
name = strings.ToLower(name[0:1]) + name[1:]
}
return name
}
func isParameterizedName(typeName string) bool {
// parameterized types contain a '`' followed by the amount of generic parameters in their name.
return strings.Contains(typeName, "`")
}
func typeFilename(typeName string) string {
// public boolean is not relevant, we are going to lower everything
goname := typeDefGoName(typeName, true)
return strings.ToLower(goname)
}
// removes Go reserved words from param names
func cleanReservedWords(name string) string {
switch name {
case "type":
return "mType"
}
return name
}
func typeNameToGoName(typeName string, public bool) string {
name := typeName
if isParameterizedName(typeName) {
name = strings.Split(name, "`")[0]
}
if !public {
name = strings.ToLower(name[0:1]) + name[1:]
}
return name
}

View File

@ -0,0 +1,60 @@
{{if not .IsAbstract}}
const Signature{{.Name}} string = "{{.Signature}}"
type {{.Name}} struct {
ole.IUnknown
}
{{if .HasEmptyConstructor}}
func New{{.Name}}() (*{{.Name}}, error) {
inspectable, err := ole.RoActivateInstance("{{.FullyQualifiedName}}")
if err != nil {
return nil, err
}
return (*{{.Name}})(unsafe.Pointer(inspectable)), nil
}
{{end}}
{{end}}
{{$owner := .Name}}
{{range .ImplInterfaces}}
{{range .Funcs}}
{{if not .Implement}}{{continue}}{{end}}
func (impl *{{$owner}}) {{funcName .}} (
{{- range .InParams -}}
{{/*do not include out parameters, they are used as return values*/ -}}
{{ if .IsOut }}{{continue}}{{ end -}}
{{.GoVarName}} {{template "variabletype.tmpl" . }},
{{- end -}}
)
{{- /* return params */ -}}
( {{range .InParams -}}
{{ if not .IsOut }}{{continue}}{{ end -}}
{{template "variabletype.tmpl" . }},{{end -}}
{{range .ReturnParams}}{{template "variabletype.tmpl" . }},{{end}} error )
{{- /* method body */ -}}
{
itf := impl.MustQueryInterface(ole.NewGUID({{if .InheritedFrom.Namespace}}{{.InheritedFrom.Namespace}}.{{end}}GUID{{.InheritedFrom.Name}}))
defer itf.Release()
v := (*{{if .InheritedFrom.Namespace}}{{.InheritedFrom.Namespace}}.{{end}}{{.InheritedFrom.Name}})(unsafe.Pointer(itf))
return v.{{funcName . -}}
(
{{- range .InParams -}}
{{if .IsOut -}}
{{continue -}}
{{end -}}
{{.GoVarName -}}
,
{{- end -}}
)
}
{{end}}
{{end}}
{{range .ExclusiveInterfaces}}
{{ template "interface.tmpl" .}}
{{end}}

View File

@ -0,0 +1,204 @@
const GUID{{.Name}} string = "{{.GUID}}"
const Signature{{.Name}} string = "{{.Signature}}"
type {{.Name}} struct {
ole.IUnknown
sync.Mutex
refs uintptr
IID ole.GUID
}
type {{.Name}}Vtbl struct {
ole.IUnknownVtbl
Invoke uintptr
}
type {{.Name}}Callback func(instance *{{.Name}},{{- range .InParams -}}
{{.GoVarName}} {{template "variabletype.tmpl" . }},
{{- end -}})
var callbacks{{.Name}} = &{{.Name | toLower}}Callbacks {
mu: &sync.Mutex{},
callbacks: make(map[unsafe.Pointer]{{.Name}}Callback),
}
var releaseChannels{{.Name}} = &{{.Name | toLower}}ReleaseChannels {
mu: &sync.Mutex{},
chans: make(map[unsafe.Pointer]chan struct{}),
}
func New{{.Name}}(iid *ole.GUID, callback {{.Name}}Callback) *{{.Name}} {
// create type instance
size := unsafe.Sizeof(*(*{{.Name}})(nil))
instPtr := kernel32.Malloc(size)
inst := (*{{.Name}})(instPtr)
// get the callbacks for the VTable
callbacks := delegate.RegisterCallbacks(instPtr, inst)
// the VTable should also be allocated in the heap
sizeVTable := unsafe.Sizeof(*(*{{.Name}}Vtbl)(nil))
vTablePtr := kernel32.Malloc(sizeVTable)
inst.RawVTable = (*interface{})(vTablePtr)
vTable := (*{{.Name}}Vtbl)(vTablePtr)
vTable.IUnknownVtbl = ole.IUnknownVtbl{
QueryInterface: callbacks.QueryInterface,
AddRef: callbacks.AddRef,
Release: callbacks.Release,
}
vTable.Invoke = callbacks.Invoke
// Initialize all properties: the malloc may contain garbage
inst.IID = *iid // copy contents
inst.Mutex = sync.Mutex{}
inst.refs = 0
callbacks{{.Name}}.add(unsafe.Pointer(inst), callback)
// See the docs in the releaseChannels{{.Name}} struct
releaseChannels{{.Name}}.acquire(unsafe.Pointer(inst))
inst.addRef()
return inst
}
func (r *{{.Name}}) GetIID() *ole.GUID {
return &r.IID
}
// addRef increments the reference counter by one
func (r *{{.Name}}) addRef() uintptr {
r.Lock()
defer r.Unlock()
r.refs++
return r.refs
}
// removeRef decrements the reference counter by one. If it was already zero, it will just return zero.
func (r *{{.Name}}) removeRef() uintptr {
r.Lock()
defer r.Unlock()
if r.refs > 0 {
r.refs--
}
return r.refs
}
func (instance *{{.Name}}) Invoke(instancePtr, rawArgs0, rawArgs1, rawArgs2, rawArgs3, rawArgs4, rawArgs5, rawArgs6, rawArgs7, rawArgs8 unsafe.Pointer) uintptr {
{{range $i, $arg := .InParams -}}
{{- if $arg.Type.IsEnum -}}
{{$arg.GoVarName}}Raw := ({{$arg.Type.UnderlyingEnumType}})(uintptr(rawArgs{{$i}}))
{{- else -}}
{{$arg.GoVarName}}Ptr := rawArgs{{$i}}
{{- end}}
{{end}}
// See the quote above.
{{range .InParams -}}
{{if .Type.IsEnum -}}
{{.GoVarName}} := ({{template "variabletype.tmpl" . }})({{.GoVarName}}Raw)
{{else -}}
{{.GoVarName}} := ({{template "variabletype.tmpl" . }})({{.GoVarName}}Ptr)
{{end -}}
{{end -}}
if callback, ok := callbacks{{.Name}}.get(instancePtr); ok {
callback(instance, {{range .InParams}}{{.GoVarName}},{{end}})
}
return ole.S_OK
}
func (instance *{{.Name}}) AddRef() uintptr {
return instance.addRef()
}
func (instance *{{.Name}}) Release() uintptr {
rem := instance.removeRef()
if rem == 0 {
// We're done.
instancePtr := unsafe.Pointer(instance)
callbacks{{.Name}}.delete(instancePtr)
// stop release channels used to avoid
// https://github.com/golang/go/issues/55015
releaseChannels{{.Name}}.release(instancePtr)
kernel32.Free(unsafe.Pointer(instance.RawVTable))
kernel32.Free(instancePtr)
}
return rem
}
type {{.Name | toLower}}Callbacks struct {
mu *sync.Mutex
callbacks map[unsafe.Pointer]{{.Name}}Callback
}
func (m *{{.Name | toLower}}Callbacks) add(p unsafe.Pointer, v {{.Name}}Callback) {
m.mu.Lock()
defer m.mu.Unlock()
m.callbacks[p] = v
}
func (m *{{.Name | toLower}}Callbacks) get(p unsafe.Pointer) ({{.Name}}Callback, bool) {
m.mu.Lock()
defer m.mu.Unlock()
v, ok := m.callbacks[p]
return v, ok
}
func (m *{{.Name | toLower}}Callbacks) delete(p unsafe.Pointer) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.callbacks, p)
}
// typedEventHandlerReleaseChannels keeps a map with channels
// used to keep a goroutine alive during the lifecycle of this object.
// This is required to avoid causing a deadlock error.
// See this: https://github.com/golang/go/issues/55015
type {{.Name | toLower}}ReleaseChannels struct {
mu *sync.Mutex
chans map[unsafe.Pointer]chan struct{}
}
func (m *{{.Name | toLower}}ReleaseChannels) acquire(p unsafe.Pointer) {
m.mu.Lock()
defer m.mu.Unlock()
c := make(chan struct{})
m.chans[p] = c
go func() {
// we need a timer to trick the go runtime into
// thinking there's still something going on here
// but we are only really interested in <-c
t := time.NewTimer(time.Minute)
for {
select {
case <-t.C:
t.Reset(time.Minute)
case <-c:
t.Stop()
return
}
}
}()
}
func (m *{{.Name | toLower}}ReleaseChannels) release(p unsafe.Pointer) {
m.mu.Lock()
defer m.mu.Unlock()
if c, ok := m.chans[p]; ok {
close(c)
delete(m.chans, p)
}
}

View File

@ -0,0 +1,6 @@
type {{.Name}} {{.Type}}
const Signature{{.Name}} string = "{{.Signature}}"
const ({{range .Values}}
{{.Name}} {{$.Name}} = {{.Value}}{{end}}
)

View File

@ -0,0 +1,36 @@
// Code generated by winrt-go-gen. DO NOT EDIT.
//go:build windows
//nolint:all
package {{.Package}}
import (
"syscall"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/saltosystems/winrt-go"
"github.com/saltosystems/winrt-go/internal/kernel32"
{{range .Imports}}"{{.}}"
{{end}}
)
{{range .Interfaces}}
{{template "interface.tmpl" .}}
{{end}}
{{range .Classes}}
{{template "class.tmpl" .}}
{{end}}
{{range .Enums}}
{{template "enum.tmpl" .}}
{{end}}
{{range .Structs}}
{{template "struct.tmpl" .}}
{{end}}
{{range .Delegates}}
{{template "delegate.tmpl" .}}
{{end}}

View File

@ -0,0 +1,30 @@
{{if .Implement}}
func {{if and .FuncOwner (not .RequiresActivation)}}
(v *{{.FuncOwner}})
{{- end -}}
{{funcName .}}
{{- /* in params */ -}}
(
{{- range .InParams -}}
{{/*do not include out parameters, they are used as return values*/ -}}
{{ if .IsOut }}{{continue}}{{ end -}}
{{.GoVarName}} {{template "variabletype.tmpl" . }},
{{- end -}}
)
{{- /* return params */ -}}
( {{range .InParams -}}
{{ if not .IsOut }}{{continue}}{{ end -}}
{{template "variabletype.tmpl" . }},{{end -}}
{{range .ReturnParams}}{{template "variabletype.tmpl" . }},{{end}} error )
{{- /* method body */ -}}
{
{{template "funcimpl.tmpl" .}}
}
{{end}}

View File

@ -0,0 +1,86 @@
{{if .RequiresActivation}}{{/*Activate class*/ -}}
inspectable, err := ole.RoGetActivationFactory("{{.ExclusiveTo}}", ole.NewGUID(GUID{{.FuncOwner}}))
if err != nil {
return {{range .ReturnParams -}}
{{.GoDefaultValue}}, {{end}}err
}
v := (*{{.FuncOwner}})(unsafe.Pointer(inspectable))
{{end -}}
{{- /* Declare out variables*/ -}}
{{range (concat .InParams .ReturnParams) -}}
{{ if not .IsOut}}{{continue}}{{end -}}
{{if eq .GoTypeName "string" -}}
var {{.GoVarName}}HStr ole.HString
{{ else -}}
var {{.GoVarName}} {{template "variabletype.tmpl" . -}}
{{if .Type.IsArray}} = make({{template "variabletype.tmpl" . -}}, {{.GoVarName}}Size){{end}}
{{ end -}}
{{ end -}}
{{- /* Convert in variables to winrt types */ -}}
{{range .InParams -}}
{{ if .IsOut}}{{continue}}{{end -}}
{{if eq .GoTypeName "string" -}}
{{.GoVarName}}HStr, err := ole.NewHString({{.GoVarName}})
if err != nil{
return {{range $.InParams}}{{if .IsOut}}{{.GoDefaultValue}}, {{end}}{{end -}}
{{range $.ReturnParams }}{{.GoDefaultValue}}, {{end}}err
}
{{ end -}}
{{ end -}}
hr, _, _ := syscall.SyscallN(
v.VTable().{{funcName .}},
uintptr(unsafe.Pointer(v)), // this
{{range (concat .InParams .ReturnParams) -}}
{{if .Type.IsArray -}}
{{/* Arrays need to pass a pointer to their first element */ -}}
uintptr(unsafe.Pointer(&{{.GoVarName}}[0])), // {{if .IsOut}}out{{else}}in{{end}} {{.GoTypeName}}
{{else if .IsOut -}}
{{if (or .Type.IsPrimitive .Type.IsEnum) -}}
{{if eq .GoTypeName "string" -}}
uintptr(unsafe.Pointer(&{{.GoVarName}}HStr)), // out {{.GoTypeName}}
{{else -}}
uintptr(unsafe.Pointer(&{{.GoVarName}})), // out {{.GoTypeName}}
{{end -}}
{{else -}}
uintptr(unsafe.Pointer(&{{.GoVarName}})), // out {{.GoTypeName}}
{{end -}}
{{else if .Type.IsPointer -}}
uintptr(unsafe.Pointer({{.GoVarName}})), // in {{.GoTypeName}}
{{else if (or .Type.IsPrimitive .Type.IsEnum) -}}
{{ if eq .GoTypeName "bool" -}}
uintptr(*(*byte)(unsafe.Pointer(&{{.GoVarName}}))), // in {{.GoTypeName}}
{{ else if eq .GoTypeName "string" -}}
uintptr({{.GoVarName}}HStr), // in {{.GoTypeName}}
{{else -}}
uintptr({{.GoVarName}}), // in {{.GoTypeName}}
{{end -}}
{{else if .Type.IsGeneric -}}
uintptr({{.GoVarName}}), // in {{.GoTypeName}}
{{else -}}
uintptr(unsafe.Pointer(&{{.GoVarName}})), // in {{.GoTypeName}}
{{end -}}
{{end -}}
)
if hr != 0 {
return {{range .InParams}}{{if .IsOut}}{{.GoDefaultValue}}, {{end}}{{end -}}
{{range .ReturnParams }}{{.GoDefaultValue}}, {{end}}ole.NewError(hr)
}
{{range (concat .InParams .ReturnParams) -}}
{{ if not .IsOut}}{{continue}}{{end -}}
{{if eq .GoTypeName "string" -}}
{{.GoVarName}} := {{.GoVarName}}HStr.String()
ole.DeleteHString({{.GoVarName}}HStr)
{{ end -}}
{{ end -}}
return {{range .InParams}}{{if .IsOut}}{{.GoVarName}}, {{end}}{{end -}}
{{range .ReturnParams }}{{.GoVarName}},{{end}} nil
{{- /* remove trailing white space*/ -}}

View File

@ -0,0 +1,22 @@
const GUID{{.Name}} string = "{{.GUID}}"
const Signature{{.Name}} string = "{{.Signature}}"
type {{.Name}} struct {
ole.IInspectable
}
type {{.Name}}Vtbl struct {
ole.IInspectableVtbl
{{range .Funcs}}
{{funcName .}} uintptr
{{- end}}
}
func (v *{{.Name}}) VTable() *{{.Name}}Vtbl {
return (*{{.Name}}Vtbl)(unsafe.Pointer(v.RawVTable))
}
{{range .Funcs}}
{{template "func.tmpl" .}}
{{end}}

View File

@ -0,0 +1,7 @@
const Signature{{.Name}} string = "{{.Signature}}"
type {{.Name}} struct {
{{range .Fields}}
{{.GoVarName}} {{.GoTypeName}}
{{end}}
}

View File

@ -0,0 +1,5 @@
{{if .Type.IsArray}}[]{{end -}}
{{if .Type.IsPointer}}*{{end -}}
{{.GoTypeName -}}
{{- /*remove trailing whitespace*/ -}}

View File

@ -0,0 +1,139 @@
//go:build windows
package delegate
import (
"sync"
"syscall"
"unsafe"
"github.com/go-ole/go-ole"
)
// Only a limited number of callbacks may be created in a single Go process,
// and any memory allocated for these callbacks is never released.
// Between NewCallback and NewCallbackCDecl, at least 1024 callbacks can always be created.
var (
queryInterfaceCallback = syscall.NewCallback(queryInterface)
addRefCallback = syscall.NewCallback(addRef)
releaseCallback = syscall.NewCallback(release)
invokeCallback = syscall.NewCallback(invoke)
)
// Delegate represents a WinRT delegate class.
type Delegate interface {
GetIID() *ole.GUID
Invoke(instancePtr, rawArgs0, rawArgs1, rawArgs2, rawArgs3, rawArgs4, rawArgs5, rawArgs6, rawArgs7, rawArgs8 unsafe.Pointer) uintptr
AddRef() uintptr
Release() uintptr
}
// Callbacks contains the syscalls registered on Windows.
type Callbacks struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
Invoke uintptr
}
var mutex = sync.RWMutex{}
var instances = make(map[uintptr]Delegate)
// RegisterCallbacks adds the given pointer and the Delegate it points to to our instances.
// This is required to redirect received callbacks to the correct object instance.
// The function returns the callbacks to use when creating a new delegate instance.
func RegisterCallbacks(ptr unsafe.Pointer, inst Delegate) *Callbacks {
mutex.Lock()
defer mutex.Unlock()
instances[uintptr(ptr)] = inst
return &Callbacks{
QueryInterface: queryInterfaceCallback,
AddRef: addRefCallback,
Release: releaseCallback,
Invoke: invokeCallback,
}
}
func getInstance(ptr unsafe.Pointer) (Delegate, bool) {
mutex.RLock() // locks writing, allows concurrent read
defer mutex.RUnlock()
i, ok := instances[uintptr(ptr)]
return i, ok
}
func removeInstance(ptr unsafe.Pointer) {
mutex.Lock()
defer mutex.Unlock()
delete(instances, uintptr(ptr))
}
func queryInterface(instancePtr unsafe.Pointer, iidPtr unsafe.Pointer, ppvObject *unsafe.Pointer) uintptr {
instance, ok := getInstance(instancePtr)
if !ok {
// instance not found
return ole.E_POINTER
}
// Checkout these sources for more information about the QueryInterface method.
// - https://docs.microsoft.com/en-us/cpp/atl/queryinterface
// - https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void)
if ppvObject == nil {
// If ppvObject (the address) is nullptr, then this method returns E_POINTER.
return ole.E_POINTER
}
// This function must adhere to the QueryInterface defined here:
// https://docs.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown
if iid := (*ole.GUID)(iidPtr); ole.IsEqualGUID(iid, instance.GetIID()) || ole.IsEqualGUID(iid, ole.IID_IUnknown) || ole.IsEqualGUID(iid, ole.IID_IInspectable) {
*ppvObject = instancePtr
} else {
*ppvObject = nil
// Return E_NOINTERFACE if the interface is not supported
return ole.E_NOINTERFACE
}
// If the COM object implements the interface, then it returns
// a pointer to that interface after calling IUnknown::AddRef on it.
(*ole.IUnknown)(*ppvObject).AddRef()
// Return S_OK if the interface is supported
return ole.S_OK
}
func invoke(instancePtr, rawArgs0, rawArgs1, rawArgs2, rawArgs3, rawArgs4, rawArgs5, rawArgs6, rawArgs7, rawArgs8 unsafe.Pointer) uintptr {
instance, ok := getInstance(instancePtr)
if !ok {
// instance not found
return ole.E_FAIL
}
return instance.Invoke(instancePtr, rawArgs0, rawArgs1, rawArgs2, rawArgs3, rawArgs4, rawArgs5, rawArgs6, rawArgs7, rawArgs8)
}
func addRef(instancePtr unsafe.Pointer) uintptr {
instance, ok := getInstance(instancePtr)
if !ok {
// instance not found
return ole.E_FAIL
}
return instance.AddRef()
}
func release(instancePtr unsafe.Pointer) uintptr {
instance, ok := getInstance(instancePtr)
if !ok {
// instance not found
return ole.E_FAIL
}
rem := instance.Release()
if rem == 0 {
// remove this delegate
removeInstance(instancePtr)
}
return rem
}

View File

@ -0,0 +1,78 @@
//go:build windows
package kernel32
import (
"sync/atomic"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type (
heapHandle = uintptr
win32Error uint32
heapFlags uint32
)
const (
heapNone heapFlags = 0
heapZeroMemory heapFlags = 8 // The allocated memory will be initialized to zero.
)
var (
libKernel32 = windows.NewLazySystemDLL("kernel32.dll")
pHeapFree uintptr
pHeapAlloc uintptr
pGetProcessHeap uintptr
hHeap heapHandle
)
func init() {
hHeap, _ = getProcessHeap()
}
// Malloc allocates the given amount of bytes in the heap
func Malloc(size uintptr) unsafe.Pointer {
return heapAlloc(hHeap, heapZeroMemory, size)
}
// Free releases the given unsafe pointer from the heap
func Free(inst unsafe.Pointer) {
_, _ = heapFree(hHeap, heapNone, inst)
}
// https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc
func heapAlloc(hHeap heapHandle, dwFlags heapFlags, dwBytes uintptr) unsafe.Pointer {
addr := getProcAddr(&pHeapAlloc, libKernel32, "HeapAlloc")
allocatedPtr, _, _ := syscall.SyscallN(addr, hHeap, uintptr(dwFlags), dwBytes)
// Since this pointer is allocated in the heap by Windows, it will never be
// GCd by Go, so this is a safe operation.
// But linter thinks it is not (probably because we are not using CGO) and fails.
return unsafe.Pointer(allocatedPtr) //nolint:gosec,govet
}
// https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapfree
func heapFree(hHeap heapHandle, dwFlags heapFlags, lpMem unsafe.Pointer) (bool, win32Error) {
addr := getProcAddr(&pHeapFree, libKernel32, "HeapFree")
ret, _, err := syscall.SyscallN(addr, hHeap, uintptr(dwFlags), uintptr(lpMem))
return ret == 0, win32Error(err)
}
func getProcessHeap() (heapHandle, win32Error) {
addr := getProcAddr(&pGetProcessHeap, libKernel32, "GetProcessHeap")
ret, _, err := syscall.SyscallN(addr)
return ret, win32Error(err)
}
func getProcAddr(pAddr *uintptr, lib *windows.LazyDLL, procName string) uintptr {
addr := atomic.LoadUintptr(pAddr)
if addr == 0 {
addr = lib.NewProc(procName).Addr()
atomic.StoreUintptr(pAddr, addr)
}
return addr
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,76 @@
package winmd
import (
"github.com/tdakkota/win32metadata/md"
"github.com/tdakkota/win32metadata/types"
)
// GetMethodOverloadName finds and returns the overload attribute for the given method
func GetMethodOverloadName(ctx *types.Context, methodDef *types.MethodDef) string {
cAttrTable := ctx.Table(md.CustomAttribute)
for i := uint32(0); i < cAttrTable.RowCount(); i++ {
var cAttr types.CustomAttribute
if err := cAttr.FromRow(cAttrTable.Row(i)); err != nil {
continue
}
// - Parent: The owner of the Attribute must be the given func
if cAttrParentTable, _ := cAttr.Parent.Table(); cAttrParentTable != md.MethodDef {
continue
}
var parentMethodDef types.MethodDef
row, ok := cAttr.Parent.Row(ctx)
if !ok {
continue
}
if err := parentMethodDef.FromRow(row); err != nil {
continue
}
// does the blob belong to the method we're looking for?
if parentMethodDef.Name != methodDef.Name || string(parentMethodDef.Signature) != string(methodDef.Signature) {
continue
}
// - Type: the attribute type must be the given type
// the cAttr.Type table can be either a MemberRef or a MethodRef.
// Since we are looking for a type, we will only consider the MemberRef.
if cAttrTypeTable, _ := cAttr.Type.Table(); cAttrTypeTable != md.MemberRef {
continue
}
var attrTypeMemberRef types.MemberRef
row, ok = cAttr.Type.Row(ctx)
if !ok {
continue
}
if err := attrTypeMemberRef.FromRow(row); err != nil {
continue
}
// we need to check the MemberRef Class
// the value can belong to several tables, but we are only going to check for TypeRef
if classTable, _ := attrTypeMemberRef.Class.Table(); classTable != md.TypeRef {
continue
}
var attrTypeRef types.TypeRef
row, ok = attrTypeMemberRef.Class.Row(ctx)
if !ok {
continue
}
if err := attrTypeRef.FromRow(row); err != nil {
continue
}
if attrTypeRef.TypeNamespace+"."+attrTypeRef.TypeName == AttributeTypeOverloadAttribute {
// Metadata values start with 0x01 0x00 and ends with 0x00 0x00
mdVal := cAttr.Value[2 : len(cAttr.Value)-2]
// the next value is the length of the string
mdVal = mdVal[1:]
return string(mdVal)
}
}
return methodDef.Name
}

89
internal/winmd/store.go Normal file
View File

@ -0,0 +1,89 @@
package winmd
import (
"fmt"
"github.com/go-kit/log"
"github.com/tdakkota/win32metadata/md"
"github.com/tdakkota/win32metadata/types"
)
// ClassNotFoundError is returned when a class is not found.
type ClassNotFoundError struct {
Class string
}
func (e *ClassNotFoundError) Error() string {
return fmt.Sprintf("class %s was not found", e.Class)
}
// Store holds the windows metadata contexts. It can be used to get the metadata across multiple files.
type Store struct {
contexts map[string]*types.Context
logger log.Logger
}
// NewStore loads all windows metadata files and returns a new Store.
func NewStore(logger log.Logger) (*Store, error) {
contexts := make(map[string]*types.Context)
winmdFiles, err := allFiles()
if err != nil {
return nil, err
}
// parse and store all files in memory
for _, f := range winmdFiles {
winmdCtx, err := parseWinMDFile(f.Name())
if err != nil {
return nil, err
}
contexts[f.Name()] = winmdCtx
}
return &Store{
contexts: contexts,
logger: logger,
}, nil
}
func parseWinMDFile(path string) (*types.Context, error) {
f, err := open(path)
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
return types.FromPE(f)
}
// TypeDefByName returns a type definition that matches the given name.
func (mds *Store) TypeDefByName(class string) (*TypeDef, error) {
// the type can belong to any of the contexts
for _, ctx := range mds.contexts {
if td := mds.typeDefByNameAndCtx(class, ctx); td != nil {
return td, nil // return the first match
}
}
return nil, &ClassNotFoundError{Class: class}
}
func (mds *Store) typeDefByNameAndCtx(class string, ctx *types.Context) *TypeDef {
typeDefTable := ctx.Table(md.TypeDef)
for i := uint32(0); i < typeDefTable.RowCount(); i++ {
var typeDef types.TypeDef
if err := typeDef.FromRow(typeDefTable.Row(i)); err != nil {
continue // keep searching instead of failing
}
if typeDef.TypeNamespace+"."+typeDef.TypeName == class {
return &TypeDef{
TypeDef: typeDef,
HasContext: HasContext{ctx},
logger: mds.logger,
}
}
}
return nil
}

310
internal/winmd/typedef.go Normal file
View File

@ -0,0 +1,310 @@
package winmd
import (
"fmt"
"strconv"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/tdakkota/win32metadata/md"
"github.com/tdakkota/win32metadata/types"
)
// TypeDef is a helper struct that wraps types.TypeDef and stores the original context
// of the typeDef.
type TypeDef struct {
types.TypeDef
HasContext
logger log.Logger
}
// QualifiedID holds the namespace and the name of a qualified element. This may be a type, a static function or a field
type QualifiedID struct {
Namespace string
Name string
}
// GetValueForEnumField returns the value of the requested enum field.
func (typeDef *TypeDef) GetValueForEnumField(fieldIndex uint32) (string, error) {
// For each Enum value definition, there is a corresponding row in the Constant table to store the integer value for the enum value.
tableConstants := typeDef.Ctx().Table(md.Constant)
for i := uint32(0); i < tableConstants.RowCount(); i++ {
var constant types.Constant
if err := constant.FromRow(tableConstants.Row(i)); err != nil {
return "", err
}
if t, _ := constant.Parent.Table(); t != md.Field {
continue
}
// does the blob belong to the field we're looking for?
// The parent is an index into the field table that holds the associated enum value record
if constant.Parent.TableIndex() != fieldIndex {
continue
}
// The value is a blob that we need to read as little endian
var blobIndex uint32
for i, b := range constant.Value {
blobIndex += uint32(b) << (i * 8)
}
return strconv.Itoa(int(blobIndex)), nil
}
return "", fmt.Errorf("no value found for field %d", fieldIndex)
}
// GetAttributeWithType returns the value of the given attribute type and fails if not found.
func (typeDef *TypeDef) GetAttributeWithType(lookupAttrTypeClass string) ([]byte, error) {
result := typeDef.GetTypeDefAttributesWithType(lookupAttrTypeClass)
if len(result) == 0 {
return nil, fmt.Errorf("type %s has no custom attribute %s", typeDef.TypeNamespace+"."+typeDef.TypeName, lookupAttrTypeClass)
} else if len(result) > 1 {
_ = level.Warn(typeDef.logger).Log(
"msg", "type has multiple custom attributes, returning the first one",
"type", typeDef.TypeNamespace+"."+typeDef.TypeName,
"attr", lookupAttrTypeClass,
)
}
return result[0], nil
}
// GetTypeDefAttributesWithType returns the values of all the attributes that match the given type.
func (typeDef *TypeDef) GetTypeDefAttributesWithType(lookupAttrTypeClass string) [][]byte {
result := make([][]byte, 0)
cAttrTable := typeDef.Ctx().Table(md.CustomAttribute)
for i := uint32(0); i < cAttrTable.RowCount(); i++ {
var cAttr types.CustomAttribute
if err := cAttr.FromRow(cAttrTable.Row(i)); err != nil {
continue
}
// - Parent: The owner of the Attribute must be the given typeDef
if cAttrParentTable, _ := cAttr.Parent.Table(); cAttrParentTable != md.TypeDef {
continue
}
var parentTypeDef TypeDef
row, ok := cAttr.Parent.Row(typeDef.Ctx())
if !ok {
continue
}
if err := parentTypeDef.FromRow(row); err != nil {
continue
}
// does the blob belong to the type we're looking for?
if parentTypeDef.TypeNamespace != typeDef.TypeNamespace || parentTypeDef.TypeName != typeDef.TypeName {
continue
}
// - Type: the attribute type must be the given type
// the cAttr.Type table can be either a MemberRef or a MethodRef.
// Since we are looking for a type, we will only consider the MemberRef.
if cAttrTypeTable, _ := cAttr.Type.Table(); cAttrTypeTable != md.MemberRef {
continue
}
var attrTypeMemberRef types.MemberRef
row, ok = cAttr.Type.Row(typeDef.Ctx())
if !ok {
continue
}
if err := attrTypeMemberRef.FromRow(row); err != nil {
continue
}
// we need to check the MemberRef Class
// the value can belong to several tables, but we are only going to check for TypeRef
if classTable, _ := attrTypeMemberRef.Class.Table(); classTable != md.TypeRef {
continue
}
var attrTypeRef types.TypeRef
row, ok = attrTypeMemberRef.Class.Row(typeDef.Ctx())
if !ok {
continue
}
if err := attrTypeRef.FromRow(row); err != nil {
continue
}
if attrTypeRef.TypeNamespace+"."+attrTypeRef.TypeName == lookupAttrTypeClass {
result = append(result, cAttr.Value)
}
}
return result
}
// GetImplementedInterfaces returns the interfaces implemented by the type.
func (typeDef *TypeDef) GetImplementedInterfaces() ([]QualifiedID, error) {
interfaces := make([]QualifiedID, 0)
tableInterfaceImpl := typeDef.Ctx().Table(md.InterfaceImpl)
for i := uint32(0); i < tableInterfaceImpl.RowCount(); i++ {
var interfaceImpl types.InterfaceImpl
if err := interfaceImpl.FromRow(tableInterfaceImpl.Row(i)); err != nil {
return nil, err
}
classTd, err := interfaceImpl.ResolveClass(typeDef.Ctx())
if err != nil {
return nil, err
}
if classTd.TypeNamespace+"."+classTd.TypeName != typeDef.TypeNamespace+"."+typeDef.TypeName {
// not the class we are looking for
continue
}
if t, ok := interfaceImpl.Interface.Table(); ok && t == md.TypeSpec {
// ignore type spec rows
continue
}
ifaceNS, ifaceName, err := typeDef.Ctx().ResolveTypeDefOrRefName(interfaceImpl.Interface)
if err != nil {
return nil, err
}
interfaces = append(interfaces, QualifiedID{Namespace: ifaceNS, Name: ifaceName})
}
return interfaces, nil
}
// Extends returns true if the type extends the given class
func (typeDef *TypeDef) Extends(class string) (bool, error) {
ns, name, err := typeDef.Ctx().ResolveTypeDefOrRefName(typeDef.TypeDef.Extends)
if err != nil {
return false, err
}
return ns+"."+name == class, nil
}
// GetGenericParams returns the generic parameters of the type.
func (typeDef *TypeDef) GetGenericParams() ([]*types.GenericParam, error) {
params := make([]*types.GenericParam, 0)
tableGenericParam := typeDef.Ctx().Table(md.GenericParam)
for i := uint32(0); i < tableGenericParam.RowCount(); i++ {
var genericParam types.GenericParam
if err := genericParam.FromRow(tableGenericParam.Row(i)); err != nil {
continue
}
// - Owner: The owner of the Attribute must be the given typeDef
if genericParamOwnerTable, _ := genericParam.Owner.Table(); genericParamOwnerTable != md.TypeDef {
continue
}
var ownerTypeDef types.TypeDef
row, ok := genericParam.Owner.Row(typeDef.Ctx())
if !ok {
continue
}
if err := ownerTypeDef.FromRow(row); err != nil {
continue
}
// does the blob belong to the type we're looking for?
if ownerTypeDef.TypeNamespace != typeDef.TypeNamespace || ownerTypeDef.TypeName != typeDef.TypeName {
continue
}
params = append(params, &genericParam)
}
if len(params) == 0 {
return nil, fmt.Errorf("could not find generic params for type %s.%s", typeDef.TypeNamespace, typeDef.TypeName)
}
return params, nil
}
// IsInterface returns true if the type is an interface
func (typeDef *TypeDef) IsInterface() bool {
return typeDef.Flags.Interface()
}
// IsEnum returns true if the type is an enum
func (typeDef *TypeDef) IsEnum() bool {
ok, err := typeDef.Extends("System.Enum")
if err != nil {
_ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err)
return false
}
return ok
}
// IsDelegate returns true if the type is a delegate
func (typeDef *TypeDef) IsDelegate() bool {
if !(typeDef.Flags.Public() && typeDef.Flags.Sealed()) {
return false
}
ok, err := typeDef.Extends("System.MulticastDelegate")
if err != nil {
_ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err)
return false
}
return ok
}
// IsStruct returns true if the type is a struct
func (typeDef *TypeDef) IsStruct() bool {
ok, err := typeDef.Extends("System.ValueType")
if err != nil {
_ = level.Error(typeDef.logger).Log("msg", "error resolving type extends, all classes should extend at least System.Object", "err", err)
return false
}
return ok
}
// IsRuntimeClass returns true if the type is a runtime class
func (typeDef *TypeDef) IsRuntimeClass() bool {
// Flags: all runtime classes must carry the public, auto layout, class, and tdWindowsRuntime flags.
return typeDef.Flags.Public() && typeDef.Flags.AutoLayout() && typeDef.Flags.Class() && typeDef.Flags&0x4000 != 0
}
// GUID returns the GUID of the type.
func (typeDef *TypeDef) GUID() (string, error) {
blob, err := typeDef.GetAttributeWithType(AttributeTypeGUID)
if err != nil {
return "", err
}
return guidBlobToString(blob)
}
// guidBlobToString converts an array into the textual representation of a GUID
func guidBlobToString(b types.Blob) (string, error) {
// the guid is a blob of 20 bytes
if len(b) != 20 {
return "", fmt.Errorf("invalid GUID blob length: %d", len(b))
}
// that starts with 0100
if b[0] != 0x01 || b[1] != 0x00 {
return "", fmt.Errorf("invalid GUID blob header, expected '0x01 0x00' but found '0x%02x 0x%02x'", b[0], b[1])
}
// and ends with 0000
if b[18] != 0x00 || b[19] != 0x00 {
return "", fmt.Errorf("invalid GUID blob footer, expected '0x00 0x00' but found '0x%02x 0x%02x'", b[18], b[19])
}
guid := b[2 : len(b)-2]
// the string version has 5 parts separated by '-'
return fmt.Sprintf("%08x-%04x-%04x-%04x-%04x%08x",
// The first 3 are encoded as little endian
uint32(guid[0])|uint32(guid[1])<<8|uint32(guid[2])<<16|uint32(guid[3])<<24,
uint16(guid[4])|uint16(guid[5])<<8,
uint16(guid[6])|uint16(guid[7])<<8,
//the rest is not
uint16(guid[8])<<8|uint16(guid[9]),
uint16(guid[10])<<8|uint16(guid[11]),
uint32(guid[12])<<24|uint32(guid[13])<<16|uint32(guid[14])<<8|uint32(guid[15])), nil
}

61
internal/winmd/winmd.go Normal file
View File

@ -0,0 +1,61 @@
package winmd
import (
"bytes"
"debug/pe"
"embed"
"io/fs"
"io/ioutil"
"github.com/tdakkota/win32metadata/types"
)
// Custom Attributes
const (
AttributeTypeGUID = "Windows.Foundation.Metadata.GuidAttribute"
AttributeTypeExclusiveTo = "Windows.Foundation.Metadata.ExclusiveToAttribute"
AttributeTypeStaticAttribute = "Windows.Foundation.Metadata.StaticAttribute"
AttributeTypeActivatableAttribute = "Windows.Foundation.Metadata.ActivatableAttribute"
AttributeTypeDefaultAttribute = "Windows.Foundation.Metadata.DefaultAttribute"
AttributeTypeOverloadAttribute = "Windows.Foundation.Metadata.OverloadAttribute"
)
// HasContext is a helper struct that holds the original context of a metadata element.
type HasContext struct {
originalCtx *types.Context
}
// Ctx return the original context of the element.
func (hctx *HasContext) Ctx() *types.Context {
return hctx.originalCtx
}
//go:embed metadata/*.winmd
var files embed.FS
// allFiles returns all winmd files embedded in the binary.
func allFiles() ([]fs.DirEntry, error) {
return files.ReadDir("metadata")
}
// open reads the given file and returns a pe.File instance.
// The user should close the returned instance once he is done working with it.
func open(path string) (*pe.File, error) {
f, err := files.Open("metadata/" + path)
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
pef, err := pe.NewFile(bytes.NewReader(data))
if err != nil {
return nil, err
}
return pef, nil
}