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

欢迎!有关如何使用本站的信息,请参阅关于页面。

+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))

`
包 clojure_static_initialization;

public class Demo {
静态{

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变量,该变量在原地初始化。另一个genclassed的命名空间调用该函数。现在在编译时,static 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: )))

编译器异常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投票
by

评论者:[email protected]

解决方法:将宏评估从编译时延迟到运行时。
在这种情况下,我假设您已经将代理调用封装在一个函数中,并且在运行时做这件事是安全的(因为您已经初始化了JavaFX或类似的东西)
1. 使用反引号防止您的宏在编译时评估。
2. 将您的反引号代码包裹在eval中

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

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

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

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

`

  1. 函数传递的参数需要在外部动态绑定,可能还需在反引号代码中的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投票
...