Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing IEnumerable Variables into .NET from ColdFusion

I'm working on doing some ColdFusion 10 integration with a custom .NET DLL that I don't have the ability to tweak. During this process, I've been able to do everything that I need to do other than create an IEnumerable data type to pass to one of the methods of the object. Here's what I need to integration with:

enter image description here

It's the Set_Events method that I'm having the trouble with. I can create the individual Events that are supposed to be part of that enum variable, but I'm unable to create what it apparently is expecting as the proper variable type. I've tried doing this:

<cfset enum = createObject(".net", "System.Collections.Generic.IEnumerable__1") />

And this gives me a valid .NET object, with a GetEnumerator() method:

enter image description here

When I attempt to call that method:

<cfdump var="#enum.GetEnumerator()#">

That just gives me the following error:

The GetEnumerator method was not found.

Other things I've tried:

Creating a Generic List and Adding things:

<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset eventList.Add(javacast("bigdecimal", "30.1234" )) />

That gives me the following error:

An exception occurred while instantiating a Java object. The class must not be an interface or an abstract class. If the class has a constructor that accepts an argument, you must call the constructor explicitly using the init(args) method. Error : System.Collections.Generic.List__1

And this gives me the same error again:

<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset eventList.Add("foo") />

Trying to Init the List

This code from Leigh works:

<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset elemClass = createObject(".net", "System.String", "dotNetCoreProxy.jar") />
<cfset elemType = elemClass.getDotNetClass() />
<cfset eventList.init( elemType ) />
<cfset eventList.Add("foo") />
<cfdump var="#eventList#">

But this doesn't:

<cfobject type="dotnet" name="VideoWallEvent" class="Utilities.VideoWall.VideoWallEvent" assembly="#ExpandPath("Utilities.dll")#">
<cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", "dotNetCoreProxy.jar") />
<cfset elemType = VideoWallEvent.getDotNetClass() />
<cfset eventList.init( elemType ) />
<cfdump var="#eventList#">

I get the following error:

Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).

JNBProxyGUI.exe

This has been completely useless. I can browse to my Utilities.dll file, and add the System.Collections assembly from GAC, but the Build option in the Project menu is always disabled, and nothing shows in any of the panes once those assemblies have been added.

Testing Methods

I've run all of the following code on one page:

<cfset UtilitiesProxy = ExpandPath("UtilitiesProxy.jar") />
<cfset DotNetCoreProxy = "dotNetCoreProxy.jar" />
<cfset CoStarUtilities = ExpandPath("CoStar.Utilities.dll") />

<h2>Try using base DLLs and DotNetCore</h2>
<cftry>
    <cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#CoStarUtilities#">
    <cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
    <cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#"> (error line)
    <cfcatch>
        <h3><cfoutput>#cfcatch.type#</cfoutput></h3>
        <p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
    </cfcatch>
</cftry>

<h2>Try using the Utilities Proxy for Everything</h2>
<cftry>
    <cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#UtilitiesProxy#">
    <cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", UtilitiesProxy) /> (error line)
    <cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#">
    <cfcatch>
        <h3><cfoutput>#cfcatch.type#</cfoutput></h3>
        <p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
    </cfcatch>
</cftry>

<h2>Try using Utilities Proxy for VideoWall, and DotNetCore for List</h2>
<cftry>
    <cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#UtilitiesProxy#">
    <cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
    <cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#"> (error line)
    <cfcatch>
        <h3><cfoutput>#cfcatch.type#</cfoutput></h3>
        <p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
    </cfcatch>
</cftry>

<h2>Try Initing Wall Event</h2>
<cftry>
    <cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#CoStarUtilities#">
    <cfset VideoWallEvent.Init() />
    <cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
    <cfdump var="#eventList.init(VideoWallEvent.getDotNetClass())#"> (error line)
    <cfcatch>
        <h3><cfoutput>#cfcatch.type#</cfoutput></h3>
        <p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
    </cfcatch>
</cftry>

<h2>Try Initing Wall Event</h2>
<cftry>
    <cfobject type="dotnet" name="VideoWallEvent" class="CoStar.Utilities.VideoWall.VideoWallEvent" assembly="#CoStarUtilities#">
    <cfset VideoWallEvent.Init() />
    <cfset eventList = CreateObject(".net","System.Collections.Generic.List`1", DotNetCoreProxy) />
    <cfdump var="#eventList.init(VideoWallEvent)#"> (error line)
    <cfcatch>
        <h3><cfoutput>#cfcatch.type#</cfoutput></h3>
        <p><cfoutput>#cfcatch.message#: #cfcatch.detail#</cfoutput></p>
    </cfcatch>
</cftry>

And I get the following error output (hint, every single one of these fails).

09:22:45.045 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 9
    Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).

09:22:48.048 - coldfusion.runtime.dotnet.ProxyGenerationException - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 19
09:22:48.048 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 31
    Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).

09:22:48.048 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 43
    Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( System.RuntimeType ).

09:22:48.048 - Object Exception - in C:/inetpub/LandsofAmerica/scribble/VideoWall/index.cfm : line 55
    Unable to find a constructor for class System.Collections.Generic.List__1 that accepts parameters of type ( CoStar.Utilities.VideoWall.VideoWallEvent ).

I've generated a proxy with (what I think is) all of the items that I need.

JNBProxyGUI

Conclusion

So now I'm stuck. I can instantiate and fill all of the necessary objects to make this thing work, I just can't push all of the objects together to make them work. What am I missing when trying to create the ENum object necessary to fill the Set_Events method?

like image 610
Dan Short Avatar asked May 24 '13 19:05

Dan Short


2 Answers

As mentioned, you need to use a concrete class (not the interface). However, looking at the method signature, it must be one that implements System.Collections.Generic.IEnumerable, and not System.Collections.IEnumerable. The latter is only for non-generic collections. One example of a generic collection you could use is List<T>, where <T> is the type of object the list holds. (You will need to review your API to determine the object type).

Unfortunately ... there is an issue with Generic classes. Essentially the tool used by createObject cannot generate proxies for Generics. (The blog entry says it was resolved in CF9, but in my tests I encountered the same problem with 9.0.1 - YMMV.) So you need to do it yourself using the JNBProxyGUI.exe. Frankly it is not the most intuitive tool .. but after a little wrangling I managed to get it working under CF9. Fortunately it is only a one time event.

After exporting the generated proxies to a jar file, use the example in the blog entry to create a generic List. Just add the proxy jar to the assembly list. Say the Collection stores simple Strings:

    // create a generic list of strings ie new List<String>()
    path = "c:/path/yourGenericsProxy.jar,c:/path/yourApplication.dll"; 
    list = createObject(".net", "System.Collections.Generic.List__1", path);
    elemClass = createObject(".net", "System.String", path);
    elemType = elemClass.getDotNetClass();
    list.init( elemType );

Once the List is initialized, you can add a few elements to it:

    list.add( "foo" );
    list.add( "bar" );

Then finally invoke GetEnumerator() and pass it into your method Then pass the List into your method:

    // wrong: yourObject.Set_Events( list );
    yourObject.Set_Events( list );

(If you have any questions about generating the proxies, just ask.)


UPDATE:

As Dan pointed out, his method is using IEnumerable not IEnumerator. So the List itself should be passed into set_Event, not Get_Enumerator() (as in the previous example). Also, when you invoke createObject, the assemblyList MUST include both the proxy jar AND the dll file. Otherwise the method call(s) may fail. Turns out that was the cause of the later problems.

Below is a corrected version that works with CF10.

Regenerated the proxy jar

  1. Open JNBProxyGUI.exe and select Create new Java -> .NET Project
  2. Enter local java settings (My settings)
    • Remote host/port: local host 6089
    • Java path: C:\ColdFusion10\cfusion\jetty\jre\bin\java.exe
    • jnbcore.jar: C:\ColdFusion10\cfusion\lib\jnbcore.jar
    • bcel.jar: C:\ColdFusion10\cfusion\lib\becel-5.1-jnbridge.jar
  3. Click Project > Edit Assembly List > Add. Locate and select "mscorlib.dll"
  4. Click Project > Add Classes from Assembly File. Locate and select "mscorlib.dll" (The gui then generates a list of classes. It might take a while).
  5. In the "Environment" list, select System.Collections.Generic package and click Add.
  6. Select Edit > Check All in Exposed Proxies
  7. Select Project > Build and select the path and file name for the new proxy jar

Cleanup:

Just to be safe, I stopped CF and removed all generated proxy jars from WEB-INF\cfclasses\dotNetProxy except dotNetCoreProxy.jar. Then restarted CF and ran the code. It worked perfectly. String. (See complete code below).


MyClass.cs

using System;
using System.Text;
using System.Collections.Generic;

namespace MyLibrary
{
    public class MyClass {
        private IEnumerable<CustomClass> events;

        public void set_Events(IEnumerable<CustomClass> evts) 
        {
            this.events = evts;
        }

        public IEnumerable<CustomClass> get_Events()
        {
            return this.events;
        }
    }
}

CustomClass.cs

using System;
using System.Text;
using System.Collections.Generic;

namespace MyLibrary
{
    public class CustomClass
    {
        private string title;
        public CustomClass(string title)
        {
            this.title = title;
        }

        public string getTitle()
        {
            return this.title;
        }

        public override string ToString()
        {
            return getTitle();
        }
    }
}

CF Code:

<cfscript>
    // MUST include both proxy jar and DLL file
    path = arrayToList([ExpandPath("./MyLibrary.dll"), ExpandPath("genericListAndEnumerator.jar")]);
    //initialize custom class
    customObj = createObject(".net", "MyLibrary.CustomClass", path).init("Blah, blah");
    elemType = customObj.getDotNetClass();

    // create generic list of custom class
    list = CreateObject(".net","System.Collections.Generic.List__1", path);
    list.init( elemType );
    list.add( customObj );

    // test setter
    mainObj = createObject(".net", "MyLibrary.MyClass", path);
    mainObj.set_Events( list );

    // test getter
    enum = mainObj.get_Events().getEnumerator();
    writeDump(enum);
    while (enum.MoveNext()) {
        WriteDump("Current="& enum.Get_Current().toString());
    }
</cfscript>
like image 192
Leigh Avatar answered Sep 21 '22 08:09

Leigh


The problem is that you're trying to call a method of an interface. Instead, you need to create an instance of a class that implements that interface in order to pass that to Set_Events(). Here is a list of .NET classes which implement IEnumerable: IEnumerable Interface.

like image 1
Russ Avatar answered Sep 19 '22 08:09

Russ