AX中args传值

Axapta参数传递技巧
本文介绍了Axapta中Args()类的多种使用方法,包括如何利用args().caller()获取调用方信息、args().record()获取数据记录、args().parmEnum()获取枚举类型值等,帮助开发者更高效地进行参数传递。
部署运行你感兴趣的模型镜像

Args()是Axapta用来传递参数最好用的系统类,其参数传递最常见的包括从窗体到类的互传,从窗体到报表的传递,从类到报表
的传递。

1)args().caller() 可以用来获取调用方的信息。比如某人报表是通过类CallAAA调用的,那么在报表里用CallAAA = element.
args.caller();即可获得呼叫类的引用,从而可以用引用类中方法。

2)args().record() 可以用来获取调用方数据记录。这个常用于从窗体到类或报表的参数传递。这个得到的为窗体中MeunItem
指定数据源的鼠标指定的那条数据记录的值(语文学得最差不太会表达,太多“的”字了希望能看明白^_^)


3) args().parmEnum() 可以用来获取枚举类型的值,如我们经常在Menu Item的EnumParameter属性设置需要的属性值,
然后在被调用者里用args().parmEnum()即可获得被设置好的属性值。

4)args().parm() 可以用来获取parameters设置的属性值,可以用它来传某些字符串型的参数到某些地方等。

5)args().parmEnumType() 可以用来传递一个枚举类型。

6)args().parmObject() 可以用来传递一个对象,在Axapta里,Object的范围比较广,比如一个窗体,一个Class等都可以当一
个Object来看待,又或者将某些数据封装到某些系统类后,parmObject()传递这个类时也会把封装好的数据一并传递了。

Q:表为上下表关系要求:在下表状态改变为同一状态时,上表的状态要保持一致
modify方法只能获取到他的前一个状态而获取不到他改变后的状态
可以在他的active()方法中

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

.3 反射 # 各位读者朋友,很高兴大家通过本博客学习 Go 语言,感谢一路相伴!《Go语言设计与实现》的纸质版图书已经上架京东,有需要的朋友请点击 链接 购买。 虽然在大多数的应用和服务中并不常见,但是很多框架都依赖 Go 语言的反射机制简化代码。因为 Go 语言的语法元素很少、设计简单,所以它没有特别强的表达能力,但是 Go 语言的 reflect 包能够弥补它在语法上reflect.Type的一些劣势。 reflect 实现了运行时的反射能力,能够让程序操作不同类型的对象1。反射包中有两对非常重要的函数和类型,两个函数分别是: reflect.TypeOf 能获取类型信息; reflect.ValueOf 能获取数据的运行时表示; 两个类型是 reflect.Type 和 reflect.Value,它们与函数是一一对应的关系: golang-reflection 图 4-15 反射函数和类型 类型 reflect.Type 是反射包定义的一个接口,我们可以使用 reflect.TypeOf 函数获取任意变量的类型,reflect.Type 接口中定义了一些有趣的方法,MethodByName 可以获取当前类型对应方法的引用、Implements 可以判断当前类型是否实现了某个接口: type Type interface { Align() int FieldAlign() int Method(int) Method MethodByName(string) (Method, bool) NumMethod() int ... Implements(u Type) bool ... } Go 反射包中 reflect.Value 的类型与 reflect.Type 不同,它被声明成了结构体。这个结构体没有对外暴露的字段,但是提供了获取或者写入数据的方法: type Value struct { // 包含过滤的或者未导出的字段 } func (v Value) Addr() Value func (v Value) Bool() bool func (v Value) Bytes() []byte ... Go 反射包中的所有方法基本都是围绕着 reflect.Type 和 reflect.Value 两个类型设计的。我们通过 reflect.TypeOf、reflect.ValueOf 可以将一个普通的变量转换成反射包中提供的 reflect.Type 和 reflect.Value,随后就可以使用反射包中的方法对它们进行复杂的操作。 4.3.1 三大法则 # 运行时反射是程序在运行期间检查其自身结构的一种方式。反射带来的灵活性是一把双刃剑,反射作为一种元编程方式可以减少重复代码2,但是过量的使用反射会使我们的程序逻辑变得难以理解并且运行缓慢。我们在这一节中会介绍 Go 语言反射的三大法则3,其中包括: 从 interface{} 变量可以反射出反射对象; 从反射对象可以获取 interface{} 变量; 要修改反射对象,其必须可设置; 第一法则 # 反射的第一法则是我们能将 Go 语言的 interface{} 变量转换成反射对象。很多读者可能会对这以法则产生困惑 — 为什么是从 interface{} 变量到反射对象?当我们执行 reflect.ValueOf(1) 时,虽然看起来是获取了基本类型 int 对应的反射类型,但是由于 reflect.TypeOf、reflect.ValueOf 两个方法的入参都是 interface{} 类型,所以在方法执行的过程中发生了类型转换。 因为 Go 语言的函数调用都是递的,所以变量会在函数调用时进行类型转换。基本类型 int 会转换成 interface{} 类型,这也就是为什么第一条法则是从接口到反射对象。 上面提到的 reflect.TypeOf 和 reflect.ValueOf 函数就能完成这里的转换,如果我们认为 Go 语言的类型和反射类型处于两个不同的世界,那么这两个函数就是连接这两个世界的桥梁。 golang-interface-to-reflection 图 4-16 接口到反射对象 我们可以通过以下例子简单介绍它们的作用,reflect.TypeOf 获取了变量 author 的类型,reflect.ValueOf 获取了变量的 draven。如果我们知道了一个变量的类型和,那么就意味着我们知道了这个变量的全部信息。 package main import ( "fmt" "reflect" ) func main() { author := "draven" fmt.Println("TypeOf author:", reflect.TypeOf(author)) fmt.Println("ValueOf author:", reflect.ValueOf(author)) } $ go run main.go TypeOf author: string ValueOf author: draven Go 有了变量的类型之后,我们可以通过 Method 方法获得类型实现的方法,通过 Field 获取类型包含的全部字段。对于不同的类型,我们也可以调用不同的方法获取相关信息: 结构体:获取字段的数量并通过下标和字段名获取字段 StructField; 哈希表:获取哈希表的 Key 类型; 函数或方法:获取入参和返回的类型; … 总而言之,使用 reflect.TypeOf 和 reflect.ValueOf 能够获取 Go 语言中的变量对应的反射对象。一旦获取了反射对象,我们就能得到跟当前类型相关数据和操作,并可以使用这些运行时获取的结构执行方法。 第二法则 # 反射的第二法则是我们可以从反射对象可以获取 interface{} 变量。既然能够将接口类型的变量转换成反射对象,那么一定需要其他方法将反射对象还原成接口类型的变量,reflect 中的 reflect.Value.Interface 就能完成这项工作: golang-reflection-to-interface 图 4-17 反射对象到接口 不过调用 reflect.Value.Interface 方法只能获得 interface{} 类型的变量,如果想要将其还原成最原始的状态还需要经过如下所示的显式类型转换: v := reflect.ValueOf(1) v.Interface().(int) Go 从反射对象到接口的过程是从接口到反射对象的镜面过程,两个过程都需要经历两次转换: 从接口到反射对象: 从基本类型到接口类型的类型转换; 从接口类型到反射对象的转换; 从反射对象到接口: 反射对象转换成接口类型; 通过显式类型转换变成原始类型; golang-bidirectional-reflection 图 4-18 接口和反射对象的双向转换 当然不是所有的变量都需要类型转换这一过程。如果变量本身就是 interface{} 类型的,那么它不需要类型转换,因为类型转换这一过程一般都是隐式的,所以我不太需要关心它,只有在我们需要将反射对象转换回基本类型时才需要显式的转换操作。 第三法则 # Go 语言反射的最后一条法则是与是否可以被更改有关,如果我们想要更新一个 reflect.Value,那么它持有的一定是可以被更新的,假设我们有以下代码: func main() { i := 1 v := reflect.ValueOf(i) v.SetInt(10) fmt.Println(i) } $ go run reflect.go panic: reflect: reflect.flag.mustBeAssignable using unaddressable value goroutine 1 [running]: reflect.flag.mustBeAssignableSlow(0x82, 0x1014c0) /usr/local/go/src/reflect/value.go:247 +0x180 reflect.flag.mustBeAssignable(...) /usr/local/go/src/reflect/value.go:234 reflect.Value.SetInt(0x100dc0, 0x414020, 0x82, 0x1840, 0xa, 0x0) /usr/local/go/src/reflect/value.go:1606 +0x40 main.main() /tmp/sandbox590309925/prog.go:11 +0xe0 Go 运行上述代码会导致程序崩溃并报出 “reflect: reflect.flag.mustBeAssignable using unaddressable value” 错误,仔细思考一下就能够发现出错的原因:由于 Go 语言的函数调用都是的,所以我们得到的反射对象跟最开始的变量没有任何关系,那么直接修改反射对象无法改变原始变量,程序为了防止错误就会崩溃。 想要修改原变量只能使用如下的方法: func main() { i := 1 v := reflect.ValueOf(&i) v.Elem().SetInt(10) fmt.Println(i) } $ go run reflect.go 10 Go 调用 reflect.ValueOf 获取变量指针; 调用 reflect.Value.Elem 获取指针指向的变量; 调用 reflect.Value.SetInt 更新变量的: 由于 Go 语言的函数调用都是递的,所以我们只能只能用迂回的方式改变原变量:先获取指针对应的 reflect.Value,再通过 reflect.Value.Elem 方法得到可以被设置的变量,我们可以通过下面的代码理解这个过程: func main() { i := 1 v := &i *v = 10 } Go 如果不能直接操作 i 变量修改其持有的,我们就只能获取 i 变量所在地址并使用 *v 修改所在地址中存储的整数。 4.3.2 类型和 # Go 语言的 interface{} 类型在语言内部是通过 reflect.emptyInterface 结体表示的,其中的 rtype 字段用于表示变量的类型,另一个 word 字段指向内部封装的数据: type emptyInterface struct { typ *rtype word unsafe.Pointer } Go 用于获取变量类型的 reflect.TypeOf 函数将入的变量隐式转换成 reflect.emptyInterface 类型并获取其中存储的类型信息 reflect.rtype: func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) } func toType(t *rtype) Type { if t == nil { return nil } return t } Go reflect.rtype 是一个实现了 reflect.Type 接口的结构体,该结构体实现的 reflect.rtype.String 方法可以帮助我们获取当前类型的名称: func (t *rtype) String() string { s := t.nameOff(t.str).name() if t.tflag&tflagExtraStar != 0 { return s[1:] } return s } Go reflect.TypeOf 的实现原理其实并不复杂,它只是将一个 interface{} 变量转换成了内部的 reflect.emptyInterface 表示,然后从中获取相应的类型信息。 用于获取接口 reflect.Value 的函数 reflect.ValueOf 实现也非常简单,在该函数中我们先调用了 reflect.escapes 保证当前逃逸到堆上,然后通过 reflect.unpackEface 从接口中获取 reflect.Value 结构体: func ValueOf(i interface{}) Value { if i == nil { return Value{} } escapes(i) return unpackEface(i) } func unpackEface(i interface{}) Value { e := (*emptyInterface)(unsafe.Pointer(&i)) t := e.typ if t == nil { return Value{} } f := flag(t.Kind()) if ifaceIndir(t) { f |= flagIndir } return Value{t, e.word, f} } Go reflect.unpackEface 会将入的接口转换成 reflect.emptyInterface,然后将具体类型和指针包装成 reflect.Value 结构体后返回。 reflect.TypeOf 和 reflect.ValueOf 的实现都很简单。我们已经分析了这两个函数的实现,现在需要了解编译器在调用函数之前做了哪些工作: package main import ( "reflect" ) func main() { i := 20 _ = reflect.TypeOf(i) } $ go build -gcflags="-S -N" main.go ... MOVQ $20, ""..autotmp_20+56(SP) // autotmp = 20 LEAQ type.int(SB), AX // AX = type.int(SB) MOVQ AX, ""..autotmp_19+280(SP) // autotmp_19+280(SP) = type.int(SB) LEAQ ""..autotmp_20+56(SP), CX // CX = 20 MOVQ CX, ""..autotmp_19+288(SP) // autotmp_19+288(SP) = 20 ... Go 从上面这段截取的汇编语言,我们可以发现在函数调用之前已经发生了类型转换,上述指令将 int 类型的变量转换成了占用 16 字节 autotmp_19+280(SP) ~ autotmp_19+288(SP) 的接口,两个 LEAQ 指令分别获取了类型的指针 type.int(SB) 以及变量 i 所在的地址。 当我们想要将一个变量转换成反射对象时,Go 语言会在编译期间完成类型转换,将变量的类型和转换成了 interface{} 并等待运行期间使用 reflect 包获取接口中存储的信息。 4.3.3 更新变量 # 当我们想要更新 reflect.Value 时,就需要调用 reflect.Value.Set 更新反射对象,该方法会调用 reflect.flag.mustBeAssignable 和 reflect.flag.mustBeExported 分别检查当前反射对象是否是可以被设置的以及字段是否是对外公开的: func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() var target unsafe.Pointer if v.kind() == Interface { target = v.ptr } x = x.assignTo("reflect.Set", v.typ, target) typedmemmove(v.typ, v.ptr, x.ptr) } Go reflect.Value.Set 会调用 reflect.Value.assignTo 并返回一个新的反射对象,这个返回的反射对象指针会直接覆盖原反射变量。 func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value { ... switch { case directlyAssignable(dst, v.typ): ... return Value{dst, v.ptr, fl} case implements(dst, v.typ): if v.Kind() == Interface && v.IsNil() { return Value{dst, nil, flag(Interface)} } x := valueInterface(v, false) if dst.NumMethod() == 0 { *(*interface{})(target) = x } else { ifaceE2I(dst, x, target) } return Value{dst, target, flagIndir | flag(Interface)} } panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) } Go reflect.Value.assignTo 会根据当前和被设置的反射对象类型创建一个新的 reflect.Value 结构体: 如果两个反射对象的类型是可以被直接替换,就会直接返回目标反射对象; 如果当前反射对象是接口并且目标对象实现了接口,就会把目标对象简单包装成接口; 在变量更新的过程中,reflect.Value.assignTo 返回的 reflect.Value 中的指针会覆盖当前反射对象中的指针实现变量的更新。 4.3.4 实现协议 # reflect 包还为我们提供了 reflect.rtype.Implements 方法可以用于判断某些类型是否遵循特定的接口。在 Go 语言中获取结构体的反射类型 reflect.Type 还是比较容易的,但是想要获得接口类型需要通过以下方式: reflect.TypeOf((*<interface>)(nil)).Elem() Go 我们通过一个例子在介绍如何判断一个类型是否实现了某个接口。假设我们需要判断如下代码中的 CustomError 是否实现了 Go 语言标准库中的 error 接口: type CustomError struct{} func (*CustomError) Error() string { return "" } func main() { typeOfError := reflect.TypeOf((*error)(nil)).Elem() customErrorPtr := reflect.TypeOf(&CustomError{}) customError := reflect.TypeOf(CustomError{}) fmt.Println(customErrorPtr.Implements(typeOfError)) // #=> true fmt.Println(customError.Implements(typeOfError)) // #=> false } Go 上述代码的运行结果正如我们在接口一节中介绍的: CustomError 类型并没有实现 error 接口; *CustomError 指针类型实现了 error 接口; 抛开上述的执行结果不谈,我们来分析一下 reflect.rtype.Implements 方法的工作原理: func (t *rtype) Implements(u Type) bool { if u == nil { panic("reflect: nil type passed to Type.Implements") } if u.Kind() != Interface { panic("reflect: non-interface type passed to Type.Implements") } return implements(u.(*rtype), t) } Go reflect.rtype.Implements 会检查入的类型是不是接口,如果不是接口或者是空就会直接崩溃并中止当前程序。在参数没有问题的情况下,上述方法会调用私有函数 reflect.implements 判断类型之间是否有实现关系: func implements(T, V *rtype) bool { t := (*interfaceType)(unsafe.Pointer(T)) if len(t.methods) == 0 { return true } ... v := V.uncommon() i := 0 vmethods := v.methods() for j := 0; j < int(v.mcount); j++ { tm := &t.methods[i] tmName := t.nameOff(tm.name) vm := vmethods[j] vmName := V.nameOff(vm.name) if vmName.name() == tmName.name() && V.typeOff(vm.mtyp) == t.typeOff(tm.typ) { if i++; i >= len(t.methods) { return true } } } return false } Go 如果接口中不包含任何方法,就意味着这是一个空的接口,任意类型都自动实现该接口,这时会直接返回 true。 golang-type-implements-interface 图 4-19 类型实现接口 在其他情况下,由于方法都是按照字母序存储的,reflect.implements 会维护两个用于遍历接口和类型方法的索引 i 和 j 判断类型是否实现了接口,因为最多只会进行 n 次比较(类型的方法数量),所以整个过程的时间复杂度是 𝑂(𝑛) 。 4.3.5 方法调用 # 作为一门静态语言,如果我们想要通过 reflect 包利用反射在运行期间执行方法不是一件容易的事情,下面的十几行代码就使用反射来执行 Add(0, 1) 函数: func Add(a, b int) int { return a + b } func main() { v := reflect.ValueOf(Add) if v.Kind() != reflect.Func { return } t := v.Type() argv := make([]reflect.Value, t.NumIn()) for i := range argv { if t.In(i).Kind() != reflect.Int { return } argv[i] = reflect.ValueOf(i) } result := v.Call(argv) if len(result) != 1 || result[0].Kind() != reflect.Int { return } fmt.Println(result[0].Int()) // #=> 1 } Go 通过 reflect.ValueOf 获取函数 Add 对应的反射对象; 调用 reflect.rtype.NumIn 获取函数的入参个数; 多次调用 reflect.ValueOf 函数逐一设置 argv 数组中的各个参数; 调用反射对象 Add 的 reflect.Value.Call 方法并入参数列表; 获取返回数组、验证数组的长度以及类型并打印其中的数据; 使用反射来调用方法非常复杂,原本只需要一行代码就能完成的工作,现在需要十几行代码才能完成,但这也是在静态语言中使用动态特性需要付出的成本。 func (v Value) Call(in []Value) []Value { v.mustBe(Func) v.mustBeExported() return v.call("Call", in) } Go reflect.Value.Call 是运行时调用方法的入口,它通过两个 MustBe 开头的方法确定了当前反射对象的类型是函数以及可见性,随后调用 reflect.Value.call 完成方法调用,这个私有方法的执行过程会分成以下的几个部分: 检查输入参数以及类型的合法性; 将入的 reflect.Value 参数数组设置到栈上; 通过函数指针和输入参数调用函数; 从栈上获取函数的返回; 我们将按照上面的顺序分析使用 reflect 进行函数调用的几个过程。 参数检查 # 参数检查是通过反射调用方法的第一步,在参数检查期间我们会从反射对象中取出当前的函数指针 unsafe.Pointer,如果该函数指针是方法,那么我们会通过 reflect.methodReceiver 获取方法的接收者和函数指针。 func (v Value) call(op string, in []Value) []Value { t := (*funcType)(unsafe.Pointer(v.typ)) ... if v.flag&flagMethod != 0 { rcvr = v rcvrtype, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) } else { ... } n := t.NumIn() if len(in) < n { panic("reflect: Call with too few input arguments") } if len(in) > n { panic("reflect: Call with too many input arguments") } for i := 0; i < n; i++ { if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) } } Go 上述方法还会检查入参数的个数以及参数的类型与函数签名中的类型是否可以匹配,任何参数的不匹配都会导致整个程序的崩溃中止。 准备参数 # 当我们已经对当前方法的参数完成验证后,就会进入函数调用的下一个阶段,为函数调用准备参数,在前面函数调用一节中,我们已经介绍过 Go 语言的函数调用惯例,函数或者方法在调用时,所有的参数都会被依次放到栈上。 nout := t.NumOut() frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype) var args unsafe.Pointer if nout == 0 { args = framePool.Get().(unsafe.Pointer) } else { args = unsafe_New(frametype) } off := uintptr(0) if rcvrtype != nil { storeRcvr(rcvr, args) off = ptrSize } for i, v := range in { targ := t.In(i).(*rtype) a := uintptr(targ.align) off = (off + a - 1) &^ (a - 1) n := targ.size ... addr := add(args, off, "n > 0") v = v.assignTo("reflect.Value.Call", targ, addr) *(*unsafe.Pointer)(addr) = v.ptr off += n } Go 通过 reflect.funcLayout 计算当前函数需要的参数和返回的栈布局,也就是每一个参数和返回所占的空间大小; 如果当前函数有返回,需要为当前函数的参数和返回分配一片内存空间 args; 如果当前函数是方法,需要向将方法的接收接收者者拷贝到 args 内存中; 将所有函数的参数按照顺序依次拷贝到对应 args 内存中 使用 reflect.funcLayout 返回的参数计算参数在内存中的位置; 将参数拷贝到内存空间中; 准备参数是计算各个参数和返回占用的内存空间并将所有的参数都拷贝内存空间对应位置的过程,该过程会考虑函数和方法、返回数量以及参数类型带来的差异。 调用函数 # 准备好调用函数需要的全部参数后,就会通过下面的代码执行函数指针了。我们会向该函数入栈类型、函数指针、参数和返回的内存空间、栈的大小以及返回的偏移量: call(frametype, fn, args, uint32(frametype.size), uint32(retOffset)) Go 上述函数实际上并不存在,它会在编译期间链接到 reflect.reflectcall 这个用汇编实现的函数上,我们在这里不会分析该函数的具体实现,感兴趣的读者可以自行了解其实现原理。 处理返回 # 当函数调用结束之后,就会开始处理函数的返回: 如果函数没有任何返回,会直接清空 args 中的全部内容来释放内存空间; 如果当前函数有返回; 将 args 中与输入参数有关的内存空间清空; 创建一个 nout 长度的切片用于保存由反射对象构成的返回数组; 从函数对象中获取返回的类型和内存大小,将 args 内存中的数据转换成 reflect.Value 类型并存储到切片中; var ret []Value if nout == 0 { typedmemclr(frametype, args) framePool.Put(args) } else { typedmemclrpartial(frametype, args, 0, retOffset) ret = make([]Value, nout) off = retOffset for i := 0; i < nout; i++ { tv := t.Out(i) a := uintptr(tv.Align()) off = (off + a - 1) &^ (a - 1) if tv.Size() != 0 { fl := flagIndir | flag(tv.Kind()) ret[i] = Value{tv.common(), add(args, off, "tv.Size() != 0"), fl} } else { ret[i] = Zero(tv) } off += tv.Size() } } return ret } Go 由 reflect.Value 构成的 ret 数组会被返回到调用方,到这里为止使用反射实现函数调用的过程就结束了。 4.3.6 小结 # Go 语言的 reflect 包为我们提供了多种能力,包括如何使用反射来动态修改变量、判断类型是否实现了某些接口以及动态调用方法等功能,通过分析反射包中方法的原理能帮助我们理解之前看起来比较怪异、令人困惑的现象。 带代码给我解释一下
07-04
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值