AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

.NET Framework - AutoHotkey Interop (feedback required)
Goto page 1, 2  Next
 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Ask for Help
View previous topic :: View next topic  
Author Message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sat Nov 24, 2007 12:52 pm    Post subject: .NET Framework - AutoHotkey Interop (feedback required) Reply with quote

(Edit: See .NET Framework Interop in Scripts & Functions.)

I've been working on a script for using the .NET Framework from within AutoHotkey. I haven't yet implemented (direct) method invocation, etc., but I have managed to run an executable (written in C#) inside the AutoHotkey.exe process Laughing (see script below.)

What I'd like to know before I continue (the reason I'm posting) is:

What .NET capabilities would people be interested in?
(Specific components or general interop features.)

I started this project with no particular goal. Rolling Eyes Now I'm asking for requests of general features or simple demonstration scripts. For instance, I know there are many useful .NET components out there, especially Controls (i.e. for GUIs.) If there are any specific components/controls you want to see in AutoHotkey, let me know.

Code:
#NoEnv

; Load .NET and get ICorRuntimeHost interface.
hr:=CLR_CorBind(pCorRuntimeHost)
if (hr != 0) {
    MsgBox CLR_CorBind error %hr%.
    ExitApp
}
; Start the .NET common language runtime.
hr:=CLR_CorStart(pCorRuntimeHost)
if (hr != 0) {
    MsgBox CLR_CorStart error %hr%.
    ExitApp
}

CLR_GetVersion(sVer)
MsgBox % "Loaded .NET Framework " sVer

hr:=CLR_CorGetDefaultDomain(pCorRuntimeHost, pAppDomain)
if (hr != 0) {
    MsgBox CLR_CorGetDefaultDomain error %hr%.
    ExitApp
}

; Since the entry point for an application does not return until the application
; exits, neither does CLR_ExecuteAssembly() when sAssemblyFile is an application...
CLR_ExecuteAssembly(pAppDomain, "Z:\AppRunTimer.exe")


; ListVars
; Pause
; Process, Exist
; RunWait, %comspec% /k tasklist /FI "PID eq %ErrorLevel%" /M


; Load the common language runtime (CLR) into the current process.
; Returns an interface pointer of type ICorRuntimeHost.
CLR_CorBind(ByRef pCorRuntimeHost)
{
    return DllCall("MSCorEE\CorBindToRuntime"
       ,"uint", 0 ; pwszVersion: use latest
       ,"uint", 0 ; pwszBuildFlavor: default (workstation)
       ,"uint", COM_GUID4String(clsid,"{CB2F6723-AB3A-11d2-9C40-00C04FA30A3E}")   ; CLSID_CorRuntimeHost
       ,"uint", COM_GUID4String(iid,"{CB2F6722-AB3A-11d2-9C40-00C04FA30A3E}")     ; IID_ICorRuntimeHost
       ,"uint*", pCorRuntimeHost)
}

CLR_GetVersion(ByRef sVer)
{
    VarSetCapacity(wsVer,40)
    hr:=DllCall("MSCorEE\GetCORVersion","uint",&wsVer,"uint",20,"uint*",0)
    sVer := COM_Ansi4Unicode(&wsVer)
    return hr
}

;
; ICorRuntimeHost Methods
;
CLR_CorStart(pCorRuntimeHost)
{   ; ICorRuntimeHost.Start()
    return DllCall(NumGet(NumGet(pCorRuntimeHost+0)+40),"uint",pCorRuntimeHost)
}

; Gets an interface pointer of type _AppDomain that represents the default domain for the current process.
CLR_CorGetDefaultDomain(pCorRuntimeHost, ByRef pAppDomain)
{   ; ICorRuntimeHost.GetDefaultDomain()
    hr:=DllCall(NumGet(NumGet(pCorRuntimeHost+0)+52),"uint",pCorRuntimeHost,"uint*",pUnk)
    if (hr = 0) {
        pAppDomain:=COM_QueryInterface(pUnk,"{05F696DC-2B29-3663-AD8B-C4389CF2A713}")
        COM_Release(pUnk)
        if !pAppDomain
            hr := 0x80004002+0 ; E_NOINTERFACE
    }
    return hr
}

;
; _AppDomain Methods
;
CLR_ExecuteAssembly(pAppDomain, sAssemblyFile, ByRef Ret="")
{
    pFilename:=COM_SysAllocString(sAssemblyFile)
    hr:=DllCall(NumGet(NumGet(pAppDomain+0)+204),"uint",pAppDomain,"uint",pFilename,"int*",Ret)
    COM_SysFreeString(pFilename)
    return hr
}
Requires COM.ahk. Tested on Vista (.NET v2.0.50727) and XP (.NET v1.1.4322). Obviously, for the example to work you'd need to replace "Z:\AppRunTimer.exe" with the path of a .NET executable.


(advanced:)
Note that MSDN suggests that the ICorRuntimeHost interface shouldn't be used with the .NET Framework 2.0, but it seems to work fine for me (on Vista, apparently .NET v2.0.50727.) Since the alternative is to not support the .NET Framework 1.0 and 1.1, I'll continue to use ICorRuntimeHost until I encounter problems.
Quote:
The hosting interfaces that are supported for the CLR in the .NET Framework versions 1.0 and 1.1 are not supported in later versions of the .NET Framework. Mixing and matching these interfaces could cause unexpected behavior.


Last edited by Lexikos on Mon Dec 03, 2007 10:34 am; edited 1 time in total
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1337

PostPosted: Sat Nov 24, 2007 4:16 pm    Post subject: Re: .NET Framework - AutoHotkey Interop (feedback required) Reply with quote

lexikos wrote:
Code:
hr:=CLR_CorBind(pCorRuntimeHost)

In my system just the following seems to load mscoree.dll:
Code:
pcor := COM_CreateObject("CLRMetaData.CorRuntimeHost","{00000000-0000-0000-C000-000000000046}")

I'm not sure why they provide ProgID for IUnknown type, though. And, is it safe to load an executable into the process space of another app?
Back to top
View user's profile Send private message
Andreone



Joined: 20 Jul 2007
Posts: 257
Location: Paris, France

PostPosted: Sat Nov 24, 2007 6:39 pm    Post subject: Reply with quote

Quote:
What .NET capabilities would people be interested in?
For example, the ability to load a c# UI component, like this one:a good c# grid (and open source): XPTable - .NET ListView


thank you for putting efforts to make ahk interop with c#. Smile
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1337

PostPosted: Sat Nov 24, 2007 7:24 pm    Post subject: Reply with quote

It's interesting.
Code:
COM_Init()
pcor := COM_CreateObject("CLRMetaData.CorRuntimeHost", "{00000000-0000-0000-C000-000000000046}")
DllCall(NumGet(NumGet(1*pcor)+40), "Uint", pcor)
DllCall(NumGet(NumGet(1*pcor)+52), "Uint", pcor, "UintP", papp)
COM_Invoke(papp, "ExecuteAssembly_2", "...\UISpy.exe")
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sun Nov 25, 2007 1:28 am    Post subject: Reply with quote

How/where did you find the CLRMetaData.CorRuntimeHost class? (edit: I found CorRuntimeHost in mscoree.tlb, but CLRMetaData...?)

I see that COM_Invoke works with the pointer returned by GetDefaultDomain... According to MSDN, it should not:
Quote:
This pointer is typed IUnknown, so callers should generally call QueryInterface to obtain an interface pointer of type _AppDomain.

Unfortunately, _AppDomain's IDispatch implementation consists entirely of "throw new NotImplementedException()". Mad
Sean wrote:
And, is it safe to load an executable into the process space of another app?
I don't see why not. A .NET executable is no different from a .NET dll. Why would they provide the function if it were not safe?

@Andreone: I'll keep that in mind, though I'll probably use one of the controls included in the .NET Framework as a demonstration. The main (additionally) necessary function is LoadFrom() to load the XPTable dll from file. (It seems there is no LoadFrom() instance method that can be Invoke()d, so it might have to be done in a more roundabout way.)

Edit: How's this for a PITA: AppDomain.Load() will accept nothing but the full name of the assembly (unless it's already loaded.) An example of a full name:
Code:
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
On the other hand, at least it works via COM_Invoke. (Thanks Sean.)

It seems one unfortunate side-effect of using Invoke is that if an exception occurs, DISP_E_EXCEPTION is returned instead of a more specific error code.


Last edited by Lexikos on Sun Nov 25, 2007 2:54 am; edited 2 times in total
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1337

PostPosted: Sun Nov 25, 2007 2:22 am    Post subject: Reply with quote

lexikos wrote:
How/where did you find the CLRMetaData.CorRuntimeHost class?

I noticed it has typelibs, mscoree.tlb, mscorlib.tlb etc, so I peeked into it and then went to registry to find it there.

Quote:
Unfortunately, _AppDomain's IDispatch implementation consists entirely of "throw new NotImplementedException()". Mad

Where and how? I didn't seem to get any exception here in XPSP2 with .NET 2. Anyway, I think it's understandable and actually wiser move to shield it totally from scripts as it looks too powerful to be accessible from scripts.

Quote:
I don't see why not. A .NET executable is no different from a .NET dll. Why would they provide the function if it were not safe?

I said it in a generic term as I know nothing about .NET/C#, managed codes in general. After running the above code, I think I now got at least some picture of it.
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sun Nov 25, 2007 3:04 am    Post subject: Reply with quote

Sean wrote:
I noticed it has typelibs, mscoree.tlb, mscorlib.tlb etc, so I peeked into it and then went to registry to find it there.
Yep, I found them also, but have seen no mention of "CLRMetaData."
Quote:
Quote:
Unfortunately, _AppDomain's IDispatch implementation consists entirely of "throw new NotImplementedException()". Mad

Where and how? I didn't seem to get any exception here in XPSP2 with .NET 2.
.NET Reflector tells me they throw NotImplementedException, though it comes out on the COM side as E_NOTIMPL. (Exceptions have a protected property "HResult"; for NotImplementedException, HResult is 0x80004001.)
Quote:
Anyway, I think it's understandable and actually wiser move to shield it totally from scripts as it looks too powerful to be accessible from scripts.

_AppDomain is just an interface to the functions of AppDomain. The IUnknown (or IDispatch as it seems) returned by GetDefaultDomain can already be used to access everything in AppDomain (as you demonstrated!); this just isn't clearly documented. I suspect the IDispatch members of _AppDomain aren't implemented because the Common Language Runtime provides automatic IDispatch support of classes.

Actually, AppDomain has the [ClassInterfaceAttribute(ClassInterfaceType.None)] attribute, which is why I had assumed (incorrectly) IDispatch would not work with it...

Interestingly, COM_CreateObject() works with classes from mscorlib, but not other assemblies. For instance, System.Object, System.Random and System.Collections.ArrayList (mscorlib.dll) worked, but System.Collections.Specialized.StringDictionary (system.dll) and System.Windows.Forms.Form (system.windows.forms.dll) did not (result: 80040154 / class not registered.)
Code:
COM_Init()
pRand := COM_CreateObject("System.Random")
Loop 4
    MsgBox % COM_Invoke(pRand,"NextDouble")
Cool
Back to top
View user's profile Send private message
tic



Joined: 22 Apr 2007
Posts: 1353

PostPosted: Sun Nov 25, 2007 3:10 am    Post subject: Reply with quote

Well I second Andreone's proposal if it is at all possible (you know how much I would love that Lex Smile ) and also if at all possible, although I doubt it is, add c#s ability to natively multithread.
Thanks
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1337

PostPosted: Sun Nov 25, 2007 4:15 am    Post subject: Reply with quote

lexikos wrote:
The IUnknown (or IDispatch as it seems) returned by GetDefaultDomain can already be used to access everything in AppDomain (as you demonstrated!); this just isn't clearly documented.

Yes, however, script languages like VBS has no way to manipulate IUnknown interface AFAIK. As a matter of fact, there is no way from the start for VBS etc to reach AppDomain as it doesn't register itself so have to reach it indirectly, like through CorRuntimeHost which isn't IDispatch. So, I would say it's already 90% immune from (VBS) scripts. Making all interfaces not of IDispatch would raise it to 100%.

Quote:
Interestingly, COM_CreateObject() works with classes from mscorlib, but not other assemblies. For instance, System.Object, System.Random and System.Collections.ArrayList (mscorlib.dll) worked, but System.Collections.Specialized.StringDictionary (system.dll) and System.Windows.Forms.Form (system.windows.forms.dll) did not

Yes, these last two ProgIDs aren't registered in the registry, so can't be used. If CLSIDs are registered though, may use them with CreateObject. If no CLSIDs either, then there is no standard way to access them directly. I guess they may be accessible indirectly, like AppDomain from CorRuntimeHost. BTW, although there exists a non-documented/non-recommended way to directly talk to DLL hosting the objects, I'm not sure if it's still applicable to these .NET system.dll etc.
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sun Nov 25, 2007 7:44 am    Post subject: Reply with quote

tic wrote:
if at all possible, although I doubt it is, add c#s ability to natively multithread.
I'd like to point out that is entirely possible to compile C# code at run-time. The ideas I had for .NET-AutoHotkey interop were (in the order I had them):
  1. Write a C++.NET dll with an unmanaged portion wrapping .NET Reflection (for creating objects, calling methods, etc.)
  2. Use Run-Time Code Generation to compile script-embedded C# code.
  3. Since #2 would require some way of calling the compiled C# code, may was well skip #2 and just write interop functions (using whatever APIs are available.)
The reason #3 didn't occur to me first is that until recently I had no idea how to load the Common Language Runtime.

Once the script is complete, it should be possible to use the run-time code generation APIs along with any other .NET API. It would basically be the same as out-sourcing multi-threading to a DLL, but it would be contained inside the script itself.
Sean wrote:
As a matter of fact, there is no way from the start for VBS etc to reach AppDomain as it doesn't register itself so have to reach it indirectly, like through CorRuntimeHost which isn't IDispatch. So, I would say it's already 90% immune from (VBS) scripts. Making all interfaces not of IDispatch would raise it to 100%.
I see. Smile
Quote:
If CLSIDs are registered though, may use them with CreateObject.
I tried that too. Didn't work.

It's not an issue, though. AppDomain.CreateInstance() can be used:
Code:
pFormHandle:=COM_Invoke(pApp,"CreateInstance","System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","System.Windows.Forms.Form")
pForm:=COM_Invoke(pFormHandle,"Unwrap"), COM_Release(pFormHandle)
COM_Invoke(pForm,"Show")
(Where pApp is a pointer to the IUnknown interface of an AppDomain, as in your previous post.)

The problem is that if the assembly isn't already loaded, it requires the full assembly name (as in the example above.) At the moment I'm trying to call the static method Assembly.LoadWithPartialName() (via _Type.InvokeMember_3()), but it's proving difficult...
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1337

PostPosted: Sun Nov 25, 2007 9:53 am    Post subject: Reply with quote

lexikos wrote:
It's not an issue, though. AppDomain.CreateInstance() can be used:
Code:
pFormHandle:=COM_Invoke(pApp,"CreateInstance","System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","System.Windows.Forms.Form")
pForm:=COM_Invoke(pFormHandle,"Unwrap"), COM_Release(pFormHandle)
COM_Invoke(pForm,"Show")

That sounds great. BTW, the following seemed to work too:
Code:
pFormHandle:=COM_Invoke(pApp,"CreateInstanceFrom",A_WinDir . "\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll","System.Windows.Forms.Form")
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sun Nov 25, 2007 2:39 pm    Post subject: Reply with quote

Progress: I now have working code to load an assembly given one of the following:
  1. A full assembly name, such as System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
  2. An absolute path or path relative to A_WorkingDir.
  3. A partial name, such as System. The assembly can be located in "the application directory" (not A_WorkingDir) or the global assembly cache (i.e. standard .NET Framework assemblies.)
#1 is preferred as it is the most "secure," and the most efficient since it can be called via pAppDomain.
#2 and #3 use static methods, which are a pain to call.

I decided to aim for easy-to-use functions rather than direct analogues to CLR functions.

For consistency and convenience (use with COM_Invoke()), IUnknown/IDispatch will be passed around where possible rather than more specific interfaces. For efficiency (I hope Rolling Eyes), I will use QueryInterface and VTable functions internally rather than Invoke (though Invoke would make for smaller script.)

Sneak Peak:
Code:
CLR_Start()
{
    global CLR_CorRuntimeHost
    if CLR_CorRuntimeHost
        return
    COM_Init()
    if CLR_CorRuntimeHost := COM_CreateObject("CLRMetaData.CorRuntimeHost","{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}")
        DllCall(NumGet(NumGet(CLR_CorRuntimeHost+0)+40),"uint",CLR_CorRuntimeHost)
    return CLR_CorRuntimeHost
}

CLR_Stop()
{
    global CLR_CorRuntimeHost
    if !CLR_CorRuntimeHost
        return
    DllCall(NumGet(NumGet(CLR_CorRuntimeHost+0)+44),"uint",CLR_CorRuntimeHost)
    COM_Release(CLR_CorRuntimeHost), CLR_CorRuntimeHost:=0
    COM_Term()
}

CLR_GetDefaultDomain() ; returns IUnknown*
{
    global CLR_CorRuntimeHost
    DllCall(NumGet(NumGet(CLR_CorRuntimeHost+0)+52),"uint",CLR_CorRuntimeHost,"uint*",pApp)
    return pApp
}

CLR_LoadLibrary(sLibrary, pAppDomain=0)
{
    pApp := pAppDomain ? pAppDomain : CLR_GetDefaultDomain()
    if !pApp
        return 0
    if p_App := COM_QueryInterface(pApp,"{05F696DC-2B29-3663-AD8B-C4389CF2A713}")
    {
        psLibrary:=COM_SysAllocString(sLibrary)
       
        ; Attempt to load by full name (incl. Version, Culture & PublicKeyToken) first.
        hr:=DllCall(NumGet(NumGet(p_App+0)+176),"uint",p_App,"uint",psLibrary,"uint*",p_Asm)
        if (hr=0 && p_Asm)
        {
            pAsm:=COM_QueryInterface(p_Asm,"{00000000-0000-0000-C000-000000000046}")
            COM_Release(p_Asm)
        }
        else
        {
            ; Get typeof(Assembly) for calling static methods. (p_App->GetType()->Assembly->GetType())
            DllCall(NumGet(NumGet(p_App+0)+40),"uint",p_App,"uint*",p_Type)
            DllCall(NumGet(NumGet(p_Type+0)+80),"uint",p_Type,"uint*",p_Asm)
            COM_Release(p_Type)
            DllCall(NumGet(NumGet(p_Asm+0)+40),"uint",p_Asm,"uint*",p_Type)
            COM_Release(p_Asm), p_Asm:=0
           
            ; Initialize VARIANTs & SAFEARRAY(VARIANT) for method args.
            VarSetCapacity(vtArg,16,0), NumPut(8,vtArg), NumPut(psLibrary,vtArg,8)
            VarSetCapacity(vtRet,16,0)
            paArgs:=DllCall("oleaut32\SafeArrayCreate","uint",12,"uint",1,"int64*",1)
            DllCall("oleaut32\SafeArrayPutElement","uint",paArgs,"int*",0,"uint",&vtArg)
           
            ; Attempt to load from file.
            ; Does not use IfExist since LoadFrom probably doesn't use A_WorkingDir.
            psMethodName:=COM_SysAllocString("LoadFrom")
            hr:=DllCall(NumGet(NumGet(p_Type+0)+228),"uint",p_Type
                ,"uint",psMethodName,"int",0x118,"uint",0
                ,"int64",1,"int64",0 ; VARIANT Target={ vt=VT_NULL }
                ,"uint",paArgs,"uint",&vtRet)
            COM_SysFreeString(psMethodName)
           
            if (hr!=0 or !NumGet(vtRet,8))
            {   ; Attempt to load using partial name.
                psMethodName:=COM_SysAllocString("LoadWithPartialName")
                hr:=DllCall(NumGet(NumGet(p_Type+0)+228),"uint",p_Type
                    ,"uint",psMethodName,"int",0x118,"uint",0
                    ,"int64",1,"int64",0,"uint",paArgs,"uint",&vtRet)
                COM_SysFreeString(psMethodName)
            }
            ; vtRet should now be of type VT_DISPATCH, so no QueryInterface necessary.
            if (hr=0)
                pAsm := NumGet(vtRet,8)
            else
                pAsm := 0
           
            DllCall("oleaut32\SafeArrayDestroy","uint",paArgs)
            COM_Release(p_Type)
        }
        COM_SysFreeString(psLibrary)
        COM_Release(p_App)
    }
    if (pAppDomain != pApp)
        COM_Release(pApp)
    return pAsm
}

; Gets either the CLR version in use, or the latest installed version.
CLR_GetVersion(ByRef sVer)
{
    VarSetCapacity(wsVer,40)
    hr:=DllCall("MSCorEE\GetCORVersion","uint",&wsVer,"uint",20,"uint*",0)
    sVer := COM_Ansi4Unicode(&wsVer)
    return hr
}

Note CLR_Stop(). Currently it doesn't unload the assemblies, but in the future it probably will via AppDomain.Unload(). (AFAIK assemblies cannot be unloaded individually.)
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1337

PostPosted: Mon Nov 26, 2007 2:51 am    Post subject: Reply with quote

lexikos wrote:
#2 and #3 use static methods, which are a pain to call.

I can see why you said/say so. And, I always want to avoid one requiring allocating SafeArray ...
So, I tried to work-around it and resulted in this. Seemed to work.
Code:
COM_Init()
pcor := COM_CreateObject("CLRMetaData.CorRuntimeHost", "{00000000-0000-0000-C000-000000000046}")
DllCall(NumGet(NumGet(1*pcor)+40), "Uint", pcor)
DllCall(NumGet(NumGet(1*pcor)+52), "Uint", pcor, "UintP", papp)
ptype := COM_QueryInterface(ptemp:=COM_Invoke(papp,"GetType"), "{00000000-0000-0000-C000-000000000046}"), COM_Release(ptemp)
pasm :=   COM_Invoke(ptype, "Assembly")
sFull:=   COM_Invoke(pasm,  "FullName")
COM_Release(pasm)
COM_Release(ptype)
StringReplace, sFull, sFull, mscorlib

AsmName := "System.Windows.Forms"
pFormHandle := COM_Invoke(papp, "CreateInstance", AsmName . sFull, "System.Windows.Forms.Form")
pform:=   COM_Invoke(pFormHandle, "Unwrap"), COM_Release(pFormHandle)
COM_Invoke(pform, "Show")
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Mon Nov 26, 2007 3:53 am    Post subject: Reply with quote

Creative solution. Smile Unfortunately not all of the .NET Framework assemblies use the same version number or PublicKeyToken.
Quote:
I always want to avoid one requiring allocating SafeArray ...
I did a small test, allocating and destroying SafeArrays with random (between 1 and 10) upper bound (100,000 iterations.) I then compared it to re-dimensioning the array (SafeArrayRedim.) SafeArrayCreate/SafeArrayDestroy averaged out to ~45µs, while SafeArrayRedim averaged out to ~25µs. Still, considering the unlikelihood of high frequency AHK -> .NET calls, it doesn't seem worth worrying about.

CLR_LoadLibrary() could allocate the SafeArray on first use, but how often would CLR_LoadLibrary() actually be called? On top of that, how does ~45µs compare to the average load time of a .NET assembly? (Rhetorical questions. Wink)
lexikos wrote:
AppDomain.CreateInstance() can be used:
...
The problem is that if the assembly isn't already loaded, it requires the full assembly name (as in the example above.) At the moment I'm trying to call the static method Assembly.LoadWithPartialName() (via _Type.InvokeMember_3()), but it's proving difficult...

After further testing, it seems that every assembly name must be fully qualified, except for "mscorlib". Specifying the full assembly name for CreateInstance is inconvenient and forces the script to rely on a specific version of the assembly.

The first solution I thought of was to get the FullName of the assembly returned by CLR_LoadLibrary().

Here's a better solution:
Code:
CLR_Start()
pAsm := CLR_LoadLibrary("System.Windows.Forms")
pForm := COM_Invoke(pAsm,"CreateInstance","System.Windows.Forms.Form")
COM_Invoke(pForm,"Show")
Back to top
View user's profile Send private message
Sean



Joined: 12 Feb 2007
Posts: 1337

PostPosted: Mon Nov 26, 2007 4:47 am    Post subject: Reply with quote

lexikos wrote:
Unfortunately not all of the .NET Framework assemblies use the same version number or PublicKeyToken.

Actually I may prefer using CreateInstanceFrom.
Code:
COM_Init()
pcor := COM_CreateObject("CLRMetaData.CorRuntimeHost", "{00000000-0000-0000-C000-000000000046}")
DllCall(NumGet(NumGet(1*pcor)+40), "Uint", pcor)
DllCall(NumGet(NumGet(1*pcor)+52), "Uint", pcor, "UintP", papp)
ptype:= COM_QueryInterface(ptemp:=COM_Invoke(papp,"GetType"), "{00000000-0000-0000-C000-000000000046}"), COM_Release(ptemp)
pasm := COM_Invoke(ptype, "Assembly")
sPath:= COM_Invoke(pasm,  "Location")
COM_Release(pasm)
COM_Release(ptype)
StringReplace, sPath, sPath, mscorlib.dll

AsmName := "System.Windows.Forms"
pFormHandle := COM_Invoke(papp, "CreateInstanceFrom", sPath . AsmName . ".dll", "System.Windows.Forms.Form")
pform:= COM_Invoke(pFormHandle, "Unwrap"), COM_Release(pFormHandle)
COM_Invoke(pform, "Show")


Quote:
it doesn't seem worth worrying about.

I'm not worried. I just don't like to play/mess with SafeArray, as I would try to manage it manually minimizing the usage of SafeArray APIs as I did with Variant.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Ask for Help All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group