2024 状态调查! 中分享您的想法。

欢迎!请查阅 关于 页面以获取更多关于如何使用本站的信息。

+1
编译器

我在一个使用 Clojure 和 RoboVM 的项目中工作。我们使用 AOT 编译将 Clojure 编译成 JVM 类,然后使用 RoboVM 将 JVM 类编译成本地代码。在我们的 Clojure 代码中,我们调用 RoboVM 提供的 Java API,这些 API 包装了原生 iOS API。

但我们发现了一个与继承和类级静态初始化代码相关的问题。许多 iOS API 需要继承自一个基对象并重写某些方法。目前,Clojure 会在编译时运行超类的静态初始化代码,无论使用 ":gen-class" 还是 "proxy" 来创建子类。然而,RoboVM 的基类 "ObjCObject"(链接:1),大多数特定的 iOS 类都继承自它,需要 iOS 运行时进行初始化,如果在设备上不运行则会抛出编译时的错误。

CLJ-1315 通过修改 "import" 以加载不运行静态初始化代码的类解决了类似问题。我编写了自己的补丁,它将此行为扩展到 ":gen-class" 和 "proxy"。单元测试通过,并且我们正在 iOS 应用中成功使用此代码。

补丁: clj-1743-2.patch

下面是一些可以用来演示当前行为的示例代码(完整演示项目在 https://github.com/figly/clojure-static-initialization

`
package clojure_static_initialization;

public class Demo {
static {

System.out.println("Running static initializers!");

}
public Demo () {
}
}
`

(ns clojure-static-initialization.gen-class-demo (:gen-class :extends clojure_static_initialization.Demo))

`
(ns clojure-static-initialization.proxy-demo)

(defn make-proxy []
(proxy [clojure_static_initialization.Demo] []))
`

(链接:1) https://github.com/robovm/robovm/blob/master/objc/src/main/java/org/robovm/objc/ObjCObject.java

14 答案

0

评论由:alexmiller 添加

与之前没有变化,只是更新到 1.7.0-RC2 的 master 版本。

0

评论由:alexmiller 添加

如果你有一张用代理和gen-class测试的草图,那将很有帮助。

0

评论人:abram

当然,您需要何种形式的草图代码?一个独立的迷你项目?单元测试?

0

评论由:alexmiller 添加

这里在测试说明中有几行Java代码(一个带有静态初始化器的类,该初始化器打印输出)和Clojure代码(用于扩展gen-class和proxy的代理),可以用来展示问题。不应依赖iOS或其他外部依赖。

0

评论人:abram

已添加示例代码,请告诉我我还可以添加什么!

0

评论人:abram

出于好奇,这种情况有可能会被收录到1.8版本中吗?

0

评论由:alexmiller 添加

未知。

0

评论人:notespin

我也受到了这个bug的影响。一个命名空间中的函数调用了一个在原地初始化的静态Java变量。另一个通过genclass初始化的命名空间调用该函数。现在在编译时,静态Java被初始化导致构建失败,因为静态Java初始化需要构建机器上不存在的资源。

0
by

评论者:michaelblume

刷新补丁以便应用到master中,没有更改,保持归属权利。

0
by

评论由:alexmiller 添加

我对在RT.loadClassForName()中使性更改的补丁感到困惑,但是编译器中的更改是调用RT.classForNameNonLoading()吗?这是补丁偏移还是怎么回事?

0
by

评论者:sonicsmooth

感谢您发布这个补丁。静态初始化器的问题使得难以同时进行AOT和交互式开发JavaFX。我今天克隆了Clojure 1.9.0-master源代码并应用了这个补丁,但示例Clojure项目还是显示"运行静态初始化器!"这已经通过我的实际用例得到了验证。如果首先启动JFXPanel,错误就会消失。截至2017年9月,有替代方案吗?例如,定义代理的另一种方式或延后到运行时?感谢。

$ lein clean;lein repl
编译1个源文件到C:\dev\clojure\clojure-static-initialization\target\classes
编译clojure-static-initialization.gen-class-demo
编译clojure-static-initialization.proxy-demo
运行静态初始化器!
Clojure 1.9.0-master-SNAPSHOT

user=> (def lcp (proxy (链接: javafx.scene.control.ListCell) (链接: )))

编译器异常java.lang.ExceptionInInitializerError,编译:(C:\dev\clojure\clojure-static-initialization\target\f31ee90298a1be447b450330204c3c0806c08b96-init.clj:1:10)

$ lein clean;lein repl
编译1个源文件到C:\dev\clojure\clojure-static-initialization\target\classes
编译clojure-static-initialization.gen-class-demo
编译clojure-static-initialization.proxy-demo
运行静态初始化器!
Clojure 1.9.0-master-SNAPSHOT

user=> (def jfxpanel (javafx.embed.swing.JFXPanel.))

'user/jfxpanel

user=> (def lcp (proxy (链接: javafx.scene.control.ListCell) (链接: )))

'user/lcp

0
by

评论者:[email protected]

我不太确定修复仅仅是将调用{{{classForNameNonLoading}}}的位置交换一下的问题。{{{proxy}}}进行了一些相当复杂的外部检查和类分析,这涉及到许多地方的静态代码。

另一种解决方案是有一个代理函数——一个像{{{proxy}}}那样但在运行时做的相同的代理。
我目前有一个解决JavaFX的ListCell问题的临时方案...

0

评论者:[email protected]

临时方案:将宏评估从编译时间延迟到运行时间。
在这种情况下,我假设您已经在一个函数中封装了您的代理调用,并且在运行时间是安全的(因为您已经初始化了JavaFX或其他内容)
1. 使用“反引号”来防止您的宏在编译时间进行评估。
2. 将您的反引号代码包裹在一个eval中

(defn make-thing [] (eval `(proxy ...

  1. 局部绑定和函数参数需要“gensym-ed” 1. .
  2. 隐式的{{this}}需要作为~'this访问

`
(defn make-thing []
(eval
`(proxy [ListCell][]

 (updateItem [item# empty?#]
   (proxy-super updateItem item# empty?#)            
     (.setText ~'this nil)
     ...

`

  1. 函数中传递的参数需要在eval外部进行动态绑定,也许在反引号代码内部的let中进行重新绑定以便在单独的线程中访问

`
(def ^:dynamic an-arg nil)

(defn make-thing [an-arg]
(binding [*an-arg* an-arg]

(eval
`(let [an-arg# *an-arg*]
  (proxy [ListCell][]
    (updateItem [item# empty?#]
      (proxy-super updateItem item# empty?#)            
       (.setText ~'this nil)
       (println "an-arg:" an-arg#)
       ...

`

这可以用宏来做吗?例如:{{proxyfn}}

0
参考信息:https://clojure.atlassian.net/browse/CLJ-1743(由Abram报告)
...