first commit
This commit is contained in:
44
internal/cli/cli.go
Normal file
44
internal/cli/cli.go
Normal 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
1260
internal/codegen/codegen.go
Normal file
File diff suppressed because it is too large
Load Diff
41
internal/codegen/config.go
Normal file
41
internal/codegen/config.go
Normal 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
|
||||
}
|
28
internal/codegen/methodfilter.go
Normal file
28
internal/codegen/methodfilter.go
Normal 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
|
||||
}
|
311
internal/codegen/templates.go
Normal file
311
internal/codegen/templates.go
Normal 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
|
||||
}
|
60
internal/codegen/templates/class.tmpl
Normal file
60
internal/codegen/templates/class.tmpl
Normal 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}}
|
204
internal/codegen/templates/delegate.tmpl
Normal file
204
internal/codegen/templates/delegate.tmpl
Normal 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)
|
||||
}
|
||||
}
|
6
internal/codegen/templates/enum.tmpl
Normal file
6
internal/codegen/templates/enum.tmpl
Normal file
@ -0,0 +1,6 @@
|
||||
type {{.Name}} {{.Type}}
|
||||
const Signature{{.Name}} string = "{{.Signature}}"
|
||||
|
||||
const ({{range .Values}}
|
||||
{{.Name}} {{$.Name}} = {{.Value}}{{end}}
|
||||
)
|
36
internal/codegen/templates/file.tmpl
Normal file
36
internal/codegen/templates/file.tmpl
Normal 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}}
|
30
internal/codegen/templates/func.tmpl
Normal file
30
internal/codegen/templates/func.tmpl
Normal 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}}
|
86
internal/codegen/templates/funcimpl.tmpl
Normal file
86
internal/codegen/templates/funcimpl.tmpl
Normal 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*/ -}}
|
22
internal/codegen/templates/interface.tmpl
Normal file
22
internal/codegen/templates/interface.tmpl
Normal 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}}
|
7
internal/codegen/templates/struct.tmpl
Normal file
7
internal/codegen/templates/struct.tmpl
Normal file
@ -0,0 +1,7 @@
|
||||
const Signature{{.Name}} string = "{{.Signature}}"
|
||||
|
||||
type {{.Name}} struct {
|
||||
{{range .Fields}}
|
||||
{{.GoVarName}} {{.GoTypeName}}
|
||||
{{end}}
|
||||
}
|
5
internal/codegen/templates/variabletype.tmpl
Normal file
5
internal/codegen/templates/variabletype.tmpl
Normal file
@ -0,0 +1,5 @@
|
||||
{{if .Type.IsArray}}[]{{end -}}
|
||||
{{if .Type.IsPointer}}*{{end -}}
|
||||
{{.GoTypeName -}}
|
||||
|
||||
{{- /*remove trailing whitespace*/ -}}
|
139
internal/delegate/delegate.go
Normal file
139
internal/delegate/delegate.go
Normal 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
|
||||
}
|
78
internal/kernel32/kernel32.go
Normal file
78
internal/kernel32/kernel32.go
Normal 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
|
||||
}
|
BIN
internal/winmd/metadata/Windows.AI.winmd
Normal file
BIN
internal/winmd/metadata/Windows.AI.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.ApplicationModel.winmd
Normal file
BIN
internal/winmd/metadata/Windows.ApplicationModel.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Data.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Data.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Devices.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Devices.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Foundation.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Foundation.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Gaming.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Gaming.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Globalization.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Globalization.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Graphics.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Graphics.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Management.Setup.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Management.Setup.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Management.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Management.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Media.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Media.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Networking.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Networking.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Perception.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Perception.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Security.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Security.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Services.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Services.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Storage.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Storage.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.System.winmd
Normal file
BIN
internal/winmd/metadata/Windows.System.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.UI.Xaml.winmd
Normal file
BIN
internal/winmd/metadata/Windows.UI.Xaml.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.UI.winmd
Normal file
BIN
internal/winmd/metadata/Windows.UI.winmd
Normal file
Binary file not shown.
BIN
internal/winmd/metadata/Windows.Web.winmd
Normal file
BIN
internal/winmd/metadata/Windows.Web.winmd
Normal file
Binary file not shown.
76
internal/winmd/methoddef.go
Normal file
76
internal/winmd/methoddef.go
Normal 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
89
internal/winmd/store.go
Normal 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
310
internal/winmd/typedef.go
Normal 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
61
internal/winmd/winmd.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user