请在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))

`
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
by

评论者:alexmiller

与前版相比没有变化,只是更新后适用于1.7.0-RC2版本的主分支。

0
by

评论者:alexmiller

如果您有一个使用代理和gen-class测试此功能的草图,那将很有帮助。

0
by

评论者:abram

当然,您希望Sketch代码呈现什么格式?一个小型独立项目?单元测试?

0
by

评论者:alexmiller

这里在测试描述中有几行Java代码(一个带静态初始化器并打印的类)和Clojure代码(为gen-class和实现它的代理)可以用来证明这个问题。不应有任何对iOS或其他外部依赖项的依赖。

0
by

评论者:abram

已经添加了示例代码,请告诉我是否可以添加其他内容!

0
by

评论者:abram

出于好奇,这种做法进入1.8的几率有多大?

0
by

评论者:alexmiller

未知。

0

由 notespin 发布的评论:

我也被这个bug困扰。一个命名空间中的函数调用了在一个地方初始化的静态Java变量。另一个被genclassed的命名空间调用了这个函数。现在在编译时,静态Java变量被初始化,导致构建失败,因为那个静态Java初始化需要构建机器上不存在的资源。

0

由 michaelblume 发布的评论:

刷新补丁,以确保其应用于master,没有变化,保留归属权。

0

评论者:alexmiller

我在RT.loadClassForName()中的修改感觉困惑,但Compiler中的变化是调用RT.classForNameNonLoading()吗?这是补丁漂移还是怎么回事?

0
Sep 25, 2017

由 sonicsmooth 发布的评论:

感谢你发布这个补丁。static初始化器的问题使使用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: )))

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

$ 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
Sep 7, 2018

[email protected] 发布的评论:

我不太确定只是简单地替换{{classForNameNonLoading}}칭问能否解决问题。{{proxy}}在进行一些相当复杂的反思和类分析时,会在许多地方 touch 到静态代码。

另一个解决方案可以是使用一个代理函数——一个和 {{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 外部动态绑定,并在反引号代码内部可能在大括号内重新绑定以在单独的线程中访问

`
(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
...