Thursday, July 12, 2012

ClojureCLR with WPF - REPL and Compile

In order to run a ClojureCLR script with a non blocking WPF UI we have the two basic options
  1. in a REPL - being able to modify the window
  2. as console app executable
The solution to both scenarios are not quite obvious if you're not familiar with WPF or the threading models. For WPF and COMInterop the threading model has to be a Single Threaded Apartment. My first thought was to use the documented STAThreadAttribute which the .NET compilers are using. In ClojureCLR .NET attributes can be applied with the meta data syntax ^{}. Either Clojure.Compile.exe isn't aware of the STAThread feature at the moment or I haven't found the option yet. Have to digg into the code later. My first try to apply it to the -main function failed.

Kick starting my approach from Marius Kjehldahl's ClojureCLR post on running a WPF Window in a REPL you'll get option 1. I've shamelessly copied his solution and created my own quick workaround where a new Thread is spawn setting the AppartmentState to STA with the ability to load a .xaml.

For option 1, I created two gists from hist post for the REPL solution with some minor modifications:

For option 2 have a look at the following gist for compiling instructions and some background information regarding the threading model.

; ClojureCLR 1.4
; to compile the wpfapp.clj into an executable run: "Clojure.Compile.exe wpfapp"
; see also: http://clojureclr.blogspot.de/2012/01/compiling-and-loading-in-clojureclr.html
; be aware of the namespace and the filename! atm it should stick to exactly the same name
(assembly-load-with-partial-name "PresentationFramework")
(assembly-load-with-partial-name "PresentationCore")
(assembly-load-with-partial-name "WindowsBase")
(assembly-load-with-partial-name "System.Xml")
(ns wpfapp
(:import (System.Windows Application Window))
(:import (System.Windows.Threading Dispatcher DispatcherPriority))
(:import (System.Threading ApartmentState ThreadStart Thread AutoResetEvent))
(:import (System.IO File))
(:import (System.Xml XmlReader))
(:import (System.Windows.Markup XamlReader))
(:import (System))
(:gen-class))
(defn run[& args]
(let [app (new Application)
win (-> "MyWpfUI.xaml"
File/OpenText
XmlReader/Create
XamlReader/Load)]
(pr "running app!")
(.Run app win)))
; STAThreadAttribute should instrcut the compiler to run the main thread
; as STA. WPF and COM interop needs STA. For further details read
; STAThreadAttribute: http://msdn.microsoft.com/en-us/library/ms680112(v=vs.85).aspx
; STA: http://msdn.microsoft.com/en-us/library/ms680112(v=vs.85).aspx
; For Clojure.Compile.exe it doesn't work with gen-class and -main.
; therefore I'm creating a new thread setting it to STA as workaround
; atm we're only able to run a console app!
; To be a first class .NET citizen the Clojure.Compile.exe
; should have options like C#:
; - Console Application
; - Class Library
; - Windows Application
(defn sta-thread [func]
(let [delegate (gen-delegate ThreadStart [] (func))
thread (doto (new Thread delegate)
(.SetApartmentState ApartmentState/STA)
(.Start))]
thread))
(defn -main [& args]
(let [uithread (sta-thread (partial run args))]))
view raw wpfapp.clj hosted with ❤ by GitHub

UPDATE: Here is a quickfix for the Clojure.Compile.exe allways appending the STAThread attribute to the  main executable. There is the generation from the clojure source file left.

No comments:

Post a Comment