Process C# in-memory with WB CLR

Started by stanl, April 13, 2014, 06:46:41 AM

Previous topic - Next topic

stanl

Basically a request for a WB example.  In a previous post about CLR Tony indicated that the .backcolor could be set with  C# code.  I then asked about that 'on the fly', and he indicated in the positive.... 

To me this seems a fantastic opportunity to leverage C# into WB (especially with so much pre-coded C#). Questions would revolve around return codes, I suppose, but I want to ask about a simple test:

assume:  XML
<Names>
    <Name type="M">John</Name>
    <Name type="F">Susan</Name>
    <Name type="M">David</Name>
</Names>

and C#

XmlDocument xml = new XmlDocument();
xml.LoadXml(str);  // suppose that str string contains "<Names>...</Names>"

XmlNodeList xnList = xml.SelectNodes("/Names/Name[@type='M']");
foreach (XmlNode xn in xnList)
{
  Console.WriteLine(xn.InnerText);
}



with a return of
John David 


How might WB get this return? [with the CLR]

td

You can instantiate the class in WinBatch directly in most cases or since C# is an object oriented programming language, you do what you almost always do with object oriented programming languages - you create a class.  You then instantiate an instance of your created class in WinBatch in the usual way.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

Deana

Here is an equivalent  WIL example to selectNode in XML:

Code (winbatch) Select
;***************************************************************************
;**   XML SelectNode
;**
;** Purpose: Selects a list of nodes matching the XPath expression.
;** Inputs:
;** Outputs: Results displayed in a message
;** Reference:
;**       REQUIRES WinBatch 2013A or newer
;**
;** Developer: Deana Falk 2014.04.14
;***************************************************************************
If Version( )< '2013A'
   Pause('Notice', 'Need 2013A or Newer Version of WinBatch')
   Exit
EndIf

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Load assemblies into the WinBatch process.
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ObjectClrOption( 'use', 'System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Prompt for input
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
strxml =        '<Names>':@lf
strxml = strxml:'   <Name type="M">John</Name>':@lf
strxml = strxml:'   <Name type="F">Susan</Name>':@lf
strxml = strxml:'   <Name type="M">David</Name>':@lf
strxml = strxml:'</Names>':@lf

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Create a class implemented by a managed assembly.
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
doc = ObjectClrNew( 'System.Xml.XmlDocument' )
doc.LoadXml( strxml )
xnList = doc.SelectNodes("/Names/Name[@type='M']");
foreach xn in xnList
   xn = ObjectClrType( 'System.Xml.XmlNode', xn )
  Pause('',xn.InnerText);
Next
Exit
Deana F.
Technical Support
Wilson WindowWare Inc.

Deana

I see you are referring to this thread where Tony suggested using GenerateInMemory: http://forum.winbatch.com/index.php?topic=823.msg3242#msg3242.

Unfortunately I have no such example at this time.
Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

Quote from: Deana on April 14, 2014, 09:35:45 AM
I see you are referring to this thread where Tony suggested using GenerateInMemory: http://forum.winbatch.com/index.php?topic=823.msg3242#msg3242.

Unfortunately I have no such example at this time.

No rush. Just emptying my Sunday wishlist. It seems that (understanding limitations, of course) GenerateInMemory is akin to what I used to call 'code blocks' or little snippets that could be executed from within a compiled EXE - for example a .wsc file. One avenue I would like to pursue is to find C# code that simulates the Powershell Out-GridView cmdlet, and generate a grid view w/out PS getting involved.

Deana

Quote from: stanl on April 14, 2014, 09:47:16 AM
Quote from: Deana on April 14, 2014, 09:35:45 AM
I see you are referring to this thread where Tony suggested using GenerateInMemory: http://forum.winbatch.com/index.php?topic=823.msg3242#msg3242.

Unfortunately I have no such example at this time.

No rush. Just emptying my Sunday wishlist. It seems that (understanding limitations, of course) GenerateInMemory is akin to what I used to call 'code blocks' or little snippets that could be executed from within a compiled EXE - for example a .wsc file. One avenue I would like to pursue is to find C# code that simulates the Powershell Out-GridView cmdlet, and generate a grid view w/out PS getting involved.

Ok. Have you seen this code sample that can be used to create an assembly (from C#) on the fly? http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/tsleft.web+WinBatch/dotNet/Third~Party~Components+Create~an~Assembly~DLL~On~The~Fly.txt
Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

Quote from: Deana on April 14, 2014, 09:56:14 AM
Ok. Have you seen this code sample that can be used to create an assembly (from C#) on the fly? http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/tsleft.web+WinBatch/dotNet/Third~Party~Components+Create~an~Assembly~DLL~On~The~Fly.txt

Yes - it generates a .dll file, and I created a couple examples using similar code. The InMemory option, if I understand correctly, does not require the dll creation.


Deana

Here is what I have come up with so far...Not quite there. I am getting stuck on the Invoke method.

Code (winbatch) Select
ObjectClrOption("version", "v2.0.50727")
ObjectClrOption("use","System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

objCSharp = ObjectClrNew('Microsoft.CSharp.CSharpCodeProvider')
objParams = ObjectClrNew('System.CodeDom.Compiler.CompilerParameters')
objParams.GenerateInMemory = ObjectType( "VT_BOOL", 1 ) ;TRUE
source = `public class FooClass { public int Execute() { return 1;}}`
objResult = objCSharp.CompileAssemblyFromSource(objParams,source)

;Compiler Output
If objResult.Output.Count > 0
   strOutput = ''
   For x = 0 to objResult.Output.Count-1
      if strOutput == "" then strOutput = objResult.Output.Item(x)
      else strOutput = strOutput:@lf:objResult.Output.Item(x)
   Next
   Pause('Compiler Output',strOutput)
Endif
; Compiler Errors
If objResult.Errors.Count > 0
    strErrors = ''
    ForEach ce In objResult.Errors
         ;Pause("Error", ce.ToString())
         if strErrors == "" then strErrors = ce.ToString()
         else strErrors = strErrors:@lf:ce.ToString()
    Next
    Pause('Compiler Errors',strErrors)
    Exit
EndIf


objType = objResult.CompiledAssembly.GetType("FooClass")
objActivator = ObjectClrNew('System.Activator')
objSystem = objActivator.CreateInstance(objType)
objMethod = objType.GetMethod("Execute")

; STUCK HERE
;http://msdn.microsoft.com/en-us/library/system.reflection.methodbase.invoke.aspx
; MethodBase.Invoke Method (Object, Object())
; Invokes the method or constructor represented by the current instance, using the specified parameters.
;
;parameter1: (System.Object) The object on which to invoke the method or constructor. If a method is static, this argument is ignored. If a constructor is static, this argument must be Nothing or an instance of the class that defines the constructor.
;parameter2: (System.Object()) An argument list for the invoked method or constructor. This is an array of objects with the same number, order, and type as the parameters of the method or constructor to be invoked. If there are no parameters, parameters should be Nothing.
;If the method or constructor represented by this instance takes a ref parameter, no special attribute is required for that parameter in order to invoke the method or constructor using this function. Any object in this array that is not explicitly initialized
;with a value will contain the default value for that object type. For reference-type elements, this value is Nothing. For value-type elements, this value is 0, 0.0, or false, depending on the specific element type.

; Second parameter is an array of objects with the same number, order, and type as the parameters of the method or constructor to be invoked. If there are no parameters, parameters should be Nothing.
NULL = ObjectType("NULL",0)
objMethod.Invoke(objSystem,NULL)

Exit
Deana F.
Technical Support
Wilson WindowWare Inc.

Deana

Okay I think I have a simple working sample:

Code (winbatch) Select
;***************************************************************************
;**   Run C# in memory using WinBatch
;**
;** Purpose: Implement C# in WinBatch
;** Inputs:
;** Outputs:
;** Reference:
;**       REQUIRES WinBatch 2013A or newer
;**
;** Developer: Deana Falk 2014.04.14
;***************************************************************************
If Version( )< '2013A'
   Pause('Notice', 'Need 2013A or Newer Version of WinBatch')
   Exit
EndIf

ObjectClrOption('version', 'v2.0.50727')
ObjectClrOption('use','System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')

objCSharp = ObjectClrNew('Microsoft.CSharp.CSharpCodeProvider')
objParams = ObjectClrNew('System.CodeDom.Compiler.CompilerParameters')
objParams.GenerateInMemory = ObjectType( "VT_BOOL", 1 ) ;TRUE

cSharpSource = `public class FooClass { public int Execute() { return 999;}}`

objResult = objCSharp.CompileAssemblyFromSource(objParams,cSharpSource)

;Compiler Output
If objResult.Output.Count > 0
   strOutput = ''
   For x = 0 to objResult.Output.Count-1
      if strOutput == "" then strOutput = objResult.Output.Item(x)
      else strOutput = strOutput:@lf:objResult.Output.Item(x)
   Next
   Pause('Compiler Output',strOutput)
Endif

; Compiler Errors
If objResult.Errors.Count > 0
    strErrors = ''
    ForEach ce In objResult.Errors
         ;Pause("Error", ce.ToString())
         if strErrors == "" then strErrors = ce.ToString()
         else strErrors = strErrors:@lf:ce.ToString()
    Next
    Pause('Compiler Errors',strErrors)
    Exit
EndIf


objType = objResult.CompiledAssembly.GetType("FooClass")
objActivator = ObjectClrNew('System.Activator')
objSystem = objActivator.CreateInstance(objType)
objMethod = objType.GetMethod("Execute")
args = objMethod.GetGenericArguments
results = objMethod.Invoke(objSystem,args)
Pause('Results of Execute', results)
Exit

Deana F.
Technical Support
Wilson WindowWare Inc.

Deana

Here is the example you asked for:

Code (winbatch) Select
;***************************************************************************
;**   Run C# in memory using WinBatch
;**
;** Purpose: Implement C# in WinBatch
;** Inputs:
;** Outputs:
;** Reference:
;**       REQUIRES WinBatch 2013A or newer
;**
;** Developer: Deana Falk 2014.04.14
;***************************************************************************
If Version( )< '2013A'
   Pause('Notice', 'Need 2013A or Newer Version of WinBatch')
   Exit
EndIf

XMLINPUT = ``

ObjectClrOption('version', 'v2.0.50727')
ObjectClrOption('use','System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')


objCSharp = ObjectClrNew('Microsoft.CSharp.CSharpCodeProvider')
objParams = ObjectClrNew('System.CodeDom.Compiler.CompilerParameters')
objParams.GenerateInMemory = ObjectType( "VT_BOOL", 1 ) ;TRUE

objParams.ReferencedAssemblies.Add("System.dll")
objParams.ReferencedAssemblies.Add("C:\Windows\assembly\GAC_MSIL\System.Xml\2.0.0.0__b77a5c561934e089\System.XML.dll")

;cSharpSource = FileGet('c:\temp\xml.cs')
;*OR*
cSharpSource =               `using System;`:@lf
cSharpSource = cSharpSource :`using System.Xml;`:@lf
cSharpSource = cSharpSource :` public class Sample {`:@lf
cSharpSource = cSharpSource :`  public string MySelectNode() {`:@lf

cSharpSource = cSharpSource :`    // Create the XmlDocument.`:@lf
cSharpSource = cSharpSource :` string str = @"<Names><Name type=""M"">John</Name> <Name type=""F"">Susan</Name><Name type=""M"">David</Name></Names>";`:@lf

cSharpSource = cSharpSource :` XmlDocument xml = new XmlDocument();` :@lf
cSharpSource = cSharpSource :`    xml.LoadXml(str);` :@lf

cSharpSource = cSharpSource :` XmlNodeList xnList = xml.SelectNodes("/Names/Name[@type='M']");`:@lf 
cSharpSource = cSharpSource :` string Results = "";`:@lf
cSharpSource = cSharpSource :` foreach (XmlNode xn in xnList)`:@lf
cSharpSource = cSharpSource :` {`:@lf
cSharpSource = cSharpSource :`   Results = Results + " " + xn.InnerText;`:@lf
cSharpSource = cSharpSource :` }`:@lf
cSharpSource = cSharpSource :` return Results;`:@lf
cSharpSource = cSharpSource :`  }`:@lf
cSharpSource = cSharpSource :`}`


objResult = objCSharp.CompileAssemblyFromSource(objParams,cSharpSource)

;Compiler Output
If objResult.Output.Count > 0
   strOutput = ''
   For x = 0 to objResult.Output.Count-1
      if strOutput == "" then strOutput = objResult.Output.Item(x)
      else strOutput = strOutput:@lf:objResult.Output.Item(x)
   Next
   Pause('Compiler Output',strOutput)
Endif

; Compiler Errors
If objResult.Errors.Count > 0
    strErrors = ''
    ForEach ce In objResult.Errors
         ;Pause("Error", ce.ToString())
         if strErrors == "" then strErrors = ce.ToString()
         else strErrors = strErrors:@lf:ce.ToString()
    Next
    Pause('Compiler Errors',strErrors)
    Exit
EndIf


objType = objResult.CompiledAssembly.GetType("Sample")
objActivator = ObjectClrNew('System.Activator')
objSystem = objActivator.CreateInstance(objType)
objMethod = objType.GetMethod("MySelectNode")
args = objMethod.GetGenericArguments
results = objMethod.Invoke(objSystem,args)
Pause('Results of MySelectNode()', results)
Exit

Deana F.
Technical Support
Wilson WindowWare Inc.

Deana

Deana F.
Technical Support
Wilson WindowWare Inc.

td

If you properly code your C# class using a name space, you simple need to compile the source into memory and call 'ObjectClrNew' using the name space and class name of your freshly minted C# class as the parameter. WinBatch will have no problems finding your in-memory class
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

Deana

Quote from: td on April 14, 2014, 01:17:32 PM
If you properly code your C# class using a name space, you simple need to compile the source into memory and call 'ObjectClrNew' using the name space and class name of your freshly minted C# class as the parameter. WinBatch will have no problems finding your in-memory class

Cool. Thanks Tony!

Here is my finalized script.

Code (winbatch) Select

;***************************************************************************
;**  Run C# in memory using WinBatch - Use Namespace.Class defined in CSharpeSource
;**
;** Purpose:  Run C# in memory using WinBatch - Using Namespace.Class defined in CSharpeSource
;** Inputs:
;** Outputs: Results in message
;** Reference:
;**       REQUIRES WinBatch 2013A or newer
;**
;** Developer: Deana Falk 2014.04.14
;***************************************************************************
If Version( )< '2013A'
   Pause('Notice', 'Need 2013A or Newer Version of WinBatch')
   Exit
EndIf

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Load assemblies into the WinBatch process.
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; mscorlib assembly is automatically loaded by WinBatch when the CLR is loaded.
; ObjectClrOption ('use','mscorlib, version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')

ObjectClrOption("version", "v2.0.50727")
ObjectClrOption("use","System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")


;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Compiles the c# code in Memory
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
objCSharp = ObjectClrNew('Microsoft.CSharp.CSharpCodeProvider')
objParams = ObjectClrNew('System.CodeDom.Compiler.CompilerParameters')
objParams.GenerateInMemory = ObjectType( "VT_BOOL", 1 ) ;TRUE

objParams.ReferencedAssemblies.Add("System.dll")
objParams.ReferencedAssemblies.Add("C:\Windows\assembly\GAC_MSIL\System.Xml\2.0.0.0__b77a5c561934e089\System.XML.dll")

;Make sure the cSharp code includes both namepspace and class that can be later used by ObjectCLRNew
cSharpSource = `namespace MyNamespace {`:@lf
cSharpSource = cSharpSource :`using System;`:@lf
cSharpSource = cSharpSource :`using System.Xml;`:@lf
cSharpSource = cSharpSource :` public class MyClass {`:@lf
cSharpSource = cSharpSource :`  public string MySelectNode() {`:@lf
cSharpSource = cSharpSource :`    // Create the XmlDocument.`:@lf
cSharpSource = cSharpSource :` string str = @"<Names><Name type=""M"">John</Name> <Name type=""F"">Susan</Name><Name type=""M"">David</Name></Names>";`:@lf
cSharpSource = cSharpSource :` XmlDocument xml = new XmlDocument();` :@lf
cSharpSource = cSharpSource :`    xml.LoadXml(str);` :@lf
cSharpSource = cSharpSource :` XmlNodeList xnList = xml.SelectNodes("/Names/Name[@type='M']");`:@lf 
cSharpSource = cSharpSource :` string Results = "";`:@lf
cSharpSource = cSharpSource :` foreach (XmlNode xn in xnList)`:@lf
cSharpSource = cSharpSource :` {`:@lf
cSharpSource = cSharpSource :`   Results = Results + " " + xn.InnerText;`:@lf
cSharpSource = cSharpSource :` }`:@lf
cSharpSource = cSharpSource :` return Results;`:@lf
cSharpSource = cSharpSource :`  }`:@lf
cSharpSource = cSharpSource :` }`:@lf
cSharpSource = cSharpSource :`}`
objResult = objCSharp.CompileAssemblyFromSource(objParams,cSharpSource)

;Compiler Output
If objResult.Output.Count > 0
   strOutput = ''
   For x = 0 to objResult.Output.Count-1
      if strOutput == "" then strOutput = objResult.Output.Item(x)
      else strOutput = strOutput:@lf:objResult.Output.Item(x)
   Next
   Pause('Compiler Output',strOutput)
Endif
; Compiler Errors
If objResult.Errors.Count > 0
    strErrors = ''
    ForEach ce In objResult.Errors
         ;Pause("Error", ce.ToString())
         if strErrors == "" then strErrors = ce.ToString()
         else strErrors = strErrors:@lf:ce.ToString()
    Next
    Pause('Compiler Errors',strErrors)
    Exit
EndIf



;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Create a class implemented by a managed assembly.
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Use Namespace.Class defined in CSharpeSource
oSample = ObjectClrNew( 'MyNamespace.MyClass' )
Results = oSample.MySelectNode()
Pause('Results',Results)

Exit
Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

Thank you both. Hope I don't stand alone in saying this is another example of Winbatch as THE 'can-do' scripting language.

stanl

Back to the .BackColor property we couldn't set in the Windows RTF form from an earlier thread.

I would like to create an in-memory namespace.class, i.e.

oColor = ObjectClrNew( 'CTemp.BK' )

Then set

oForm.BackColor = oColor.gColor(255,185,109)

Where (using Deana's code to produce an in-memory class) I build


namespace CTemp {
using System;
using System Drawing;
   public class BK {
      public structure gGolor( byte r, byte g, byte b ){
         return color.FromArgb(r,g,b)
      }
   }
}


Several dumb questions:

Do I need to declare r,g,b as system.byte or will ObjectType(I1...) suffice?

Do I need to use objParams.ReferencedAssemblies.Add("System.dll") in the code to create the class, or the fact that ObjectClrOption() in the main code already declares it won't require it?

Is the C# snippet even close to getting BackColor?

Deana

Stan, I have also been working on a solution using C# GenerateInMemory solution. However I seem to get back the exact same value for the Color Structure. Maybe Tony will chime in with a solution. Here is what I have come up with:

Code (winbatch) Select

;***************************************************************************
;**
;**   Get Color Structure using WIL
;**
;***************************************************************************
ObjectClrOption( 'use','System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )
Color  = ObjectClrNew( 'System.Drawing.Color' )
mycolorstructWIL = Color.FromArgb(255,185,209,234)
Pause('WIL Result', mycolorstructWIL )

;***************************************************************************
;**
;**   Get Color Structure using C# GenerateInMemory
;**
;***************************************************************************
ObjectClrOption('use','System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
objCSharp = ObjectClrNew('Microsoft.CSharp.CSharpCodeProvider')
objParams = ObjectClrNew('System.CodeDom.Compiler.CompilerParameters')
objParams.GenerateInMemory = ObjectType( "VT_BOOL", 1 ) ;TRUE
objParams.ReferencedAssemblies.Add("C:\Windows\assembly\GAC_MSIL\System.Drawing\2.0.0.0__b03f5f7f11d50a3a\System.Drawing.dll")
cSharpSource = ``
cSharpSource = cSharpSource :`using System;`:@lf
cSharpSource = cSharpSource :`using System.Drawing;`:@lf
cSharpSource = cSharpSource :`namespace MyNamespace {`:@lf
cSharpSource = cSharpSource :`  public class MyClass {`
cSharpSource = cSharpSource :`    public Color GetColorStruct() {`:@lf     
cSharpSource = cSharpSource :`      Color argb = Color.FromArgb (255,185,209,234);`:@lf
cSharpSource = cSharpSource :`      return argb;`:@lf
cSharpSource = cSharpSource :`    }`:@lf
cSharpSource = cSharpSource :`  }`:@lf
cSharpSource = cSharpSource :`}`:@lf
objResult = objCSharp.CompileAssemblyFromSource(objParams,cSharpSource)
;Compiler Output
If objResult.Output.Count > 0
   strOutput = ''
   For x = 0 to objResult.Output.Count-1
      if strOutput == "" then strOutput = objResult.Output.Item(x)
      else strOutput = strOutput:@lf:objResult.Output.Item(x)
   Next
   Pause('Compiler Output',strOutput)
Endif
objClass = ObjectClrNew('MyNamespace.MyClass')
mycolorstructC = objClass.GetColorStruct()
Pause('C# GenerateInMemory Results', mycolorstructC)
Exit
Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

Quote from: Deana on April 15, 2014, 08:18:45 AM
Stan, I have also been working on a solution using C# GenerateInMemory solution. However I seem to get back the exact same value for the Color Structure. Maybe Tony will chime in with a solution. Here is what I have come up with:

So many ways to skin the cat. I'm still a little confused over Color being a structure, not a property or class.  I took your code and did this (script + c# code as text file attached). Changed to version 4.0, and get the error that 'Color' not in namespace context.

If we get this, makes more sense to create a .dll rather than in-memory as .BackColor is common to most .net Forms. 

Deana

Quote from: stanl on April 15, 2014, 09:31:50 AM
Quote from: Deana on April 15, 2014, 08:18:45 AM
Stan, I have also been working on a solution using C# GenerateInMemory solution. However I seem to get back the exact same value for the Color Structure. Maybe Tony will chime in with a solution. Here is what I have come up with:

So many ways to skin the cat. I'm still a little confused over Color being a structure, not a property or class.  I took your code and did this (script + c# code as text file attached). Changed to version 4.0, and get the error that 'Color' not in namespace context.

If we get this, makes more sense to create a .dll rather than in-memory as .BackColor is common to most .net Forms.

See bk.txt: Change  gGolor to  gColor.
Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

OK. Cleaned up bk.txt; added reference to system.drawing assembly, get a type conversion error. [see attached]

Deana

That is a C# error. Unfortunately I am not familiar enough with C# to offer debugging advice. In cases such as this I defer to Google.... http://support.microsoft.com/kb/304696

in your C# code try:

return (Color)argb;
Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

Yeah [maybe]... got the script to return a result. Took out the structure portion of bk.txt; made reference to my actual System.Drawing.dll

objParams.ReferencedAssemblies.Add("C:\Windows\winsxs\msil_system.drawing_b03f5f7f11d50a3a_6.1.7600.21139_none_4d477e26c6cce58a\System.Drawing.dll")

and finally called the function with values rather that ObjectType().
Results = oColor.gColor(255,185,109) ;send values as integers


Now to find, if possible, a way to make .Backcolor available to a Windows form.

Deana

I think your call to ObjectType type unnecessary and the wrong data type. Did you see the code I already posted? It appears we are going down the same 'incorrect' path (rabbit hole).
Deana F.
Technical Support
Wilson WindowWare Inc.

td

Me thinks y'all be trying to solve the wrong problem. Done that many times myself.

The problem is the fact that you get a color property returning one variant type but the FCL will not accept that very same variant type when you try to set a color property.  To work around this MSFT induced inconsistency, you need to perform the set in c# so variants don't get in the way.   Here is the c# snippet from an old test script.  It could be much improved but might (or might not) get things going in the right direction. 
Code (winbatch) Select

strSource = ""
strSource = strSource:`using System.Drawing;`:@crlf
strSource = strSource:`using System.Windows.Forms;`:@crlf
strSource = strSource:`namespace WinBatch`:@crlf
strSource = strSource:`{`:@crlf
strSource = strSource:` class WbSetColor`:@crlf
strSource = strSource:` {`:@crlf
strSource = strSource:`    public void SetForeColor(Control objControl, int a, int r, int g, int b )`:@crlf
strSource = strSource:`    {`:@crlf
strSource = strSource:`        try `:@crlf
strSource = strSource:`        { `:@crlf
strSource = strSource:`            objControl.ForeColor = Color.FromArgb(a,r,g,b);`:@crlf
strSource = strSource:`        }`:@crlf
strSource = strSource:`        catch (System.Exception ex)`:@crlf
strSource = strSource:`        {`:@crlf
strSource = strSource:`           System.Diagnostics.Debug.WriteLine (ex.ToString());`:@crlf
strSource = strSource:`        }`:@crlf
strSource = strSource:`     }`:@crlf
strSource = strSource:`  }`:@crlf
strSource = strSource:`}`:@crlf

       
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on April 15, 2014, 01:24:50 PM
Me thinks y'all be trying to solve the wrong problem. Done that many times myself.

The problem is the fact that you get a color property returning one variant type but the FCL will not accept that very same variant type when you try to set a color property. 

To clarify. The code I just posted returns a UI4 Type. You are saying that even if that code works applying that return to a Form.Backcolor will fail? So don't even go there.

Deana

Thanks Tony! Here is what I came up with that seems to work great:

Code (winbatch) Select
;***************************************************************************
;**   Use a Windows .Net Form with a ForeColor and BackColor
;**
;** Purpose: Set the ForeColor and BackColor of a Windows Form
;** Inputs: 
;** Outputs: Results written to screen as a form
;** Reference:
;**       REQUIRES WinBatch 2013A or newer
;**
;** Developer: Deana Falk 2014.04.15
;***************************************************************************
If Version( )< '2013A'
   Pause('Notice', 'Need 2013A or Newer Version of WinBatch')
   Exit
EndIf


_True = ObjectType( 'BOOL', -1 )
_False = ObjectType( 'BOOL', 0 )

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; ObjectClrOption - Load assembly into the WinBatch process
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;ObjectClrOption('use','System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
ObjectClrOption( 'use', 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' )
ObjectClrOption( 'use', 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' )

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; ObjectClrNew - Create a class implemented by a managed assembly.
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Form1 = ObjectClrNew('System.Windows.Forms.Form')
Button1 =  ObjectClrNew('System.Windows.Forms.Button')

; Enumerations
enumStartPos = ObjectClrNew('System.Windows.Forms.FormStartPosition')
enumDialogResult = ObjectClrNew( 'System.Windows.Forms.DialogResult')

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Define Form1
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Set the caption bar text of the form.   
Form1.Text = 'BackColor'
; Set the start position of the form to the center of the screen.
Form1.StartPosition = ObjectClrType( 'System.Windows.Forms.FormStartPosition', enumStartPos.CenterScreen ) ;CenterScreen
; Set the accept button of the form to button1.
button1.Location = ObjectClrNew('System.Drawing.Point',100,100)
Form1.AcceptButton = button1
; Obtain the Width of the form for later use with controls
Form1.Size =   ObjectClrNew( 'System.Drawing.Size',900,550)
formwidth = Form1.Width


;***************************************************************************
;**
;**   Set Color Structure using C# GenerateInMemory
;**
;***************************************************************************
;This is an oddity in the FCL's implementation of the structures and there is really not much that can be done about it.
;System.Drawing.Color has a lot of static methods that are quite handy but they return an VT_I4 variant when returning a color. 
;This wouldn't be a problem if all the properties that expect that structure also accepted VT_I4s as an appropriate type. 
;But many of them don't.  They are expecting some kind of VT_RECORD instead.  This wouldn't be a problem either
;if the FCL provided an IRECORD interface for the FCL structure so WinBatch could automagically convert the type but it doen't do that either! 
;It is a catch 22 and ostensibly a inconsistency in the implementation of the FCL. C'est la guerre.
;objColor  = ObjectClrNew( 'System.Drawing.Color' )
;Form1.BackColor =  objColor.Blue
objCSharp = ObjectClrNew('Microsoft.CSharp.CSharpCodeProvider')
objParams = ObjectClrNew('System.CodeDom.Compiler.CompilerParameters')
objParams.GenerateInMemory = ObjectType( "VT_BOOL", 1 ) ;TRUE
objParams.ReferencedAssemblies.Add("System.Drawing.dll")
objParams.ReferencedAssemblies.Add("System.Windows.Forms.dll")
objParams.ReferencedAssemblies.Add("System.dll")
strSource = ""
strSource = strSource:`using System.Drawing;`:@crlf
strSource = strSource:`using System.Windows.Forms;`:@crlf
strSource = strSource:`namespace WinBatch`:@crlf
strSource = strSource:`{`:@crlf
strSource = strSource:` class WbSetColor`:@crlf
strSource = strSource:` {`:@crlf
strSource = strSource:`    public void SetForeColor(Control objControl, int a, int r, int g, int b )`:@crlf
strSource = strSource:`    {`:@crlf
strSource = strSource:`        try `:@crlf
strSource = strSource:`        { `:@crlf
strSource = strSource:`            objControl.ForeColor = Color.FromArgb(a,r,g,b);`:@crlf
strSource = strSource:`        }`:@crlf
strSource = strSource:`        catch (System.Exception ex)`:@crlf
strSource = strSource:`        {`:@crlf
strSource = strSource:`           System.Diagnostics.Debug.WriteLine (ex.ToString());`:@crlf
strSource = strSource:`        }`:@crlf
strSource = strSource:`     }`:@crlf
strSource = strSource:`    public void SetBackColor(Control objControl, int a, int r, int g, int b )`:@crlf
strSource = strSource:`    {`:@crlf
strSource = strSource:`        try `:@crlf
strSource = strSource:`        { `:@crlf
strSource = strSource:`            objControl.BackColor = Color.FromArgb(a,r,g,b);`:@crlf
strSource = strSource:`        }`:@crlf
strSource = strSource:`        catch (System.Exception ex)`:@crlf
strSource = strSource:`        {`:@crlf
strSource = strSource:`           System.Diagnostics.Debug.WriteLine (ex.ToString());`:@crlf
strSource = strSource:`        }`:@crlf
strSource = strSource:`     }`:@crlf
strSource = strSource:`  }`:@crlf
strSource = strSource:`}`:@crlf
objResult = objCSharp.CompileAssemblyFromSource(objParams,strSource)
;Compiler Output
If objResult.Output.Count > 0
   strOutput = ''
   For x = 0 to objResult.Output.Count-1
      if strOutput == "" then strOutput = objResult.Output.Item(x)
      else strOutput = strOutput:@lf:objResult.Output.Item(x)
   Next
   Pause('Compiler Output',strOutput)
Endif
objWbSetColor = ObjectClrNew('WinBatch.WbSetColor')

;Set Foreground color to WHITE
a=255
r=255
g=255
b=255
objWbSetColor.SetForeColor(Form1,a,r,g,b)

;Set Background color to RED
a=255
r=255
g=0
b=0
objWbSetColor.SetBackColor(Form1,a,r,g,b)

;Set Button Background color to BLUE
a=255
r=0
g=0
b=255
objWbSetColor.SetBackColor(Button1,a,r,g,b)


;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Define Button1
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Set the position of the button on the form.
button1.Location = ObjectClrNew('System.Drawing.Point',100,110)
; Set the text of button1 to 'OK'.
button1.Text = 'OK'
; Specify tabindex
button1.TabIndex  = 1
; IMPORTANT: The dialog box can be assigned one of the values of the DialogResult enumeration by assigning it to the DialogResult property of a Button on the form.
button1.DialogResult = ObjectClrType( 'System.Windows.Forms.DialogResult', enumDialogResult.OK )

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Add Controls
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Form1.Controls.Add(Button1)

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Display the form as a modal dialog box.
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ret = Form1.ShowDialog()

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; Clean up
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Button1.Dispose()
Form1.Dispose()

Exit

Deana F.
Technical Support
Wilson WindowWare Inc.

Deana

Quote from: stanl on April 15, 2014, 02:08:45 PM
Quote from: td on April 15, 2014, 01:24:50 PM
Me thinks y'all be trying to solve the wrong problem. Done that many times myself.

The problem is the fact that you get a color property returning one variant type but the FCL will not accept that very same variant type when you try to set a color property. 

To clarify. The code I just posted returns a UI4 Type. You are saying that even if that code works applying that return to a Form.Backcolor will fail? So don't even go there.

Correct see the code I just posted using Tony's recommended implementation.
Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

Quote from: Deana on April 15, 2014, 02:10:31 PM
Correct see the code I just posted using Tony's recommended implementation.

Ran your code. Worked. Still puzzling: the assembly .dll's you referenced do not exist on my laptop in the path your code used, yet it didn't complain. Uncommented the references and the script failed. So somehow, even though the references had incorrect paths the script requires them. I say this thinking about versionitis if compiling your test and running on other PC's (which I intend to do).

As for why Tony's suggestion worked. The code includes the object and the property associated with it, as calculating the property then applying it to the object outside the C# snippet will fail. I guess I understand this like working with bound and unbound data sources when applied to forms or grids. Makes more sense to me that way.

Deana

Quote from: stanl on April 16, 2014, 03:47:54 AM
Ran your code. Worked. Still puzzling: the assembly .dll's you referenced do not exist on my laptop in the path your code used, yet it didn't complain. Uncommented the references and the script failed. So somehow, even though the references had incorrect paths the script requires them. I say this thinking about versionitis if compiling your test and running on other PC's (which I intend to do).


It appears that only the assembly name is required ( no path information) when adding the referenced assemblies:
http://msdn.microsoft.com/en-us/library/system.codedom.compiler.compilerparameters.referencedassemblies(v=vs.110).aspx

I updated the code sample to only reference the assembly dll: http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/tsleft.web+WinBatch/dotNet/System_Windows/System_Window_Forms+Color~Structure~Issue.txt

You should make sure the  assemblies are registered in the GAC (Global Assembly Cache), because that is where it eventually looks for assemblies when an absolute path isn't supplied.

Deana F.
Technical Support
Wilson WindowWare Inc.

stanl

Quote from: Deana on April 16, 2014, 09:20:06 AM
[It appears that only the assembly name is required

Yeah, that dawned on me as probable. My updates to the RTF, just reference the assembly name.

QuoteYou should make sure the  assemblies are registered in the GAC

I believe when the CLR was first announced I posted a script that created a lookup for the assemblies in the GAC.  Might be in the old board.

Deana

Here is a UNDEBUGGED script to get a list of all the assemblies in the GAC using the GacUtil commandline

Code (winbatch) Select
;***************************************************************************
;**    GACUtil to StdOut
;**
;** Purpose: List All Assemblies in GAC Glocal Assembly Cache
;** Inputs:
;** Outputs: Results in an array which can be searched
;** Reference:
;**       REQUIRES WinBatch 2013A or newer
;**
;** Developer: Deana Falk 2014.04.16
;***************************************************************************

GoSub UDFS

;***************************************************************************
;**
;**   Tab delimited list of assemblies
;**
;***************************************************************************


gaclist = EnumGACAssemblies( 0 )
count = ItemCount( gaclist, @tab )
AskItemList(count, gaclist, @tab, @sorted, @single )

;***************************************************************************
;**
;**   Array of assemblies
;**
;***************************************************************************


array = EnumGACAssemblies( 1 )
MyDialogFormat=`WWWDLGED,6.2`

MyDialogCaption=`WIL Dialog 1`
MyDialogX=002
MyDialogY=059
MyDialogWidth=436
MyDialogHeight=353
MyDialogNumControls=003
MyDialogProcedure=`DEFAULT`
MyDialogFont=`DEFAULT`
MyDialogTextColor=`DEFAULT`
MyDialogBackground=`DEFAULT,DEFAULT`
MyDialogConfig=0

MyDialog001=`113,323,036,012,PUSHBUTTON,"PushButton_OK",DEFAULT,"OK",1,10,32,DEFAULT,DEFAULT,DEFAULT`
MyDialog002=`265,325,036,012,PUSHBUTTON,"PushButton_Cancel",DEFAULT,"Cancel",0,20,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog003=`005,005,422,306,REPORTVIEW,"ReportView_1",array,DEFAULT,DEFAULT,30,DEFAULT,DEFAULT,DEFAULT,DEFAULT`

ButtonPushed=Dialog("MyDialog")
Exit

;***************************************************************************
;***************************************************************************
;***************************************************************************


:UDFS

#DefineFunction EnumGACAssemblies( type ) ; TYPE: 0 = tab delimited list, 1 = array
   output = FileCreateTemp( 'GAC' )
   gacutil = 'C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\gacutil.exe'; FileLocate('gacutil.exe')
   ;DirChange( FilePath(gacutil) )
   If !FileExist( gacutil )
      Pause( 'EnumGACAssemblies Error', 'Unable to locate gacutil.exe' )
      Exit
   Endif
   assemblyname = ''
   CaptureArray=CaptureDosOutput(`cmd.exe /c "`:gacutil:`" /l `:assemblyname)      ; for built-in dos commands
   ;Pause(StrCat("Exit code: ",CaptureArray[0]),CaptureArray[1])
   If CaptureArray[0] !=0
      Pause("CaptureDosOutput Error",CaptureArray[2])
      Exit
   Endif
   If !FileExist( output )
      Pause( 'EnumGACAssemblies Error', 'Unable to generate output' )
      Exit
   Endif
   gaclist = CaptureArray[1]
   gaclist = StrReplace( gaclist, @crlf:@crlf, @tab )
   gaclist = StrReplace( gaclist, @crlf, @tab )
   For x = 1 to 3
      gaclist = ItemRemove( 1, gaclist, @tab )
   Next
   gaclist = ItemRemove( -1, gaclist, @tab )
   gaclist = ItemRemove( -1, gaclist, @tab )
   

   If type == 1
      gaclist = Arrayize( gaclist, @tab )
   Endif
   ;AskItemList(count, gaclist, @tab, @unsorted, @single )   
   FileDelete( output )
   Return gaclist
#ENDFunction


#DefineFunction CaptureDosOutput(DosCommand)
   
   DataArray=ArrDimension(3)               ;Allocate return array
   ArrInitialize(DataArray,"")             ;initialize to all null strings
   oShell = ObjectOpen("WScript.Shell")    ;open shell object
   oScriptExec = oShell.Exec(DosCommand)   ;run the command

   ;Open output objects
   oStdOut = oScriptExec.StdOut
   oStdErr = oScriptExec.StdErr

   While (oScriptExec.Status==0)           ;wait for completion

      ;Caputure StdOut data
      oStdOut = oScriptExec.StdOut
      While ! oStdOut.AtEndOfStream
         strLine = oStdOut.ReadLine
         DataArray[1] = StrCat(DataArray[1],strLine,@CRLF)
      EndWhile
   
      ;Capture StdErr data
      oStdErr = oScriptExec.StdErr
      While ! oStdErr.AtEndOfStream
         strLine = oStdErr.ReadLine
         DataArray[2] = StrCat(DataArray[2],strLine,@CRLF)
      EndWhile
   
      TimeDelay(0.1)
   EndWhile

   ;Get remainder of data, if any

      ;Caputure StdOut data
      oStdOut = oScriptExec.StdOut
      While ! oStdOut.AtEndOfStream
         strLine = oStdOut.ReadLine
         DataArray[1] = StrCat(DataArray[1],strLine,@CRLF)
      EndWhile
   
      ;Capture StdErr data
      oStdErr = oScriptExec.StdErr
      While ! oStdErr.AtEndOfStream
         strLine = oStdErr.ReadLine
         DataArray[2] = StrCat(DataArray[2],strLine,@CRLF)
      EndWhile


   DataArray[0]=oScriptExec.ExitCode         ;save errorlevel/exit code

   ;Close handles that were opened
   ObjectClose(oStdOut)
   ObjectClose(oStdErr)
   ObjectClose(oScriptExec)
   ObjectClose(oShell)

   ;Return the array
   Return(DataArray)

#EndFunction
RETURN
Deana F.
Technical Support
Wilson WindowWare Inc.

JTaylor

Just wanted to say thanks again for this thread.   Was able to create/run a needed Method internally, removing the inclusion of the DLL I had created for the purpose and the requisite registering as well.

Jim