-
Notifications
You must be signed in to change notification settings - Fork 16
CLR Interop
CLR interop is essentially the same as JVM interop. All the basic functions for interop mentioned on http://clojure.org/java_interop work as advertised. This includes all of
- Member access
(.instanceMember instance args*)
(.instanceMember Classname args*)
(Classname/staticMethod args*)
Classname/staticField
- Dot special form
(. instance-expr member-symbol)
(. Classname-symbol member-symbol)
-
(. instance-expr (method-symbol args*))
or (. instance-expr method-symbol args*)
-
(. Classname-symbol (method-symbol args*))
or (. Classname-symbol method-symbol args*)
- Instantiation
(Classname. args*)
(new Classname args*)
- Assignment
(set! (. instance-expr instanceFieldName-symbol) expr)
(set! (. Classname-symbol staticFieldName-symbol) expr)
- Miscellaneous
(.. instance-expr member+)
(.. Classname-symbol member+)
(doto instance-expr (instanceMethodName-symbol args*)*)
(instance? Class expr)
(memfn method-name arg-names*)
Most of the array interop functions work.
The things not implemented yet:
bean
parse
Thanks to the magic of DLR, reflection is no longer as much of a performance hit. Any interop call that cannot be resolved at compile-time gets compiled into a call-site that supports caching of methods discovered at run-time. Thus, if we have an interop call (.m2 c x)
, and it is called with a @c@’s value being an instance of class C1
and a @x@’s value being a String
, the first call will identify the appropriate method to call and cache this information. A subsequent call with a C2
and a DateTime
will identify and cache a different method. The next call’s parameter values will be checked for matching C1/String
and C2/DateTime
. If either test succeeds, then the corresponding cached method will be called, thus avoiding reflection. Failure to match will cause reflection to be used.
Support has been added for ref
and out
parameters. The special syntactic forms refparam
and outparam
can wrap local variables in interop calls to indicate a parameter that is to be passed by reference. Upon completion of the call, the local variable will be rebound to the value ‘returned’ from the interop call.
For example, suppose you had the following class defined in C#:
public class dm.interop.C1
{
public int m3(int x) { return x; }
public int m3(ref int x) { x = x + 1; return x+20; }
public string m5(string x, ref int y) { y = y + 10; return x + y.ToString(); }
public int m5(int x, ref int y) { y = y + 100; return x+y; }
}
The following Clojure function, passed an instance of dm.interop.C1
and an Int32
will call the overload of m3
with the by-ref argument.
(defn f3r [c n]
(let [m (int n)]
(.m3 c (refparam m))
m))
Note that it is necessary to provide the type hint via (int n)
. Otherwise, the only argument to match would be a ref Object
. This example will use reflection and will match on a first argument of any type that has a method with signature m3(ref int)
. To avoide the reflection, you could type-hint the the variable c
.
To get the other overload, the interop call (.m3 c m)
will do the trick.
For method m5
, consider
(defn f5 [c x y]
(let [m (int y)
v (.m5 c x (refparam m))]
[v m]))
Then (f5 c1 "help" 12) => ["help22" 22]
and (f5 c1 15 20) => [135 120]
. In other words, because the m5
call is not resolved at compile-time, reflection will pick the correct overload at run-time. Note, however, that if there were an additional overload of m5 with signature m5(Object, ref Int32)
, then the call in f5
would resolve to it at compile-time.
The same mechanism works for new
expressions.
The syntactic form refparam
and outparam
can be used only at the top-level of interop calls, as shown. They can only wrap a local variable. Supplying a non-local variable argument or invoking someplace other than the top-level of an interop call will cause an exception to be thrown. Since there are no uninitialized local variables in Clojure, at this time refparam
and outparam
are identical.
Consider the following class:
namespace dm.interop
{
public class C6
{
public static int sm1(int x, params object[] ys)
{
return x + ys.Length;
}
public static int sm1(int x, params string[] ys)
{
int count = x;
foreach (String y in ys)
count += y.Length;
return count;
}
public static int m2(ref int x, params object[] ys)
{
x += ys.Length;
return ys.Length;
}
}
}
Consider calling sm1
. There are overloads with params
args of type object[]
and of type string[]
. The first overload can be invoked by
(dm.interop.C6/sm1 12 #^objects (into-array Object [1 2 3] ))
or
(dm.interop.C6/sm1 12 #^"System.Object[]" (into-array Object [1 2 3]))
The second overload can be invoked by
(dm.interop.C6/sm1 12 #^"System.String[]" (into-array String ["abc" "de" "f"]))
or
(dm.interop.C6/sm1 12 #^"System.String[]" (into-array ["abc" "de" "f"]))
Note that when a type name is given as a string in a type tag, it must be namespace-qualified.
For the combination of by ref and params
as given by m2
:
(defn c6m2 [x]
(let [n (int x)
v (dm.interop.C6/m2 (refparam n) #^objects (into-array Object [1 2 3 4]))]
[n v]))
The proxy
function is especially useful in JVM-land to create listeners and the like. In CLR-land, we need delegates. We have added a gen-delegate
macro to assist in creating delegates. Here is a sample use:
(.add_Click button
(gen-delegate EventHandler [sender args]
(let [c (Double/Parse (.Text tb)) ]
(.set_Text f-label (str (+ 32 (* 1.8 c)) " Fahrenheit")))))
See Completing CLR interop for some thoughts on the items below.
The JVM version can ignore generics. The CLR version cannot. We do not have a good way of referring to generics in the forms above that reference types. This is high priority item.
CLR supports true multi-dimensional arrays in addition to the ragged arrays that the JVM supports. Some extensions need to be defined.
The JVM works with class files found directories and JAR files on the classpath. We have substituted the environment variable clojure.load.path
as a mechanism for providing the set of directories to probe when looking for CLJ scripts and compiled-from-CLJ assemblies to load.
Not dealt with is the proper reference of types with fully-qualified assembly names. This is a high-priority item.
We need to extend gen-class and gen-interface to deal with CLR properties.