Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

.NET Framework Interop


  • Please log in to reply
155 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Microsoft Common Language Runtime / .NET Framework Interop

CLR (for AutoHotkey), CLR_L (for AutoHotkey_L)
public domain

CLR (not CLR_L) requires COM Standard Library

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

CLR_Start() (CLR)
CLR_Start( [ RuntimeVersion ] ) (CLR_L)
Loads the Common Language Runtime.
CLR_L only: 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 [, Type1, Arg1, Type2, Arg2 ... ] ) (CLR)
CLR_CreateObject( Assembly, sType [, Arg1, Arg2 ... ] ) (CLR_L)
Instantiates an object of the specified type from the specified assembly. Optionally accepts a list of arguments to pass to the object's constructor. For AutoHotkey Basic, TypeN is a value from the VARENUM enumeration. For AutoHotkey_L, use ComObject(Type, Arg) to pass a typed value. A list of type codes can be found in the AutoHotkey_L documentation.
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.

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".
Examples

Additional Functions
Not included in CLR.ahk since they generally aren't of use.

CLR_GetVersion( ByRef sVer )
If the Common Language Runtime is loaded, gets the version in use. According to MSDN, it should otherwise get the latest installed version. However, on my Windows 7 system with both v2.0.50727 and v4.0.30319, it returns v2.0.50727.
CLR_GetAssemblies( [ pAppDomain ] )
Gets a `n-delimited list of assemblies loaded into the specified AppDomain (or the default AppDomain if not specified.) This function is not supported with CLR_L v1.2 or later. Instead, use the much simpler code shown further below.
; AutoHotkey Basic users need StrGet.ahk:
; http://www.autohotkey.com/forum/topic59738.html

; Gets either the CLR version in use, or the latest installed version.
CLR_GetVersion(ByRef sVer)
{
    VarSetCapacity(wsVer, 40)
    hr := DllCall("MSCorEE\GetCORVersion",A_PtrSize ? "ptr":"uint",&wsVer,"uint",20,"uint*",ccVer)
    sVer := StrGet(&wsVer,ccVer,"UTF-16")
    return hr
}

; Lists the assemblies loaded into an AppDomain.
CLR_GetAssemblies(pAppDomain=0)
{
    pApp := pAppDomain ? pAppDomain : CLR_GetDefaultDomain()
    if !pApp
        return
    if p_App := COM_QueryInterface(pApp,"{05F696DC-2B29-3663-AD8B-C4389CF2A713}")
    {
        DllCall(NumGet(NumGet(p_App+0)+228),"uint",p_App,"uint*",pa_Asm)
        DllCall("oleaut32\SafeArrayGetUBound","uint",pa_Asm,"uint",1,"int*",ubound)
        Loop, % ubound+1
        {
            DllCall("oleaut32\SafeArrayGetElement","uint",pa_Asm,"int*",A_Index-1,"uint*",p_Asm)
            DllCall(NumGet(NumGet(p_Asm+0)+112),"uint",p_Asm,"uint*",psAsm)
            list .= StrGet(psAsm,DllCall("oleaut32\SysStringLen","uint",psAsm),"UTF-16") "`n"
            COM_Release(p_Asm), COM_SysFreeString(psAsm)
        }
        DllCall("oleaut32\SafeArrayDestroy","uint",pa_Asm)
    }
    if (pAppDomain != pApp)
        COM_Release(pApp)
    return SubStr(list,1,-1)
}
; Example: Enumerating loaded assemblies using CLR_L.
for assembly in CLR_GetDefaultDomain().GetAssemblies()
    MsgBox % assembly.Location

2009/09/21 - Added list of links to examples.
2010/01/10 - Added CLR_L.
2010/06/03 - Corrected CLR_CreateObject documentation for CLR_L.
2011/02/16 - Added RuntimeVersion parameter to CLR_Start and changed default behaviour to load latest runtime version. Fixed CLR_LoadLibrary to work with file path or partial name on .NET v4.
2011/02/17 - Updated CLR_GetVersion and CLR_GetAssemblies.
2011/03/31 - v1.2 - Updated CLR_L to take advantage of native COM support.


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
First Example
The following is a translation of some sample code accompanying XPTable. The sample code (and XPTable itself) can be found at:
XPTable - .NET ListView meets Java's JTable (@ The Code Project)
#NoEnv
SetWorkingDir, %A_ScriptDir%

CLR_Start()
pasm := CLR_LoadLibrary("XPTable.dll")

; Type names must be fully qualified.
table       := CLR_CreateObject(pasm,"XPTable.Models.Table")
columnModel := CLR_CreateObject(pasm,"XPTable.Models.ColumnModel")
tableModel  := CLR_CreateObject(pasm,"XPTable.Models.TableModel")

; Prefix "+" to pass objects (IDispatch),
COM_Invoke(table,"ColumnModel=","+" columnModel)
; or use COM_Invoke_ and pass the type explicitly. Generally IUnknown (13)
; and IDispatch (9) are interchangeable, but maybe not for interfaces with
; this attribute: [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
COM_Invoke_(table,"TableModel=",13,tableModel)

Columns := COM_Invoke(columnModel,"Columns")
COM_Invoke_(Columns,"Add", 13,col1:=CLR_CreateObject(pasm,"XPTable.Models.TextColumn",8,"Text"))
COM_Invoke_(Columns,"Add", 13,col2:=CLR_CreateObject(pasm,"XPTable.Models.CheckBoxColumn",8,"CheckBox"))
COM_Invoke_(Columns,"Add", 13,col3:=CLR_CreateObject(pasm,"XPTable.Models.ButtonColumn",8,"Button"))

; Object references must be released explicitly.
COM_Release(col1), COM_Release(col2), COM_Release(col3)
COM_Release(Columns)

Rows := COM_Invoke(tableModel,"Rows")

; The C# code:
;   tableModel.Rows[0]
; is roughly equivalent to:
;   Rows := COM_Invoke(tableModel,"Rows")
;   Rows0 := COM_Invoke(Rows,"Item",0)
;   COM_Release(Rows)
; Since we are creating the Row object, we don't need to use the above.

COM_Invoke_(Rows,"Add"
    , 13,row:=CLR_CreateObject(pasm,"XPTable.Models.Row"))
Cells := COM_Invoke(row,"Cells")
COM_Release(row)
COM_Invoke_(Cells,"Add"
    , 13,cel1:=CLR_CreateObject(pasm,"XPTable.Models.Cell",8,"Text 1"))
COM_Invoke_(Cells,"Add"
    , 13,cel2:=CLR_CreateObject(pasm,"XPTable.Models.Cell",8,"CheckBox 1",11,true))
COM_Invoke_(Cells,"Add"
    , 13,cel3:=CLR_CreateObject(pasm,"XPTable.Models.Cell",8,"Button 1"))
COM_Release(cel1), COM_Release(cel2), COM_Release(cel3)
COM_Release(Cells)

COM_Invoke_(Rows,"Add", 13,row:=CLR_CreateObject(pasm,"XPTable.Models.Row"))
Cells := COM_Invoke(row,"Cells")
COM_Release(row)
COM_Invoke_(Cells,"Add"
    , 13,cel1:=CLR_CreateObject(pasm,"XPTable.Models.Cell",8,"Text 2"))
COM_Invoke_(Cells,"Add"
    , 13,cel2:=CLR_CreateObject(pasm,"XPTable.Models.Cell",8,"CheckBox 2",11,false))
COM_Invoke_(Cells,"Add"
    , 13,cel3:=CLR_CreateObject(pasm,"XPTable.Models.Cell",8,"Button 2"))
COM_Release(cel1), COM_Release(cel2), COM_Release(cel3)
COM_Release(Cells)

COM_Release(Rows)

COM_Release(tableModel), COM_Release(columnModel)

; END CODE BASED ON CODEPROJECT SAMPLE

COM_Invoke(table,"Left=",10)
COM_Invoke(table,"Top=",10)
COM_Invoke(table,"Width=",300)
COM_Invoke(table,"Height=",200)

hwnd := COM_Invoke(table,"Handle")

Gui, +LastFound
DllCall("SetParent","uint",hwnd,"uint",WinExist())
Gui, Show, W320 H220, XPTable

return

GuiClose:
ExitApp
I haven't yet been able to hook events of .NET objects...

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Thx m8, this is really kewl. Will check it out in a second.

Didn't know about XPTable, but its sure valuable thing to have.

Talking about .Net, I developed IPC module for C# (with exactly same API and usage as AHK version) so if ppl are interested I can upload it. With it, u can talk C# <-> AHK and C# <-> C#
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Heh, you thought that was cool. Now I've added on-the-fly compiling of C# and VB code. 8)

Second Example
c# =
(
    using System.Windows.Forms;
    class Foo {
        public void Test() {
            MessageBox.Show("Hello, world, from C#!");
        }
    }
)
vb =
(
    Imports System.Windows.Forms
    Class Foo
        Public Sub Test()
            MessageBox.Show("Hello, world, from VB!")
        End Sub
    End Class
)

CLR_Start()

asm := CLR_CompileC#(c#, "System.dll | System.Windows.Forms.dll")
obj := CLR_CreateObject(asm, "Foo")
COM_Invoke(obj, "Test")
COM_Release(obj)
; Note: This doesn't unload the Assembly itself, just frees the Assembly object.
COM_Release(asm)

asm := CLR_CompileVB(vb, "System.dll | System.Windows.Forms.dll")
obj := CLR_CreateObject(asm, "Foo")
COM_Invoke(obj, "Test")
COM_Release(obj), COM_Release(asm)


majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
OMG!

Currently it issues this message.

---------------------------
1.ahk
---------------------------
Error at line 1 in #include file "c:\Utils\AutoHotkey\Lib\CLR.ahk".

Line Text: CLR_Start()
Error: Duplicate function definition.

The program will exit.
---------------------------
OK
---------------------------
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
:lol: You didn't update CLR.ahk, did you? I added CLR_CompileC#(), CLR_CompileVB() and CLR_CompileAssembly() after you posted.

(CompileAssembly() potentially supports other languages, like JScript and managed C++.)

Andreone
  • Members
  • 257 posts
  • Last active: Mar 19 2008 05:30 PM
  • Joined: 20 Jul 2007
Some more informations about XPtable: it is now an open-source project hosted on sourceforge.net XPTable - advanced data grid (lastest version is 1.1.7 - 08/03/07).

For those who have interest with XPtable, I recently found out another great c# control hosted on sourceforge: ZedGraph:

ZedGraph is a class library, user control, and web control for .net, written in C#, for drawing 2D Line, Bar, and Pie Charts. It features full, detailed customization capabilities, but most options have defaults for ease of use.

A stand-alone demo is available and it deserves to be checked out. :)

And last but not least, thank you lexikos for sharing your work. :D

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006

You didn't update CLR.ahk, did you?

Yes I did.... must be some cache bug with Opera. I was alrady experiencing it from time to time... I mean, I know that kind of error.

2Andreone
Thx for info
Posted Image

AHKnow*
  • Guests
  • Last active:
  • Joined: --
Hahahaha.... I knew you smart guys would be doing something like this, sooner or later. Another dream comes true C# and AHK working hand and hand.

Here is another thought.... Why can't AutoHotkey make ActiveX DLLs??? With this code or variation of it, using C#/.NET, you can. So why not advance the concept more?

Hmmmm... Maybe we could discuss thing in Chat.

Great work lexikos and company.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Yes, sounds like idea :!:

The bad thing is u are connected with dotNet framework and many ppl don't like to have it in the system, but I guess that will become very rare in the future.
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Here is another thought.... Why can't AutoHotkey make ActiveX DLLs??? With this code or variation of it, using C#/.NET, you can. So why not advance the concept more?

You could generate a DLL (from C# or VB source, not .ahk) which other applications may load, but you would not be able to load the AutoHotkey script itself into another application (i.e. via the DLL). It is literally just another .NET DLL, with no relation to AutoHotkey.

However, Sean recently demonstrated that it is possible to write a local (i.e. EXE) COM Server in AutoHotkey. (Follow the link and look for "AutoHotkeyVerb.zip".)

Anyway...
Handling Events
c# =
(
    using System;
    using System.Runtime.InteropServices;
    
    class ObjectWithEvent {
        public void RaiseEvent() {
            if (OnEvent != null)
                OnEvent(this, EventArgs.Empty);
        }
        public event EventHandler OnEvent;
        
        public Delegate Delegate4FuncPtr(uint ptr, Type t) {
            return Marshal.GetDelegateForFunctionPointer((IntPtr)ptr, t);
        }
    }
)

CLR_Start()

asm := CLR_CompileC#(c#, "System.dll")
obj := CLR_CreateObject(asm, "ObjectWithEvent")

asmCor := CLR_LoadLibrary("mscorlib")
; Get typeof(System.EventHandler)
tEH := COM_Invoke(asmCor, "GetType_2", "System.EventHandler")
; Create a .NET Delegate for the EventHandler() callback.
pEH := COM_Invoke(obj, "Delegate4FuncPtr", RegisterCallback("EventHandler"), "+" tEH)
; Register the event handler. (Events export add_event() and remove_event()).
COM_Invoke_(obj, "add_OnEvent", 13,pEH)
; Call the C# method which raises the event.
COM_Invoke(obj, "RaiseEvent")

; (Cleanup code omitted.)

ListVars
Pause

EventHandler(vt, junk1, sender, junk2, eventArgs)
{
    MsgBox, 0, Event Raised, % "sender: " COM_Invoke(sender,"ToString")
        . "`neventArgs: " COM_Invoke(eventArgs,"ToString")
}
Events raised by .NET objects cannot be handled by unmanaged code "directly." Instead, the unmanaged callback must be wrapped in a .NET Delegate. Fortunately, the .NET Framework 2.0 provides GetDelegateForFunctionPointer(). (Since it is not an instance method, it is much easier to call from C# than from AutoHotkey.)

You may notice that Delegate4FuncPtr() accepts a uint, but casts it to IntPtr. It seems that IntPtr is marshalled into VT_INT (22), but since VT_INT is marshalled into System.Int32, Invoke will always fail. (It would, however, be possible to call the function directly via the exported COM interface.)

Parameters of type System.Object, unless otherwise specified by [MarshalAsAttribute], are marshalled as VARIANT. This means that for every Object parameter in .NET code, 16 bytes are pushed onto the stack, which equals 4 parameters for the AHK callback. The low-word of the first parameter (vt) defines the COM VARTYPE of the variant. For most managed types, this should be 13 (VT_UNKNOWN.) If VT_UNKNOWN, the third parameter is a pointer to the IUnknown interface of the object (which can generally be COM_Invoke'd.)

Parameters of more specific reference types (such as System.EventArgs) are marshalled as a pointer to the relevant exported COM interface - i.e. 4 bytes = 1 parameter. (Usually this is an automatically-generated interface which can be COM_Invoke'd.)

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006

You could generate a DLL (from C# or VB source, not .ahk) which other applications may load, but you would not be able to load the AutoHotkey script itself into another application (i.e. via the DLL). It is literally just another .NET DLL, with no relation to AutoHotkey.

Well, thats not quite true. There are workarounds.

First, you can communicate with AHK script using IPC module. I have IPC C# part which can be used in DLL for communication. Then, u can create an AHK function that creates dll, and executes adequate AHK functions and receive results via IPC or some other message mechanism. This is similar to what JDN has done in its ASM dlls.

Ofcourse, the good side is that you can tweak DLL code from AHK, using C# language and add other kind of logic, even some more communication with AHK if needed etc...

I am posting you now C# IPC part, so you can try if you want. The usage is exactly the same as with AHK.

IPC.cs
// IPC module C# side
// by majkinetor
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MM_IPC
{
	/// <summary>
	/// Implementation of process communication via WM_COPYDATA
	/// </summary>
	public class IPC : NativeWindow
	{
		/// <summary> Delegate for IPC.OnMessage event. </summary>
		///	<param name="message">Message that was received</param>
		///	<param name="port">Port that received the message</param>
		public delegate void MessageAction(string message, int port);
	
		/// <summary>Event fired when message arrives</summary>
		public event MessageAction OnMessage;

		#region Private fields
		IntPtr		 id = new IntPtr(951753);
		const int	 WM_COPYDATA = 74;
		string		 strData;
			        			
		[StructLayout(LayoutKind.Sequential)]
			struct COPYDATASTRUCT
		{
			public int dwData;
			public int cbData;
			public int lpData;
		}
			
		COPYDATASTRUCT CD;
		#endregion

		#region Win32 imports
		[DllImport("user32.dll", SetLastError = true)]
		private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
		
		[DllImport("user32.dll",CharSet=CharSet.Ansi)]
		private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, ref COPYDATASTRUCT lParam);	
		#endregion
		
		///<summary> Creates IPC object. </summary>
		///<param name="host">Form object that will monitor and accept communication with other process</param>
		public IPC(Form host) 
		{
			this.AssignHandle(host.Handle);
		}

		
		///<summary> Find window by title </summary>
		///<param name="WinTitle">Window title, case insensitive</param>
		public static IntPtr WinExist( string WinTitle ) 
		{
			return FindWindow(null, WinTitle);
		}
		
		///<summary>Send the message to another process (receiver) using WM_COPYDATA.</summary>
		///<param name="hHost">Handle of the receiver</param>
		///<param name="msg">Message to be sent</param>
		///<param name="port">Port on which to send the message</param>
		public bool Send(IntPtr hHost, string msg, int port)
		{
			COPYDATASTRUCT cd = new COPYDATASTRUCT();
			cd.dwData = port;
			cd.cbData = msg.Length+1;
			cd.lpData = Marshal.StringToHGlobalAnsi(msg).ToInt32();
			
			//IntPtr pcd = Marshal.AllocCoTaskMem(Marshal.SizeOf(cd));	// Alocate memory
			//Marshal.StructureToPtr(cd, pcd, true);					// Converting structure to IntPtr
			int i = SendMessage(hHost, WM_COPYDATA, id, ref cd);	
			return i==1 ? true : false;
		}

		protected override void WndProc(ref Message m)
		{
			if((m.Msg==WM_COPYDATA) && (m.WParam == id))
			{
				CD = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
				strData = Marshal.PtrToStringAnsi(new IntPtr(CD.lpData), CD.cbData);
												
				if (OnMessage != null)
					OnMessage( strData, CD.dwData );

				return;
			}
			
			base.WndProc(ref m);
		}
	}
}

To use this in any C# app, you just add Using IPC.cs in the C# cod header. This is the simple test:

private void btnSend_Click(object sender, System.EventArgs e)
		{
			IntPtr hHost = IPC.WinExist("Client");
			if (!ipc.Send( hHost, "message", "101")  ///send message on port 101
				MessageBox.Show("Sending failed");
		}

This is the test app:
<!-- m -->https://ahknet.autoh...IPC/IPCTest.rar<!-- m -->

You can try AHk <-> C# communication with samples on IPC page:
<!-- m -->http://www.autohotke...topic21699.html<!-- m -->
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

You could generate a DLL (from C# or VB source, not .ahk) which other applications may load, but you would not be able to load the AutoHotkey script itself into another application (i.e. via the DLL). It is literally just another .NET DLL, with no relation to AutoHotkey.

Well, thats not quite true. There are workarounds.

Your workarounds involve starting a separate instance of AutoHotkey. What I said is perfectly accurate, but what I really meant to say about AHKnow's post...

With this code or variation of it, using C#/.NET, you can. So why not advance the concept more?

...was: what does this have to do with AutoHotkey or this script?

If your goal is to generate a .NET DLL for use in some process other than AutoHotkey, you don't even need my script. Given that the so-called "in-memory compiling" actually creates a number of temporary files in %A_Temp% (including a .cs and a .dll), it would probably be just as efficient to simply run the C# compiler (csc.exe), which I think is included with all .NET Framework installs.

Do you actually have a use for generating .NET DLLs? If so, I'd like to know what that is...

In any case, to generate a DLL I think all you need to do is remove this line:
COM_Invoke(compilerParms, "GenerateInMemory=", true)
and set the OutputAssembly property to the file name to generate. Also, you should be able to generate .exe files by setting GenerateExecutable to true.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006

Your workarounds involve starting a separate instance of AutoHotkey

I don't think so, but I don't want to argue.

Do you actually have a use for generating .NET DLLs?

Well, it can be there just for the sake of compliteness then :), especialy if there isn't much to be done about it, like you already noted :)
Posted Image

AHKnow
  • Members
  • 121 posts
  • Last active: May 17 2009 09:11 PM
  • Joined: 03 Jul 2004
My suggestion was meant along the lines of doing something "funky" like what majkinetor was suggesting.

A .NET DLL is a .NET DLL.

I meant having a compiled AHK.exe make use of functions from another compiled AHK.exe, have an AHK script compile a .NET DLL "on the fly" and in response to a variable and then communicate with it to carry out various tasks, or have an AHK script create and make use of an AcitveX DLL or something funky that is close to it.

By the way, I'm just spewing out some stuff. Lexikos, reflect on it at your leisure. I think what you are doing here is great.