请在 2024 Clojure 状况调查! 分享您的想法。

欢迎!请查看 关于 页面以了解更多此处的信息。

+1
编译器

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

但我们发现继承和类级别的静态初始化代码存在一个问题。许多 iOS APIs 需要继承自一个基类并重写某些方法。目前,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 的 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

我也受到了这个问题的困扰。一个命名空间中的函数调用了初始化在当前位置的静态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 (link: javafx.scene.control.ListCell) (link: )))

CompilerException 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 (link: javafx.scene.control.ListCell) (link: )))

'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” 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
...