Page 1 of 11

.NET Framework Interop (CLR, C#, VB)

Posted: 19 Sep 2014, 19:10
by lexikos
Microsoft Common Language Runtime / .NET Framework Interop

CLR.ahk v1.2 for AutoHotkey v1.1
CLR.ahk v2.0 for AutoHotkey v2.0-beta.1
License: public domain / CC0

Key Features:
  • Load the Common Language Runtime into the script's process.
  • Load .NET assemblies (dll files) by full name, partial name, or path.
  • Instantiate objects and call instance methods or properties.
  • Compile C# or VB code on the fly or to file.

Functions

The usage shown here is for CLR.ahk v1.2, AutoHotkey v1.1.

CLR_Start( [ RuntimeVersion ] )
Loads the Common Language Runtime. RuntimeVersion specifies the exact version to load - for example, "v2.0.50727" or "v4.0.30319". If omitted, the latest version is loaded. If this function is not called and another CLR function requires the runtime to be loaded, the latest version will be loaded.

CLR_StartDomain( ByRef AppDomain [, BaseDirectory ] )
Starts a new AppDomain and stores a pointer or reference to it in AppDomain. This can be passed to CLR_LoadLibrary() to load an assembly into the AppDomain. BaseDirectory defines the base search path used when loading assemblies into the AppDomain.

CLR_StopDomain( AppDomain )
Stops the specified AppDomain and attempts to unload any assemblies that were loaded into it.

CLR_LoadLibrary( AssemblyName [, AppDomain ] )
Loads an assembly, where AssemblyName is its full name, partial name or path. Optionally loads the assembly into the given AppDomain instead of the default AppDomain. Returns a pointer or reference to the Assembly, which can be used with CLR_CreateObject.
Note: Once an assembly is loaded, it can only be unloaded by stopping the AppDomain which contains it.

CLR_CreateObject( Assembly, sType [, Arg1, Arg2 ... ] )
Instantiates an object of the specified type from the specified assembly. Optionally accepts a list of arguments to pass to the object's constructor. Use ComObject(Type, Arg) to pass a typed value. A list of type codes can be found here. Alternatively, you can call Assembly.CreateInstance(sType) directly if you do not need to pass parameters.

CLR_CompileC#( Code, References [, AppDomain, FileName, CompilerOptions ] )
CLR_CompileVB( Code, References [, AppDomain, FileName, CompilerOptions ] )
Compile the specified C# or VB code. If FileName is omitted, the assembly is compiled "in-memory" and automatically loaded. DLL and EXE files may be generated. Specify for References a pipe (|) delimited list of assemblies that the code requires. If FileName is omitted and compilation is successful, returns a pointer or reference to the compiled Assembly, which can be used with CLR_CreateObject; otherwise returns FileName on success or 0 on failure.
Note: Some versions of .NET may require an explicit reference to the appropriate language dll, such as Microsoft.CSharp.dll.

Additional command-line arguments can be passed to the compiler via CompilerOptions. For instance, if FileName specifies an .exe file, a console app is generated unless CompilerOptions includes "/target:winexe".


See the original thread for older discussion.

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 21 Sep 2014, 02:14
by lexikos
Simple C# example:

Code: Select all

c# =
(
    using System.Windows.Forms;
    class Foo {
        public void Test() {
            MessageBox.Show("Hello, world, from C#!");
        }
    }
)
asm := CLR_CompileC#(c#, "System.dll | System.Windows.Forms.dll")
obj := CLR_CreateObject(asm, "Foo")
obj.Test()
Simple VB example:

Code: Select all

vb =
(
    Imports System.Windows.Forms
    Class Foo
        Public Sub Test()
            MessageBox.Show("Hello, world, from VB!")
        End Sub
    End Class
)
asm := CLR_CompileVB(vb, "System.dll | System.Windows.Forms.dll")
obj := CLR_CreateObject(asm, "Foo")
obj.Test()

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 21 Sep 2014, 02:32
by lexikos
Event Handling

Handling events can be a little easier with AutoHotkey v1.1 than in AutoHotkey v1.0 thanks to SAFEARRAY support. There are also some techniques that I've learnt about since writing CLR.ahk v1.0, or that were introduced in later .NET Framework versions. These are demonstrated in the C# code below.

There are two methods of handling an event demonstrated below:
  1. For events of type EventHandler, MakeHandler can be used. This simply wraps our callback pointer in a delegate, then creates and returns an anonymous function which wraps the parameters in an array and passes them to our callback via the delegate. Since this only covers events which use the EventHandler delegate type, you would need to basically copy the MakeHandler method for each event type.
  2. For other events, we can call AddHandler, passing the target object, event name and callback pointer. AddHandler dynamically generates a CLR delegate similar to MakeHandler's anonymous function, but matching the signature of the event. This method is quite a bit longer.
In both cases, parameters are passed to our callback via a pointer to a SAFEARRAY, to make things easy. The callback pointer is passed as a string, since COM automation/IDispatch does not support 64-bit integers.

Code: Select all

; Compile the helper class. This could be pre-compiled.
FileRead c#, EventHelper.cs
helperAsm := CLR_CompileC#(c#)
helper := helperAsm.CreateInstance("EventHelper")

; Create our test object, which simply exposes a single event.
c# =
(
    public class ObjectWithEvent {
        public void RaiseEvent() {
            if (OnEvent != null)
                OnEvent(this, System.EventArgs.Empty);
        }
        public event System.EventHandler OnEvent;
    }
)
asm := CLR_CompileC#(c#)
obj := asm.CreateInstance("ObjectWithEvent")

; Add an event handler for the "OnEvent" event.  Use "" to pass the
; address as a string, since IDispatch doesn't support 64-bit integers.
helper.AddHandler(obj, "OnEvent", "" RegisterCallback("EventHandler",,, 1))

; Make an event handler (event must be of the EventHandler type).
handler := helper.MakeHandler("" RegisterCallback("EventHandler",,, 2))
obj.add_OnEvent(handler)

; Test the event handlers.
obj.RaiseEvent()


; Our event handler is called with a SAFEARRAY of parameters.  This
; makes it much easier to get the type and value of each parameter.
EventHandler(pprm)
{
    ; Wrap the SAFEARRAY pointer in an object for easy access.
    prm := ComObject(0x200C, pprm)
    ; Show parameters:
    MsgBox, 0, Event Raised, % "Callback #" A_EventInfo
        . "`nSender: " prm[0].ToString()
        . "`nEventArgs: " prm[1].ToString()
}

Code: Select all

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

public class EventHelper {
    // Delegate type for the AutoHotkey callback.
    public delegate void CallbackType([MarshalAs(UnmanagedType.SafeArray)] object[] argv);
    // AddHandler: Adds a callback as a handler for the given event of the given object.
    public void AddHandler(object target, string eventName, string pcb) {
        var cb = ParseCB(pcb);
        // Reference: http://msdn.microsoft.com/en-us/library/ms228976
        EventInfo evt = target.GetType().GetEvent(eventName);
        Type handlerType = evt.EventHandlerType;
        MethodInfo handlerSig = handlerType.GetMethod("Invoke");
        ParameterInfo[] parameters = handlerSig.GetParameters();
        Type[] parameterTypes = new Type[parameters.Length+1];
        parameterTypes[0] = typeof(CallbackType);
        for (int i = 0; i < parameters.Length; i++)
            parameterTypes[i+1] = parameters[i].ParameterType;
        
        var handler = new DynamicMethod("", handlerSig.ReturnType, parameterTypes, true);
        
        var il = handler.GetILGenerator();
        var loc = il.DeclareLocal(typeof(object[]));
        il.Emit(OpCodes.Ldc_I4_2);
        il.Emit(OpCodes.Newarr, typeof(object));
        il.Emit(OpCodes.Stloc_0);
        
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Stelem_Ref); 
        
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Ldarg_2);
        il.Emit(OpCodes.Stelem_Ref);
        
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Call, typeof(CallbackType).GetMethod("Invoke"));
        il.Emit(OpCodes.Ret);
        
        var delg = handler.CreateDelegate(handlerType, cb);
        var adder = evt.GetAddMethod();
        adder.Invoke(target, new object[] { delg });
    }
    // Much simpler method, restricted to a specific delegate type.
    public EventHandler MakeHandler(string pcb) {
        var cb = ParseCB(pcb);
        return (sender, e) => cb(new object[]{ sender, e });
    }
    public CallbackType ParseCB(string cb) {
        // For 32-bit, simply marking the parameter of AddHandler/MakeHandler with:
        //   [MarshalAs(UnmanagedType.FunctionPtr)] CallbackType cb
        // is adequate, but since IDispatch doesn't support 64-bit integers,
        // we have to pass the callback address as a string for x64 builds.
        return (CallbackType) Marshal.GetDelegateForFunctionPointer(
            (IntPtr)Int64.Parse(cb), typeof(CallbackType));
    }
}

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 13 Oct 2014, 22:12
by joedf
Thanks for this. You should put this on GitHub too.

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 31 Oct 2014, 14:10
by BGM
This looks interesting! I know some c#.
The basic demo does indeed produce the messagebox! I am impressed!
So I decided to try using c# to navigate xml, so I hooked up this code, and it gives me this error:
Compilation Failed
Error CS0006 on line 0: Metadata file 'System.Xml.Linq' could not be found
How do I resolve *that*? I am guessing I need an explicit reference to the assembly. How do I add that?

Code: Select all


;http://stackoverflow.com/questions/7119806/c-sharp-reading-data-from-xml

#include clr.ahk

c# =
(
    using System;
    using System.Windows.Forms;
    using System.Xml.Linq;
    class Foo {
        public void Test() {
            MessageBox.Show("Ready for XML from C# via Autohotkey?");
        }

		public void ahkxml(){
			XDocument doc = XDocument.Load( "polycom-test-cfg.cfg" );
			var info = doc.Descendants( "reg" );
			foreach ( var thisinfo in info ){
				MessageBox.Show(thisinfo.Value);
			}     
		}
		
    }
)
asm := CLR_CompileC#(c#, "System.dll | System.Windows.Forms.dll | System.Xml.Linq")
obj := CLR_CreateObject(asm, "Foo")
obj.Test()
obj.ahkxml()

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 05 Apr 2015, 19:19
by TLM
Simple examples for creating/declaring variable types and return their values from C# to AHk
All of my examples assume you've used #include for the CLR library.

Code: Select all

; Return a string type
cSharp =
(
    class MainReturnValTest
    {
        public string Main()
        {
            string var = "some string";
            return var;
        }
    }
)

obj := CLR_CreateObject( CLR_CompileC#( cSharp ), "MainReturnValTest")
msgbox % obj.Main()

Code: Select all

; Return an integer type
cSharp =
(
    class MainReturnValTest
    {
        public int Main()
        {
            int n = 5;
            return n;
        }
    }
)

obj := CLR_CreateObject( CLR_CompileC#( cSharp ), "MainReturnValTest")
msgbox % obj.Main()

Code: Select all

; Return both string and integer types
cSharp =
(
    class MainReturnValTest
    {
        private int Integer()
        {
            int n = 5;
            return n;
        }

        public string Main()
        {
            string str = "High ";
            return str + Integer();
        }
    }
)

obj := CLR_CreateObject( CLR_CompileC#( cSharp ), "MainReturnValTest")
msgbox % obj.Main()

Code: Select all

; Return string and decimal types
cSharp =
(
    class MainReturnValTest
    {
        private decimal Decimal()
        {
            decimal d = 5.3m; // decimals must be appended with M or m suffix
            return d;
        }

        public string Main()
        {
            string str = "Decimal ";
            return str + Decimal();
        }
    }
)

obj := CLR_CreateObject( CLR_CompileC#( cSharp ), "MainReturnValTest")
msgbox % obj.Main()
I will keep posting simple examples as I learn more.
Hope this helps people out some.

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 05 Apr 2015, 20:18
by IMEime
Wow, Looks good.
I am going to give it a try.
Thanks, nice post.

(So, Main method returns "string"? interesting..)

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 06 Apr 2015, 12:37
by TLM
Compile an executable in C#

Code: Select all

; Compile an executable in C#
cSharp =
(
    using System;
    namespace HelloWorld
    {
        class Hello 
        {
            static void Main() 
            {
                Console.WriteLine("Hello World!");

                // Keep the console window open in debug mode.
                Console.WriteLine("Press any key to exit.");
                Console.ReadKey();
            }
        }
    }
)

FileName := A_ScriptDir "\HelloWorld.exe"

CLR_CompileC#( cSharp, "System.dll",, FileName ) 

Run % FileName
Creating a console application will be of use to display many examples found on msdn.

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 06 Apr 2015, 15:02
by TLM
Compiling executable that excepts arguments

Code: Select all

; Pass arguments to compiled executable
cSharp =
(
    using System;
    namespace HelloWorld
    {
        class Hello 
        {
            static void Main(string[] arr) 
            {
                for (int i = 0; i < arr.Length; i++)
                {
                    Console.Write(arr[i] + "{0}", i < arr.Length - 1 ? " " : "");
                }
                Console.WriteLine();

                // Keep the console window open in debug mode.
                Console.WriteLine("Press any key to exit.");
                Console.ReadKey();
            }
        }
    }
)

FileName := A_ScriptDir "\HelloWorld.exe"

CLR_CompileC#( cSharp, "System.dll",, FileName ) 

RunWait % FileName " arg1 arg2"
Run % FileName " Some more arguments"
Note: The Using keyword directive allows for each use of Console to only need the class rather than having to attach System also.
For more info see Directives
htms

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 06 Apr 2015, 19:02
by IMEime
I tried two codes.

This one is fine.

Code: Select all

Kodes =
(
    class Tecting
    {
        public string Main()
        {
            int ABC = 123 ;
            return ABC.ToString( "000###" )  ;
        }
    }
)
Ovject := CLR_CreateObject( CLR_CompileC#( Kodes ), "Tecting")
Msgbox % Ovject.Main()
And, the second one, I had namespace error.

Code: Select all

Sample =
(
using System.Windows.Forms;
    class Testing
    {
        public void Main()
        {
            MessageBox.Show( "a" ) ;
        }
    }
)
Ovject := CLR_CreateObject( CLR_CompileC#( Sample ), "Testing")
Ovject.Main()
(And, This one also..
using System.Text.RegularExpressions;
)

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 06 Apr 2015, 19:32
by HotKeyIt
This is an example from old forum:

Code: Select all

c# =
(
    using System.Windows.Forms;
    class Foo {
        public void Test() {
            MessageBox.Show("Hello, world, from C#!");
        }
    }
)
CLR_Start()

asm := CLR_CompileC#(c#, "System.dll | System.Windows.Forms.dll")
obj := CLR_CreateObject(asm, "Foo")
obj.Test()

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 06 Apr 2015, 20:36
by IMEime
Ho,
you made it without using "Main" method.
(Looks like some difficult to me though)

I will give it a try.
Thanks.

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 06 Apr 2015, 22:46
by TLM
IMEime wrote:I tried two codes.... the second one, I had namespace error.
Its because you need to reference the System.Windows.Forms assembly System.Windows.Forms.dll to use messagebox :)
HotKeyIt wrote:This is an example from old forum:

Code: Select all

;...
CLR_Start()
;...
only needed to specify the runtime version of .net

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 07 Apr 2015, 04:14
by IMEime
Hmm..
How about this ?

My final goal is to make some Excel & Word Ribbon handling codes.
It has bunch of usings.
Do I need to wirte down all and every usings and dlls somewhere else?

Code: Select all

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.IO;										
using System.Diagnostics;								
using System.Windows.Forms;				
using System.Runtime.InteropServices;			
using System.Text.RegularExpressions;		
using aEXL = Microsoft.Office.Interop.Excel ;
using aWRD = Microsoft.Office.Interop.Word ;
using aCOR = Microsoft.Office.Core;			
Something like this ?

Code: Select all

asm := CLR_CompileC#(c#, "System.dll 
| System.Collections.Generic.dll
| System.ComponentModel.dll
| ...
| ...dll")

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 07 Apr 2015, 09:47
by TLM
Manipulate console colors

Code: Select all

; Change Fore/Background console colors
c# =
(
using System;
class ChangeColors
{
    public static void Main() 
    {
        ConsoleColor[] colors = (ConsoleColor[]) ConsoleColor.GetValues(typeof(ConsoleColor));
        foreach (var color in colors)
        {
            if ( color == colors[ 0 ] ) continue;
            else
            {
                Console.BackgroundColor = colors[ 0 ]; Console.ForegroundColor = color;
                Console.WriteLine( "ForegroundColor: {0}", color );

                Console.ForegroundColor = colors[ 0 ]; Console.BackgroundColor = color;
                Console.WriteLine( "BackgroundColor: {0}", color ); 
            }
        }
        Console.ReadKey();
    }
}
)

CLR_CompileC#( c#,,, FileName := A_ScriptDir "\Colors.exe" )
Run % FileName

Code: Select all

; Change entire background color
c# =
(
using System;
class ChangeBackgroundColor
{
    public static void Main() 
    {
        ConsoleColor[] colors = (ConsoleColor[]) ConsoleColor.GetValues(typeof(ConsoleColor));
        int ColorsLeft = ( colors.Length-2 );

        foreach (var color in colors)
        {
            if ( color != colors[ 0 ] )
            {
                Console.ForegroundColor = colors[ 0 ]; Console.BackgroundColor = color;
                Console.Clear();   // Used to color the entire buffer 
                Console.WriteLine( "BackgroundColor: {0}",  color );
                Console.WriteLine( "Colors left: {0}. Press any key to continue...", ColorsLeft-- );
                Console.ReadKey();
           }
        }
    }
}
)

CLR_CompileC#( c#,,, FileName := A_ScriptDir "\BGColors.exe" )
Run % FileName

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 08 Apr 2015, 17:30
by TLM
pass a string to SendWait() ( C# Send )

Code: Select all

c# =
(
using System.Windows.Forms;
class KeyStrokes
{
    public void Main(string var) 
    {
        Keys( var );
    }

    private void Keys(string var) // recommeded approach
    {
        SendKeys.SendWait( var );
    }
}
)

csObj := CLR_CreateObject( CLR_CompileC#( c#, "System.dll | System.Windows.Forms.dll" ), "KeyStrokes" )

csObj.Main( "Send this string.." )

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 09 Apr 2015, 19:31
by IMEime
RegEx.

Code: Select all

Kodes =
(
    using System.Windows.Forms;
    using System.Linq;                          
    using System.Collections.Generic;                   
    using System.Text.RegularExpressions;               
    class Klass
    {
        public void Mesod()
        {
            string KnTs = @"
            abc123def
            abc1234def
            abc1def
            abc12def
            ";  
            var Arrey = Regex.Matches( KnTs, @".*?(\d+).*?")
                        .Cast<Match>()
                        .Select( a => a.Groups[1].Value)
                        .OrderByDescending( a => a.Length);
            MessageBox.Show ( string.Join("\n", Arrey )) ;  
        }
    }
)
Leferences = 
(	Join|
	System.Windows.Forms.dll
	System.Core.dll
	mscorlib.dll
	System.dll
)
CLR_CreateObject( CLR_CompileC#( Kodes, Leferences), "Klass").Mesod()

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 23 May 2015, 18:28
by TLM
Return character descriptions

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 23 May 2015, 19:07
by IMEime
TLM>
Thanks for the nice codes.

I have errors for a Chinese string;

"一二三"
0x4E00 一 (One)
0x4E8C 二 (Two)
0x4E09 三 (Three)

Any good tips ?

Error descriptions is something like this;
"There are not resource name in image file."

Re: .NET Framework Interop (CLR, C#, VB)

Posted: 23 May 2015, 20:25
by TLM
Only the 1st character is available from that resource.
Image
CharSet.Unicode does nothing..
I'm trying to figure where to get other character descriptions.