Skip to content
dmiller edited this page Sep 13, 2010 · 12 revisions

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

Reflection

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.

ref and out parameters

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.

params arguments

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

gen-delegate

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

Areas needing work

See Completing CLR interop for some thoughts on the items below.

Generics

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.

Multi-dimensional arrays

CLR supports true multi-dimensional arrays in addition to the ragged arrays that the JVM supports. Some extensions need to be defined.

Assemblies

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.

Properties in gen-class and gen-interface

We need to extend gen-class and gen-interface to deal with CLR properties.