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

欢迎!请参阅 关于 页面以获取更多有关此信息的工作情况。

+1
编译器

我正在使用 Clojure 和 RoboVM 进行一个项目。我们使用 AOT 编译将 Clojure 编译成 JVM 类,然后使用 RoboVM 将 JVM 类编译成原生代码。在我们的 Clojure 代码中,我们调用 RoboVM 提供的 Java 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))

`
包 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版本的主分支。

0
by

评论者:alexmiller

如果你有一个包含代理和gen-class测试的草图,那将是有帮助的。

0
by

评论者:abram

当然,您希望草图代码以什么形式?一个小型独立项目?单元测试?

0
by

评论者:alexmiller

在测试描述中这里有一小段Java代码(包含静态初始化并打印的类)以及Clojure代码(用于扩展它的gen-class和proxy),可以用来展示问题。不应对iOS或其他外部依赖有任何依赖。

0
by

评论者:abram

已添加示例代码,请告知是否还需要添加其他内容!

0
by

评论者:abram

出于好奇,这种可能被纳入1.8的机会有多大?

0
by

评论者:alexmiller

未知。

0
by

评论者:notespin

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

0

评论人:michaelblume

刷新补丁以应用 master 版,没有更改,保持归属。

0

评论者:alexmiller

我对补丁在 RT.loadClassForName() 中所做的更改感到困惑,但 Compiler 中的更改是调用 RT.classForNameNonLoading() 吗?这是补丁的偏差还是怎么回事?

0

评论人:sonicsmooth

感谢您发布这个补丁。关于静态初始化器的问题一直使得在 AOT 和交互式开发下使用 JavaFX 变得困难。我今天克隆了 Clojure 1.9.0-master 源代码并应用了补丁,但示例 Clojure 项目仍然显示 "Running static initializers!" 我通过我的实际使用场景进行了验证。如果先启动一个 JFXPanel,错误就会消失。截至 2017 年 9 月,有没有什么解决方案,比如另一种定义代理或延迟到运行时的方式?谢谢。

$ lein clean;leon 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 (link: javafx.scene.control.ListCell) (link: )))

CompilerException java.lang.ExceptionInInitializerError, compiling:(C:\dev\clojure\clojure-static-initialization\target\f31ee90298a1be447b450330204c3c0806c08b96-init.clj:1:10)

$ lein clean;leon 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 (link: javafx.scene.control.ListCell) (link: )))

'user/lcp

0

评论人:[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
...