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

Post your working scripts, libraries and tools for AHK v1.1 and older
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

19 Sep 2014, 19:10

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.
Last edited by lexikos on 28 Aug 2021, 01:52, edited 1 time in total.
Reason: Add v2.0
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

21 Sep 2014, 02:14

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()
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

21 Sep 2014, 02:32

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));
    }
}
User avatar
joedf
Posts: 8940
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

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

13 Oct 2014, 22:12

Thanks for this. You should put this on GitHub too.
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
User avatar
BGM
Posts: 507
Joined: 20 Nov 2013, 20:56
Contact:

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

31 Oct 2014, 14:10

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()
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

05 Apr 2015, 19:19

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.
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

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

05 Apr 2015, 20:18

Wow, Looks good.
I am going to give it a try.
Thanks, nice post.

(So, Main method returns "string"? interesting..)
Last edited by IMEime on 05 Jul 2017, 18:44, edited 1 time in total.
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

06 Apr 2015, 12:37

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.
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

06 Apr 2015, 15:02

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
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

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

06 Apr 2015, 19:02

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;
)
Last edited by IMEime on 05 Jul 2017, 18:44, edited 1 time in total.
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

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

06 Apr 2015, 19:32

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()
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

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

06 Apr 2015, 20:36

Ho,
you made it without using "Main" method.
(Looks like some difficult to me though)

I will give it a try.
Thanks.
Last edited by IMEime on 05 Jul 2017, 18:44, edited 1 time in total.
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

06 Apr 2015, 22:46

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
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

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

07 Apr 2015, 04:14

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")
Last edited by IMEime on 05 Jul 2017, 18:44, edited 1 time in total.
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

07 Apr 2015, 09:47

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
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

08 Apr 2015, 17:30

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.." )
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

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

09 Apr 2015, 19:31

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()
Last edited by IMEime on 05 Jul 2017, 18:45, edited 1 time in total.
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

23 May 2015, 18:28

Return character descriptions
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

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

23 May 2015, 19:07

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."
User avatar
TLM
Posts: 1608
Joined: 01 Oct 2013, 07:52
Contact:

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

23 May 2015, 20:25

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.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 140 guests