Scala Macros - 元编程 Metaprogramming with Def Macros
Scala Macros对scala函数库编程人员来说是一项不可或缺的编程工具,可以通过它来解决一些用普通编程或者类层次编程(type level programming)都无法解决的问题,这是因为Scala Macros可以直接对程序进行修改。Scala Macros的工作原理是在程序编译时按照编程人员的意旨对一段程序进行修改产生出一段新的程序。具体过程是:当编译器在对程序进行类型验证(typecheck)时如果发现Macro标记就会将这个Macro的功能实现程序(implementation):一个语法树(AST, Abstract Syntax Tree)结构拉过来在Macro的位置进行替代,然后从这个AST开始继续进行类型验证过程。
下面我们先用个简单的例子来示范分析一下Def Macros的基本原理和使用方法:
object modules {
greeting("john")
} object mmacros {
def greeting(person: String): Unit = macro greetingMacro
def greetingMacro(c: Context)(person: c.Expr[String]): c.Expr[Unit] = ...
}
以上是Def Macros的标准实现模式。基本原理是这样的:当编译器在编译modules遇到方法调用greeting("john")时会进行函数符号解析、在mmacros里发现greeting是个macro,它的具体实现在greetingMacro函数里,此时编译器会运行greetingMacro函数并将运算结果-一个AST调用替代表达式greeting("john")。注意编译器在运算greetingMacro时会以AST方式将参数person传入。由于在编译modules对象时需要运算greetingMacro函数,所以greetingMacro函数乃至整个mmacros对象必须是已编译状态,这就意味着modules和mmacros必须分别在不同的源代码文件里,而且还要确保在编译modules前先完成对mmacros的编译,我们可以从sbt设置文件build.sbt看到它们的关系:
name := "learn-macro" version := "1.0.1" val commonSettings = Seq(
scalaVersion := "2.11.8",
scalacOptions ++= Seq("-deprecation", "-feature"),
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1",
"org.specs2" %% "specs2" % "2.3.12" % "test",
"org.scalatest" % "scalatest_2.11" % "2.2.1" % "test"
),
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
) lazy val root = (project in file(".")).aggregate(macros, demos) lazy val macros = project.in(file("macros")).settings(commonSettings : _*) lazy val demos = project.in(file("demos")).settings(commonSettings : _*).dependsOn(macros)
注意最后一行:demos dependsOn(macros),因为我们会把所有macros定义文件放在macros目录下。
下面我们来看看macro的具体实现方法:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
import java.util.Date
object LibraryMacros {
def greeting(person: String): Unit = macro greetingMacro def greetingMacro(c: Context)(person: c.Expr[String]): c.Expr[Unit] = {
import c.universe._
println("compiling greeting ...")
val now = reify {new Date().toString}
reify {
println("Hello " + person.splice + ", the time is: " + new Date().toString)
}
}
}
以上是macro greeting的具体声明和实现。代码放在macros目录下的MacrosLibrary.scala里。首先必须import macros和Context。
macro调用在demo目录下的HelloMacro.scala里:
object HelloMacro extends App {
import LibraryMacros._
greeting("john")
}
注意在编译HelloMacro.scala时产生的输出:
Mac-Pro:learn-macro tiger-macpro$ sbt
[info] Loading global plugins from /Users/tiger-macpro/.sbt/0.13/plugins
[info] Loading project definition from /Users/tiger-macpro/Scala/IntelliJ/learn-macro/project
[info] Set current project to learn-macro (in build file:/Users/tiger-macpro/Scala/IntelliJ/learn-macro/)
> project demos
[info] Set current project to demos (in build file:/Users/tiger-macpro/Scala/IntelliJ/learn-macro/)
> compile
[info] Compiling Scala source to /Users/tiger-macpro/Scala/IntelliJ/learn-macro/macros/target/scala-2.11/classes...
[info] 'compiler-interface' not yet compiled for Scala 2.11.. Compiling...
[info] Compilation completed in 7.876 s
[info] Compiling Scala source to /Users/tiger-macpro/Scala/IntelliJ/learn-macro/demos/target/scala-2.11/classes...
compiling greeting ...
[success] Total time: s, completed -- ::
>
从compiling greeting ...这条提示我们可以得出在编译demo目录下源代码文件的过程中应该运算了greetingMacro函数。测试运行后产生结果:
Hello john, the time is: Wed Nov :: HKT Process finished with exit code
运算greeting实际上是调用了greetingMacro中的macro实现代码。上面这个例子使用了最基础的Scala Macro编程模式。注意这个例子里函数greetingMacro的参数c: Context和在函数内部代码中reify,splice的调用:由于Context是个动态函数接口,每个实例都有所不同。对于大型的macro实现函数,可能会调用到其它同样会使用到Context的辅助函数(helper function),容易出现Context实例不匹配问题。另外reify和splice可以说是最原始的AST操作函数。我们在下面这个例子里使用了最新的模式和方法:
def tell(person: String): Unit = macro MacrosImpls.tellMacro
class MacrosImpls(val c: Context) {
import c.universe._
def tellMacro(person: c.Tree): c.Tree = {
println("compiling tell ...")
val now = new Date().toString
q"""
println("Hello "+$person+", it is: "+$now)
"""
}
}
在这个例子里我们把macro实现函数放入一个以Context为参数的class。我们可以把所有使用Context的函数都摆在这个class里面大家共用统一的Context实例。quasiquotes是最新的AST操作函数集,可以更方便灵活地控制AST的产生、表达式还原等。这个tell macro的调用还是一样的:
object HelloMacro extends App {
import LibraryMacros._
greeting("john")
tell("mary")
}
测试运算产生下面的结果:
Hello john, the time is: Wed Nov :: HKT
Hello mary, it is: Wed Nov :: HKT Process finished with exit code
Def Macros的Macro实现函数可以是泛型函数,支持类参数。在下面的例子我们示范如何用Def Macros来实现通用的case class与Map类型的转换。假设我们有个转换器CaseClassMapConverter[C],那么C类型可以是任何case class,所以这个转换器是泛型的,那么macro实现函数也就必须是泛型的了。大体来说,我们希望实现以下功能:把任何case class转成Map:
def ccToMap[C: CaseClassMapConverter](c: C): Map[String,Any] =
implicitly[CaseClassMapConverter[C]].toMap(c) case class Person(name: String, age: Int)
case class Car(make: String, year: Int, manu: String) val civic = Car("Civic",,"Honda")
println(ccToMap[Person](Person("john",)))
println(ccToMap[Car](civic)) ...
Map(name -> john, age -> )
Map(make -> Civic, year -> , manu -> Honda)
反向把Map转成case class:
def mapTocc[C: CaseClassMapConverter](m: Map[String,Any]) =
implicitly[CaseClassMapConverter[C]].fromMap(m) val mapJohn = ccToMap[Person](Person("john",))
val mapCivic = ccToMap[Car](civic)
println(mapTocc[Person](mapJohn))
println(mapTocc[Car](mapCivic)) ...
Person(john,)
Car(Civic,,Honda)
我们来看看这个Macro的实现函数:macros/CaseClassConverter.scala
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context trait CaseClassMapConverter[C] {
def toMap(c: C): Map[String,Any]
def fromMap(m: Map[String,Any]): C
}
object CaseClassMapConverter {
implicit def Materializer[C]: CaseClassMapConverter[C] = macro converterMacro[C]
def converterMacro[C: c.WeakTypeTag](c: Context): c.Tree = {
import c.universe._ val tpe = weakTypeOf[C]
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramLists.head val companion = tpe.typeSymbol.companion
val (toParams,fromParams) = fields.map { field =>
val name = field.name.toTermName
val decoded = name.decodedName.toString
val rtype = tpe.decl(name).typeSignature (q"$decoded -> t.$name", q"map($decoded).asInstanceOf[$rtype]") }.unzip q"""
new CaseClassMapConverter[$tpe] {
def toMap(t: $tpe): Map[String,Any] = Map(..$toParams)
def fromMap(map: Map[String,Any]): $tpe = $companion(..$fromParams)
}
"""
}
}
首先,trait CaseClassMapConverter[C]是个typeclass,代表了C类型数据的行为函数toMap和fromMap。我们同时可以看到Macro定义implicit def Materializer[C]是隐式的,而且是泛型的,运算结果类型是CaseClassMapConverter[C]。从这个可以推断出这个Macro定义通过Macro实现函数可以产生CaseClassMapConverter[C]实例,C可以是任何case class类型。在函数ccToMap和mapTocc函数需要的隐式参数CaseClassMapConverter[C]实例就是由这个Macro实现函数提供的。注意我们只能用WeakTypeTag来获取类型参数C的信息。在使用quasiquotes时我们一般是在q括号中放入原始代码。在q括号内调用AST变量用$前缀(称为unquote)。对类型tpe的操作可以参考scala.reflect api。示范调用代码在demo目录下的ConverterDemo.scala里:
import CaseClassMapConverter._
object ConvertDemo extends App { def ccToMap[C: CaseClassMapConverter](c: C): Map[String,Any] =
implicitly[CaseClassMapConverter[C]].toMap(c) case class Person(name: String, age: Int)
case class Car(make: String, year: Int, manu: String) val civic = Car("Civic",,"Honda")
//println(ccToMap[Person](Person("john",18)))
//println(ccToMap[Car](civic)) def mapTocc[C: CaseClassMapConverter](m: Map[String,Any]) =
implicitly[CaseClassMapConverter[C]].fromMap(m) val mapJohn = ccToMap[Person](Person("john",))
val mapCivic = ccToMap[Car](civic)
println(mapTocc[Person](mapJohn))
println(mapTocc[Car](mapCivic)) }
在上面这个implicit Macros例子里引用了一些quasiquote语句(q"xxx")。quasiquote是Scala Macros的一个重要部分,主要替代了原来reflect api中的reify功能,具备更强大、方便灵活的处理AST功能。Scala Def Macros还提供了Extractor Macros,结合Scala String Interpolation和模式匹配来提供compile time的extractor object生成。Extractor Macros的具体用例如下:
import ExtractorMicros._
val fname = "William"
val lname = "Wang"
val someuser = usr"$fname,$lname" //new FreeUser("William","Wang") someuser match {
case usr"$first,$last" => println(s"hello $first $last")
}
在上面这个例子里usr"???"的usr是一个pattern和extractor object。与通常的string interpolation不同的是usr不是一个方法(method),而是一个对象(object)。这是由于模式匹配中的unapply必须在一个extractor object内,所以usr是个object。我们知道一个object加上它的apply可以当作method来调用。也就是说如果在usr object中实现了apply就可以用usr(???)作为method使用,如下:
implicit class UserInterpolate(sc: StringContext) {
object usr {
def apply(args: String*): Any = macro UserMacros.appl
def unapply(u: User): Any = macro UserMacros.uapl
}
}
通过Def Macros在编译过程中自动生成apply和unapply,它们分别对应了函数调用:
val someuser = usr"$fname,$lname" case usr"$first,$last" => println(s"hello $first $last")
下面是macro appl的实现:
def appl(c: Context)(args: c.Tree*) = {
import c.universe._
val arglist = args.toList
q"new FreeUser(..$arglist)"
}
主要通过q"new FreeUser(arg1,arg2)"实现了个AST的构建。macro uapl的实现相对复杂些,对quasiquote的应用会更深入些。首先要确定类型的primary constructor的参数数量和名称,然后通过quasiquote的模式匹配分解出相应的sub-AST,再重新组合形成最终完整的AST:
def uapl(c: Context)(u: c.Tree) = {
import c.universe._
val params = u.tpe.members.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m.asMethod
}.get.paramLists.head.map {p => p.asTerm.name.toString} val (qget,qdef) = params.length match {
case len if len == =>
(List(q""),List(q""))
case len if len == =>
val pn = TermName(params.head)
(List(q"def get = u.$pn"),List(q""))
case _ =>
val defs = List(q"def _1 = x",q"def _2 = x",q"def _3 = x",q"def _4 = x")
val qdefs = (params zip defs).collect {
case (p,d) =>
val q"def $mname = $mbody" = d
val pn = TermName(p)
q"def $mname = u.$pn"
}
(List(q"def get = this"),qdefs)
} q"""
new {
class Matcher(u: User) {
def isEmpty = false
..$qget
..$qdef
}
def unapply(u: User) = new Matcher(u)
}.unapply($u)
"""
}
}
前面大部分代码就是为了形成List[Tree] qget和qdef,最后组合一个完整的quasiquote q""" new {...}"""。
完整的Macro实现源代码如下:
trait User {
val fname: String
val lname: String
} class FreeUser(val fname: String, val lname: String) extends User {
val i =
def f = +
}
class PremiumUser(val name: String, val gender: Char, val vipnum: String) //extends User object ExtractorMicros {
implicit class UserInterpolate(sc: StringContext) {
object usr {
def apply(args: String*): Any = macro UserMacros.appl
def unapply(u: User): Any = macro UserMacros.uapl
}
}
}
object UserMacros {
def appl(c: Context)(args: c.Tree*) = {
import c.universe._
val arglist = args.toList
q"new FreeUser(..$arglist)"
}
def uapl(c: Context)(u: c.Tree) = {
import c.universe._
val params = u.tpe.members.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m.asMethod
}.get.paramLists.head.map {p => p.asTerm.name.toString} val (qget,qdef) = params.length match {
case len if len == =>
(List(q""),List(q""))
case len if len == =>
val pn = TermName(params.head)
(List(q"def get = u.$pn"),List(q""))
case _ =>
val defs = List(q"def _1 = x",q"def _2 = x",q"def _3 = x",q"def _4 = x")
val qdefs = (params zip defs).collect {
case (p,d) =>
val q"def $mname = $mbody" = d
val pn = TermName(p)
q"def $mname = u.$pn"
}
(List(q"def get = this"),qdefs)
} q"""
new {
class Matcher(u: User) {
def isEmpty = false
..$qget
..$qdef
}
def unapply(u: User) = new Matcher(u)
}.unapply($u)
"""
}
}
调用示范代码:
object Test extends App {
import ExtractorMicros._
val fname = "William"
val lname = "Wang"
val someuser = usr"$fname,$lname" //new FreeUser("William","Wang") someuser match {
case usr"$first,$last" => println(s"hello $first $last")
}
}
Macros Annotation(注释)是Def Macro重要的功能部分。对一个目标,包括类型、对象、方法等进行注释意思是在源代码编译时对它们进行拓展修改甚至完全替换。比如我们下面展示的方法注释(method annotation):假设我们有下面两个方法:
def testMethod[T]: Double = {
val x = 2.0 + 2.0
Math.pow(x, x)
} def testMethodWithArgs(x: Double, y: Double) = {
val z = x + y
Math.pow(z,z)
}
如果我想测试它们运行所需时间的话可以在这两个方法的内部代码前设定开始时间,然后在代码后截取完成时间,完成时间-开始时间就是运算所需要的时间了,如下:
def testMethod[T]: Double = {
val start = System.nanoTime() val x = 2.0 + 2.0
Math.pow(x, x) val end = System.nanoTime()
println(s"elapsed time is: ${end - start}")
}
我们希望通过注释来拓展这个方法:具体做法是保留原来的代码,同时在方法内部前后增加几行代码。我们看看这个注释的目的是如何实现的:
def impl(c: Context)(annottees: c.Tree*): c.Tree = {
import c.universe._ annottees.head match {
case q"$mods def $mname[..$tpes](...$args): $rettpe = { ..$stats }" => {
q"""
$mods def $mname[..$tpes](...$args): $rettpe = {
val start = System.nanoTime()
val result = {..$stats}
val end = System.nanoTime()
println(${mname.toString} + " elapsed time in nano second = " + (end-start).toString())
result
}
"""
}
case _ => c.abort(c.enclosingPosition, "Incorrect method signature!")
}
可以看到:我们还是用quasiquote来分拆被注释的方法,然后再用quasiquote重现组合这个方法。在重组过程中增加了时间截取和列印代码。下面这行是典型的AST模式分拆(pattern desctruction):
case q"$mods def $mname[..$tpes](...$args): $rettpe = { ..$stats }" => {...}
用这种方式把目标分拆成重组需要的最基本部分。Macro Annotation的实现源代码如下:
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context class Benchmark extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro Benchmark.impl
}
object Benchmark {
def impl(c: Context)(annottees: c.Tree*): c.Tree = {
import c.universe._ annottees.head match {
case q"$mods def $mname[..$tpes](...$args): $rettpe = { ..$stats }" => {
q"""
$mods def $mname[..$tpes](...$args): $rettpe = {
val start = System.nanoTime()
val result = {..$stats}
val end = System.nanoTime()
println(${mname.toString} + " elapsed time in nano second = " + (end-start).toString())
result
}
"""
}
case _ => c.abort(c.enclosingPosition, "Incorrect method signature!")
} }
}
注释的调用示范代码如下:
object annotMethodDemo extends App { @Benchmark
def testMethod[T]: Double = {
//val start = System.nanoTime() val x = 2.0 + 2.0
Math.pow(x, x) //val end = System.nanoTime()
//println(s"elapsed time is: ${end - start}")
}
@Benchmark
def testMethodWithArgs(x: Double, y: Double) = {
val z = x + y
Math.pow(z,z)
} testMethod[String]
testMethodWithArgs(2.0,3.0) }
有一点值得注意的是:Macro扩展是编译中遇到方法调用时发生的,而注释目标的扩展则在更早一步的方法声明时。我们下面再看注释class的例子:
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context class TalkingAnimal(val voice: String) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro TalkingAnimal.implAnnot
} object TalkingAnimal {
def implAnnot(c: Context)(annottees: c.Tree*): c.Tree = {
import c.universe._ annottees.head match {
case q"$mods class $cname[..$tparams] $ctorMods(..$params) extends Animal with ..$parents {$self => ..$stats}" =>
val voice = c.prefix.tree match {
case q"new TalkingAnimal($sound)" => c.eval[String](c.Expr(sound))
case _ =>
c.abort(c.enclosingPosition,
"TalkingAnimal must provide voice sample!")
}
val animalType = cname.toString()
q"""
$mods class $cname(..$params) extends Animal {
..$stats
def sayHello: Unit =
println("Hello, I'm a " + $animalType + " and my name is " + name + " " + $voice + "...")
}
"""
case _ =>
c.abort(c.enclosingPosition,
"Annotation TalkingAnimal only apply to Animal inherited!")
}
}
}
我们看到:同样还是通过quasiquote进行AST模式拆分:
case q"$mods class $cname[..$tparams] $ctorMods(..$params) extends Animal with ..$parents {$self => ..$stats}" =>
然后再重新组合。具体使用示范如下:
object AnnotClassDemo extends App {
trait Animal {
val name: String
}
@TalkingAnimal("wangwang")
case class Dog(val name: String) extends Animal @TalkingAnimal("miaomiao")
case class Cat(val name: String) extends Animal //@TalkingAnimal("")
//case class Carrot(val name: String)
//Error:(12,2) Annotation TalkingAnimal only apply to Animal inherited! @TalingAnimal
Dog("Goldy").sayHello
Cat("Kitty").sayHello }
运算结果如下:
Hello, I'm a Dog and my name is Goldy wangwang...
Hello, I'm a Cat and my name is Kitty miaomiao... Process finished with exit code
Scala Macros - 元编程 Metaprogramming with Def Macros的更多相关文章
- 元编程 (meta-programming)
元编程 (meta-programming) 术语 meta:英语前缀词根,来源于希腊文.中国大陆一般翻译成"元". 在逻辑学中,可以理解为:关于X的更高层次,同时,这个更高层次的 ...
- Python的元编程案例
Python的元编程案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是元编程 元编程概念来自LISP和smalltalk. 我们写程序是直接写代码,是否能够用代码来生成 ...
- C++ 元编程 —— 让编译器帮你写程序
目录 1 C++ 中的元编程 1.1 什么是元编程 1.2 元编程在 C++ 中的位置 1.3 C++ 元编程的历史 2 元编程的语言支持 2.1 C++ 中的模板类型 2.2 C++ 中的模板参数 ...
- C++模板元编程(C++ template metaprogramming)
实验平台:Win7,VS2013 Community,GCC 4.8.3(在线版) 所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得 ...
- .Net元编程【Metaprogramming in NET】 序-翻译
最近在看这本书,比较实用.抽点时间把公开的部分内容简单的翻译了一下,下文是序部分. 书的具体地址为: http://www.amazon.cn/Metaprogramming-in-NET-Hazza ...
- 模板元编程(Template metaprogramming)
https://en.wikipedia.org/wiki/Template_metaprogramming 没看懂...只知道了模板元编程的代码是在编译期运行的... 敲了2个例子: 1. #inc ...
- AutoSharedLibrary -- 基于模板元编程技术的跨平台C++动态链接载入库
基于模板元编程技术的跨平台C++动态链接载入库.通过模板技术,使用者仅需通过简单的宏,就可以使编译器在编译期自己主动生成载入动态链接库导出符号的代码,无不论什么额外的执行时开销. extern &qu ...
- 翻译 - 元编程动态方法之public_send
李哲 - MAY 20, 2015 原文地址:Metaprogramming Dynamic Methods: Using Public_send 作者:Friends of The Web的开发者V ...
- Ruby元编程:单元测试框架如何找到测试用例
前几天看了Google Testing Blog上的一篇文章讲到C++因为没有反射机制,所以如何注册测试用例就成了一件需要各显神通的事情.从我的经验来看,无论是Google的GTest还是微软的LTM ...
随机推荐
- input type='file'上传控件假样式
采用bootstrap框架样式 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> &l ...
- 多线程条件通行工具——CountDownLatch
CountDownLatch的作用是,线程进入等待后,需要计数器达到0才能通行. CountDownLatch(int)构造方法,指定初始计数. await()等待计数减至0. await(long, ...
- java常用的设计模式
设计模式:一个程序员对设计模式的理解:"不懂"为什么要把很简单的东西搞得那么复杂.后来随着软件开发经验的增加才开始明白我所看到的"复杂"恰恰就是设计模式的精髓所 ...
- Atitit.研发管理如何避免公司破产倒闭的业务魔咒
Atitit.如何避免公司破产倒闭的业务魔咒 1. 大型公司的衰落或者倒闭破产案例1 1.1. 摩托罗拉1 1.2. 诺基亚2 1.3. sun2 2. 为什么他们会倒闭?? 常见的一些倒闭元素2 2 ...
- Xamarin技术文档------VS多平台开发
此技术业余时间研究,仅供大家学习参考,不涉及深入研究,有一定开发基础的人员,应该都能较快上手. 一.简介 Xamarin始创于2011年,旨在使移动开发变得难以置信地迅捷和简单.Xamarin的产品简 ...
- 深入理解IIS的多线程工作机制
首先让我们来看看IIS里面的这2个数字:最大并发连接数,队列长度.先说这2个数字在哪里看. 最大并发连接数:在IIS中选中一个网站,右键网站名称,在右键菜单中找到并点击[管理网站]->[高级设置 ...
- qt5中信号和槽的新语法
qt5中的连接 有下列几种方式可以连接到信号上 旧语法 qt5将继续支持旧的语法去连接,在QObject对象上定义信号和槽函数,及任何继承QObjec的对象(包含QWidget). connect(s ...
- ABP源码分析四十二:ZERO的身份认证
ABP Zero模块通过自定义实现Asp.Net Identity完成身份认证功能, 对Asp.Net Identity做了较大幅度的扩展.同时重写了ABP核心模块中的permission功能,以实现 ...
- C++11 shared_ptr 智能指针 的使用,避免内存泄露
多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...
- 【Win 10 应用开发】三维变换
所谓三维变换,其实是在二维平面上产生三维的视觉效果.前面老周简单提了一下透视效果,如果透视效果不能满需求,那可以考虑用三维变换. UIElement类有一个属性叫Transform3D,它定义的类型为 ...