What I meant was that the "ActiveX" DLL would be loaded into whatever process, but AutoHotkey.exe would also be running. I guess I don't see the point in communicating with/using AutoHotkey when the DLL is written in .NET. (I may not see the point, but it still interests me; read on...)I don't think so, but I don't want to argue.Your workarounds involve starting a separate instance of AutoHotkey
I've added a few new features to the script: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 notedDo you actually have a use for generating .NET DLLs?
[*:20hrbuay]CLR_StartDomain() now accepts an optional BaseDirectory, which is used to search for and load assemblies. (I've had problems with CLR looking for assemblies in %A_ProgramFiles%\AutoHotkey even though they're already loaded. :x BaseDirectory=A_WorkingDir solves that.)
[*:20hrbuay]CLR_CompileC# & 'VB now accept two additional optional parameters: FileName and CompilerOptions. If FileName is specified, a DLL or EXE is generated, but not loaded. Additional command-line arguments may be passed to the compiler via CompilerOptions. Of particular use, specify "/target:winexe" to generate a "windows" application rather than a (Windows) console application.
Since majkinetor mentioned inter-process communication earlier...
Remoting
.NET Remoting can be used to communicate with .NET objects in other processes (or even other computers) as though they are in the same process. The following demonstration is mostly C# based, but shows that the "proxy" for the remote object can be COM_Invoke'd the same as any other object. Requires .NET 2.0+ (but should run on earlier versions with minor modifications; see below.)
#NoEnv ; Base dll, defines the "interface" used to communicate. c#_dll_file = AhkDotNetRemoteDemoBase.dll c#_dll_refs = System.Windows.Forms.dll c#_dll = ( public abstract class RemoteDemoBase : System.MarshalByRefObject { public abstract void StdOut(string msg); public abstract void Exit(); } ) ; Server application. c#_app_file = AhkDotNetRemoteDemo.exe c#_app_refs = System.dll|System.Windows.Forms.dll|System.Runtime.Remoting.dll|%c#_dll_file% c#_app = ( using System; using System.Windows.Forms; using Remoting = System.Runtime.Remoting; class RemoteDemo : RemoteDemoBase { static string Caption = ""; // Entry-point for the application. static void Main() { //MessageBox.Show("Press OK to set up remoting."); Caption = "PID: " + System.Diagnostics.Process.GetCurrentProcess().Id; Remoting.Channels.ChannelServices.RegisterChannel( new Remoting.Channels.Ipc.IpcChannel("%A_ScriptName%")); Remoting.RemotingConfiguration.RegisterWellKnownServiceType( typeof(RemoteDemo), "RemoteDemo", Remoting.WellKnownObjectMode.Singleton); // Wait for exit message. Application.Run(); } // Methods accessible by client. // public override void StdOut(string msg) { Console.WriteLine(msg); } public override void Exit() { Application.Exit(); } } ) ; Client assembly. c#_client_refs = System.Runtime.Remoting.dll | %c#_dll_file% c#_client = ( using System; using Remoting = System.Runtime.Remoting; class RemoteDemoClient { RemoteDemoBase mRemoteDemo; public RemoteDemoClient() { Remoting.Channels.ChannelServices.RegisterChannel( new Remoting.Channels.Ipc.IpcChannel()); // Get a proxy for the remote object. mRemoteDemo = (RemoteDemoBase) Activator.GetObject( typeof(RemoteDemoBase), "ipc://%A_ScriptName%/RemoteDemo"); } public RemoteDemoBase RemoteDemo { get { return mRemoteDemo; } } public void Test() { try { mRemoteDemo.StdOut("test message."); } catch (Exception ex) { Console.WriteLine(ex.GetType().Name + " -- " + ex.Message); } } } ) OnExit, CloseAndDeleteServer CLR_Start() ; Start a new AppDomain so we can define the base path to load assemblies from. CLR_StartDomain(pApp, A_WorkingDir) ; Compile the interface assembly to be used by both server and client. CLR_CompileC#(c#_dll, c#_dll_refs, pApp, c#_dll_file) ; Compile the server application. CLR_CompileC#(c#_app, c#_app_refs, pApp, c#_app_file) ; Compile and load the client assembly. asm := CLR_CompileC#(c#_client, c#_client_refs, pApp) ; Start the server. Run, %c#_app_file%,, UseErrorLevel, c#_app_pid if !c#_app_pid { MsgBox Failed to launch %c#_app_pid%. ExitApp } ; TODO: Wait for a signal from EXE that it is ready. ; (The next two lines will work regardless, but any attempts to Invoke ; methods of 'remote' will fail until the EXE finishes initializing.) client := CLR_CreateObject(asm, "RemoteDemoClient") remote := COM_Invoke(client, "RemoteDemo") COM_Release(client) Loop { InputBox, text,,,,, 100 if ErrorLevel break COM_Invoke(remote, "StdOut", text) if (_hResult_) MsgBox Error: %_hResult_% } ExitApp CloseAndDeleteServer: if (remote) { COM_Invoke(remote, "Exit") COM_Release(remote) } CLR_StopDomain(pApp) ; unload c#_dll_file if c#_app_pid Process, WaitClose, %c#_app_pid% FileDelete, %c#_app_file% FileDelete, %c#_dll_file% ExitAppA basic summary of the script:
[*:20hrbuay]Generate a DLL which defines the abstract base class (RemoteDemoBase) of the object; i.e. the Invoke'able functions. This DLL is later loaded into the script and the server exe. This means the client and server need only load code that is relevant.
[*:20hrbuay]Generate an EXE which hosts the object (RemoteDemo) and its actual implementation. This is referred to as the server.
[*:20hrbuay]Generate and load an assembly which will be used to retrieve a proxy to the RemoteDemo object (as type RemoteDemoBase.) This is referred to as the client.
[*:20hrbuay]Start the generated EXE, and wait for the IpcChannel to initialize. Since IpcChannel uses a named pipe internally, the script simply waits for "\\.\pipe\channelname" to exist (Loop, IfExist, Sleep.)
[*:20hrbuay]Get a proxy to the RemoteDemo object. Note that only the metadata for the RemoteDemoBase abstract class is loaded into the script's process.
[*:20hrbuay]Receive input from the user, and Invoke the StdOut method of RemoteDemo(Base). Note that the COM-Callable Wrapper generated by .NET does not allow interface methods to be Invoked via IDispatch (hence the abstract base class rather than a true interface.)
[*:20hrbuay]When the user cancels, RemoteDemo.Exit() is Invoked. The AppDomain containing the client assembly is then unloaded so that the interface DLL can be deleted. The script waits for the server process to close, then deletes the generated EXE and DLL files.To interact with remote objects over a network, TcpChannel or HttpChannel must be used in place of IpcChannel. Using TcpChannel should make the script .NET 1.0-compatible, but IpcChannel has less overhead (i.e. is more efficient for Inter-process communication.)