Page 9 of 11

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

Posted: 16 Dec 2020, 17:25
by metaprogram
I think there is a bug in this script when returning arrays of length 0. For example:

Code: Select all

    ; Return a string[] type
    cSharp =
    (
        class ArrayReturnValueTest
        {
            public string[] Foo()
            {
                return new string[0]{};
            }
        }
    )

    obj := CLR_CreateObject( CLR_CompileC#( cSharp ), "ArrayReturnValueTest")
    result := obj.Foo()
    For found in result
    {
        
    }
complains about an invalid memory read/write. Anyone know how to fix this?

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

Posted: 30 Jan 2021, 02:05
by lexikos
The script just calls the compiler and instantiates the class. It is not responsible for marshaling the method call or return value, or implementing the method or enumeration (for-loop).

The bug is with enumeration of an empty SafeArray by AutoHotkey, and can potentially be reproduced by for a in ComObjArray(0x8, 0) { ... }.

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

Posted: 30 Jan 2021, 11:00
by serzh82saratov
Hello!
Is it possible using the CLR to create a Windows.ApplicationModel.DataTransfer object, and then use it in AutoHotkey?
Here is a sample code, not convenient and difficult to use for each DllCall method (NumGet (NumGet (.
Would like something like here JavaScript is easy to use an object.

Code: Select all

; Author - teadrinker
; http://forum.script-coding.com/viewtopic.php?pid=145546#p145546

; puts the second item from the Windows 10 clipboard history into the Clipboard and returns a success status
MsgBox, % (new ClipboardHistory).PutHistoryItemIntoClipboard(2)

; get the text of the elements of the clipboard history by index
ClipHist := new ClipboardHistory
Loop % ClipHist.Count
   MsgBox,, Item %A_Index%, % ClipHist.GetHistoryItemText(A_Index), .7

class ClipboardHistory
{
; https://is.gd/bYyogJ ; Clipboard Class (MSDN)
; https://is.gd/2z2Y9G ; Windows.ApplicationModel.DataTransfer.0.h (GitHub)
; https://is.gd/T4Lb7b ; asyncinfo.h (GitHub)
   __New() {
      static IID_IClipboardStatics2 := "{D2AC1B6A-D29F-554B-B303-F0452345FE02}"
      if !(A_OSVersion ~= "^\d") {
         MsgBox, This class requires Windows 10 or later!
         Return
      }
      riid := CLSIDFromString(IID_IClipboardStatics2, _)

      WinStr := new WindowsString("Windows.ApplicationModel.DataTransfer.Clipboard")
      WinStr.CreateInterface(riid, pIClipboardStatics2)
      WinStr := ""

      this.ClipboardStatics2 := new IClipboardStatics2( pIClipboardStatics2 )
   }
   
   __Delete() {
      this.ClipboardStatics2 := ""
   }
   
   IsHistoryEnabled[] {
      get {
         Return this.ClipboardStatics2.IsHistoryEnabled
      }
   }
   
   Count[] {
      get {
         Return this._GetClipboardHistoryItems(ReadOnlyList)
      }
   }
   
   ClearHistory() {
      Return this.ClipboardStatics2.ClearHistory()
   }
   
   GetHistoryItemText(index) { ; 1 based
      if !pIClipboardHistoryItem := this._GetClipboardHistoryItemByIndex(index)
         Return
      ClipboardHistoryItem := new IClipboardHistoryItem( pIClipboardHistoryItem )
      pIDataPackageView := ClipboardHistoryItem.Content
      DataPackageView := new IDataPackageView( pIDataPackageView )
      pIReadOnlyList := DataPackageView.AvailableFormats
      ReadOnlyList := new IReadOnlyList( pIReadOnlyList )
      Loop % ReadOnlyList.Count
         if StrGet(ReadOnlyList.Item[A_Index - 1] + 20 + A_PtrSize, "UTF-16") = "Text" && textFound := true
            break
      if !textFound
         Return
      DataPackageView.GetTextAsync(pIAsyncOperation)
      pString := this._AsyncOperationGetResults(pIAsyncOperation)
      Return StrGet(pString + 20 + A_PtrSize, "UTF-16")
   }
   
   PutHistoryItemIntoClipboard(index) { ; 1 based
      static SetHistoryItemAsContentStatus := ["Success", "AccessDenied", "ItemDeleted"]
      if !pIClipboardHistoryItem := this._GetClipboardHistoryItemByIndex(index)
         Return
      ClipboardHistoryItem := new IClipboardHistoryItem( pIClipboardHistoryItem )
      status := this.ClipboardStatics2.SetHistoryItemAsContent( ClipboardHistoryItem.ptr )
      Return SetHistoryItemAsContentStatus[ status + 1 ]
   }
   
   _GetClipboardHistoryItemByIndex(index) { ; 1 based
      count := this._GetClipboardHistoryItems(ReadOnlyList)
      if (count < index) {
         MsgBox, 48, % " ", Index "%index%" exceeds items count!
         Return
      }
      Return pIClipboardHistoryItem := ReadOnlyList.Item[index - 1]
   }
   
   _GetClipboardHistoryItems(ByRef ReadOnlyList) {
      this.ClipboardStatics2.GetHistoryItemsAsync( pIAsyncOperation )
      pIClipboardHistoryItemsResult := this._AsyncOperationGetResults(pIAsyncOperation)
      ClipboardHistoryItemsResult := new IClipboardHistoryItemsResult( pIClipboardHistoryItemsResult )
      pIReadOnlyList := ClipboardHistoryItemsResult.Items
      ReadOnlyList := new IReadOnlyList( pIReadOnlyList )
      Return ReadOnlyList.Count
   }
   
   _AsyncOperationGetResults(pIAsyncOperation) {
      static AsyncStatus  := ["Started", "Completed", "Canceled", "Error"]
      AsyncOperation := new IAsyncOperation( pIAsyncOperation )
      AsyncOperation.QueryIAsyncInfo(pIAsyncInfo)
      AsyncInfo := new IAsyncInfo( pIAsyncInfo )
      Loop {
         Sleep, 10
         status := AsyncStatus[ AsyncInfo.Status + 1 ]
      } until status != "Started" || status = ""
      if (status != "Completed")
         throw "AsyncInfo error, status: """ . status . """"
      AsyncOperation.GetResults( pResult )
      Return pResult
   }
}

class IClipboardStatics2 extends _InterfaceBase
{
   GetHistoryItemsAsync(ByRef pIAsyncOperation) {
      hr := DllCall(this.VTable(6), "Ptr", this.ptr, "UIntP", pIAsyncOperation)
      this.IsError(A_ThisFunc, hr)
   }
   ClearHistory() {
      hr := DllCall(this.VTable(7), "Ptr", this.ptr, "UIntP", res)
      this.IsError(A_ThisFunc, hr)
      Return res
   }
   SetHistoryItemAsContent(pIClipboardHistoryItem) {
      hr := DllCall(this.VTable(9), "Ptr", this.ptr, "Ptr", pIClipboardHistoryItem, "UIntP", res)
      this.IsError(A_ThisFunc, hr)
      Return res
   }
   IsHistoryEnabled[] {
      get {
         hr := DllCall(this.VTable(10), "Ptr", this.ptr, "UIntP", res)
         this.IsError(A_ThisFunc, hr)
         Return res
      }
   }
}

class IClipboardHistoryItemsResult extends _InterfaceBase
{
   Items[] {
      get {
         hr := DllCall(this.VTable(7), "Ptr", this.ptr, "PtrP", pIReadOnlyList)
         this.IsError(A_ThisFunc, hr)
         Return pIReadOnlyList
      }
   }
}

class IClipboardHistoryItem extends _InterfaceBase
{
   Content[] {
      get {
         hr := DllCall(this.VTable(8), "Ptr", this.ptr, "PtrP", pIDataPackageView)
         this.IsError(A_ThisFunc, hr)
         Return pIDataPackageView
      }
   }
}

class IDataPackageView extends _InterfaceBase
{
   AvailableFormats[] {
      get {
         hr := DllCall(this.VTable(9), "Ptr", this.ptr, "PtrP", pIReadOnlyList)
         this.IsError(A_ThisFunc, hr)
         Return pIReadOnlyList
      }
   }
   GetTextAsync(ByRef pIAsyncOperation) {
      hr := DllCall(this.VTable(12), "Ptr", this.ptr, "UIntP", pIAsyncOperation)
      this.IsError(A_ThisFunc, hr)
   }
}

class IReadOnlyList extends _InterfaceBase
{
   Item[index] {
      get {
         hr := DllCall(this.VTable(6), "Ptr", this.ptr, "Int", index, "PtrP", pItem)
         this.IsError(A_ThisFunc, hr)
         Return pItem
      }
   }
   Count[] {
      get {
         hr := DllCall(this.VTable(7), "Ptr", this.ptr, "UIntP", count)
         this.IsError(A_ThisFunc, hr)
         Return count
      }
   }
}

class IAsyncOperation extends _InterfaceBase
{
   QueryIAsyncInfo(ByRef pIAsyncInfo) {
      static IID_IAsyncInfo := "{00000036-0000-0000-C000-000000000046}"
      pIAsyncInfo := ComObjQuery(this.ptr, IID_IAsyncInfo)
   }
   GetResults(ByRef pResult) {
      hr := DllCall(this.VTable(8), "Ptr", this.ptr, "PtrP", pResult)
      this.IsError(A_ThisFunc, hr)
   }
}

class IAsyncInfo extends _InterfaceBase
{
   Status[] {
      get {
         hr := DllCall(this.VTable(7), "Ptr", this.ptr, "UIntP", status)
         this.IsError(A_ThisFunc, hr)
         Return status
      }
   }
}

class _InterfaceBase {
   __New(ptr) {
      this.ptr := ptr
   }
   __Delete() {
      ObjRelease(this.ptr)
   }
   VTable(idx) {
      Return NumGet(NumGet(this.ptr + 0) + A_PtrSize*idx)
   }
   IsError(method, result, exc := true) {
      if (result = 0)
         Return 0
      error := StrReplace(method, ".", "::") . " failed.`nResult: "
                              . ( result = "" ? "No result" : SysError(Format("{:#x}", result & 0xFFFFFFFF)) )
                              . "`nErrorLevel: " . ErrorLevel
      if !exc
         Return error
      throw error
   }
}

class WindowsString {
   __New(string, isHandle := false) {
      if isHandle
         this.hString := string
      else {
         DllCall("Combase\WindowsCreateString", "WStr", string, "UInt", StrLen(string), "PtrP", hString)
         this.hString := hString
      }
   }
   __Delete() {
      DllCall("Combase\WindowsDeleteString", "Ptr", this.hString)
   }
   Get() {
      pBuff := DllCall("Combase\WindowsGetStringRawBuffer", "Ptr", this.hString, "UIntP", len, "Ptr")
      Return StrGet(pBuff, len, "UTF-16")
   }
   CreateInterface(riid, ByRef pInterface) {
      hr := DllCall("Combase\RoGetActivationFactory", "Ptr", this.hString, "Ptr", riid, "PtrP", pInterface)
      if (hr != 0)
         throw SysError(hr)
   }
}

CLSIDFromString(IID, ByRef CLSID) {
   VarSetCapacity(CLSID, 16, 0)
   if hr := DllCall("ole32\CLSIDFromString", "WStr", IID, "Ptr", &CLSID, "UInt")
      throw "CLSIDFromString failed. Error: " . Format("{:#x}", hr)
   Return &CLSID
}

SysError(ErrorNum = "")
{ 
   static flag := (FORMAT_MESSAGE_ALLOCATE_BUFFER := 0x100) | (FORMAT_MESSAGE_FROM_SYSTEM := 0x1000)
   (ErrorNum = "" && ErrorNum := A_LastError)
   DllCall("FormatMessage", "UInt", flag, "UInt", 0, "UInt", ErrorNum, "UInt", 0, "PtrP", pBuff, "UInt", 512, "Str", "")
   Return (str := StrGet(pBuff)) ? str : ErrorNum
}

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

Posted: 30 Jan 2021, 11:57
by serzh82saratov
I try so, error.
Spoiler

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

Posted: 30 Jan 2021, 13:44
by serzh82saratov
More precisely, something like this. The point is to get the root object and manipulate it from AutoHotkey.
This is an approximate VB code, maybe you suggest C # and it will be better.

Code: Select all

vb =
( 
   Imports System
   Imports Windows
   Class Foo
      Function GetWA() as Object
         Return Windows.ApplicationModel
     End Function
   End Class
)
asm := CLR_CompileVB(vb, "System.dll | Windows.dll")
Foo := CLR_CreateObject(asm, "Foo")
WA := Foo.GetWA()
WA.DataTransfer.Clipboard.clear()

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

Posted: 30 Jan 2021, 19:29
by lexikos
You can do whatever you want in C# or VB. This script is just for loading the CLR, invoking the C# and VB compilers and creating instances of classes directly. Once you have an instance, you use it via .NET COM interop (as in, the COM interfaces provided by the library or the CLR itself) and AutoHotkey's COM support, which are not part of this script.

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

Posted: 30 Jan 2021, 19:49
by serzh82saratov
Yes, but it is with Windows.ApplicationModel that it fails.

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

Posted: 31 Jan 2021, 00:48
by lexikos
What's your point? Mine is that the failure is outside the scope of this script.

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

Posted: 27 Mar 2021, 06:08
by nadure
Hi! :D
This is first write for question!
How to return the c# Dictionary object.

Below, this is example code. using CLR lib.


Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

;~ IDictionary List
;~ {F31091E6-B0A8-11D6-B6FC-005056C00008}
;~ {6A6841DF-3287-3D87-8060-CE0B4C77D2A1}
;~ {42C642C1-97E1-11CF-978F-00A02463E06F}

#Include CLR.ahk

ICollection_IID := "{DE8DB6F8-D101-3A92-8D1C-E72E5F10E992}"
IDictionary_IID_v1 := "{F31091E6-B0A8-11D6-B6FC-005056C00008}"
IDictionary_IID_v2 := "{42C642C1-97E1-11CF-978F-00A02463E06F}"
IDictionary_IID_v3 := "{6A6841DF-3287-3D87-8060-CE0B4C77D2A1}"

IList_IID := "{7BCFA00F-F764-3113-9140-3BBD127A96BB}"
Dictionary_IID := "{000209AD-0000-0000-C000-000000000046}"

IDispatch := "{00020400-0000-0000-C000-000000000046}"
IUnknown := "{00000000-0000-0000-C000-000000000046}"

IID_IProvideClassInfo := "{B196B283-BAB4-101A-B69C-00AA00341D07}"


vc =
( 
	using System;
	using System.Collections.Generic;
	
	class DictionaryReturnValueTest
	{
		public Dictionary<string, string> ReturningDictTest()
        {
            var dict = new Dictionary<string, string>();
            dict.Add("who", "nadure");
            dict.Add("is", "C_Sharp");
            dict.Add("tis", "t_Sharp");
            dict.Add("dis", "d_Sharp");
            return dict;
        }
		
		public string ReturningTest()
		{
			string str = "Hello";
			return str;
		}
	}
)

dv := CLR_CreateObject(CLR_CompileC#(vc, "System.dll | System.Windows.Forms.dll | System.Collections.dll"), "DictionaryReturnValueTest")


value := dv.ReturningDictTest()

ICollection := ComObject(9, ComObjQuery(value, ICollection_IID), 1)
IDictionary := ComObject(0x2000, ComObjQuery(ICollection, IDictionary_IID_v3), 1)

;~ res := ComObjQuery(ICollection, IDictionary_IID_v3)


;~ Msgbox,% res
;~ if !(res)
;~ {
	;~ Msgbox, Error
;~ }


Msgbox,% IDictionary["who"]

return


f2::
ExitApp



I tried using IID but not works. :evil:

Is it possible?

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

Posted: 27 Mar 2021, 22:14
by lexikos
Isn't just value := dv.ReturningDictTest() sufficient?

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

Posted: 28 Mar 2021, 22:34
by nadure
lexikos wrote:
27 Mar 2021, 22:14
Isn't just value := dv.ReturningDictTest() sufficient?
um.. this is not works for the test.

Code: Select all

Msgbox,% value["who"]
Is this a SafeArray?

if the value is SafeArray, what is the method for pick up the value from the SafeArray?

In other comment,
https://www.autohotkey.com/boards/viewtopic.php?style=7&f=6&t=4633&p=292050&hilit=IList#p292050

Using ComobjQuery and Comobject for picking up value from array(actually IList). so i had done similar. but not works..

Would you like to give me some ideas?

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

Posted: 30 Mar 2021, 03:42
by lexikos
AutoHotkey works with .NET objects by querying for the _Object interface, which extends IDispatch. Querying for IDispatch only works if the class has appropriate attributes (i.e. it's designed to be used by COM clients), whereas querying for _Object always returns that interface. It generally works, but probably isn't officially supported by Microsoft.

It looks like the dispatch implementation for generic types through the _Object interface is broken or absent. When we attempt to call IDispatch::GetIDsOfNames, something within the implementation (in clr.dll) tries to dereference a null pointer, causing an access violation. I confirmed this with a simple Test<T> class with one field T Value;, instantiated with Test<string>. The equivalent non-generic class worked fine.

I suppose that dynamic dispatch of calls from COM clients to generic types just isn't implemented, because COM fundamentally doesn't support generics.

Some notes regarding your attempts:
  • ComObject(9, ptr) is only valid if ptr is for IDispatch or an interface derived from IDispatch. ICollection does not derive from IDispatch.
  • If the interface does not derive from IDispatch, you cannot invoke it dynamically. You may invoke it by manually by retrieving the function address from the interface's virtual function table with NumGet and passing it to DllCall (for example, see ITaskbarList). You must specify the correct offset in the function table, and the correct parameter types.
    It would be far easier to use a non-generic class which will work with IDispatch automatically.
  • Interfaces and SAFEARRAYs are completely different. You cannot reinterpret an interface pointer as a pointer to a SAFEARRAY, such as by passing it to ComObject(0x2000 ...).
  • Dictionaries and SAFEARRAYs are not equivalent. SAFEARRAY is equivalent to a plain array.
  • If you have a pointer to a SAFEARRAY, when you wrap it with ComObject() you must specify the base type of the array. For example, VT_ARRAY|VT_BSTR (0x2008) is an array of strings. You will get a SAFEARRAY of this type when you return a string[] from C#.

Changing the return type to object or a non-generic interface will not help if the object is still a generic type, as it is the implementation of the object/class that matters. For instance, (IDictionary)dict is still the same object as dict, so (_Object)(IDictionary)dict == (_Object)dict.

Querying for a non-generic interface should work (I'm not sure that you can query for a generic interface anyway, as it might never have an IID), but you still have to use the interface correctly. ICollection and IDictionary could only be used through DllCall, and working out the order of methods in the vtable can be a pain so I'm going to suggest that you just avoid using generic classes (for objects that you return to the script).

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

Posted: 30 Mar 2021, 08:51
by nadure
Thanks for very detailed explanation.
As your mention, this is very hard way to work on.
but I'm so touching very things. i'll understand little more, and use it another project.


Have a good day!

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

Posted: 18 Apr 2021, 23:41
by hylotropy
@lexikos please help, I tried to translate the following powershell script to autohotkey but got an error 0x80131509.

ILSpy : https://imgur.com/a/5jtXOxG

powershell

Code: Select all

Add-Type -Path "C:\Users\Admin\Desktop\OpenHardwareMonitor\OpenHardwareMonitorLib.dll"
$Comp = New-Object -TypeName OpenHardwareMonitor.Hardware.Computer
$Comp.Open()
$Comp.CPUEnabled = $true
$Comp.Hardware[0].HardwareType    #return "Cpu"

autohotkey

Code: Select all

SetWorkingDir %A_ScriptDir%  ;
OpenHardwareMonitorLibPath:=A_ScriptDir "\OpenHardwareMonitorLib.dll"
asm := CLR_LoadLibrary(OpenHardwareMonitorLibPath)
computer := CLR_CreateObject(asm, "OpenHardwareMonitor.Hardware.Computer")
computer.Open()
computer.CpuEnabled  :=computer.HDDEnabled := computer.RAMEnabled := 1 
Hardwares:= computer.Hardware
msgbox,%  ComObjType(Hardwares[0])  ;return  9
msgbox,%  ComObjType(Hardwares[1])  ;return  9
msgbox,%  ComObjType(Hardwares[2])  ;return  9
msgbox,%  ComObjType(Hardwares[0].HardwareType)  ; Error:  0x80131509 -  Specifically: HardwareType
Image

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

Posted: 19 Apr 2021, 06:25
by hylotropy
What is the difference between clr.ahk and pythonnet? It is easy to use OpenHardwareMonitorLib.dll in vb, powershell, and even python (pythonnet), I don't understand why it is so difficult to use OpenHardwareMonitorLib.dll in autohotkey.

Code: Select all

import clr
clr.AddReference("c:/OpenHardwareMonitorLib.dll") 
from OpenHardwareMonitor.Hardware import Computer 
 
computer_tmp = Computer() 
computer_tmp.CPUEnabled = True 
computer_tmp.Open()
print (computer_tmp.Hardware[0].HardwareType)  # CPU

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

Posted: 08 May 2021, 15:43
by dd900
@lexikos

typeofAssembly.InvokeMember_3("LoadFrom", 0x158, null, null, args)

I'm having trouble trying to figure out bitwise combinations still. Please don't refer me to jeeswg's mathematics tutorial. While it is an amazing resource, I cannot just read an explanation and look at arbitrary numbers and be expected to understand. That's not how my brain works. I need a real world example and the code above is perfect. How did you come up with 0x158? I know its a bitwise combo of BindingFlags. What I don't know is what Flags you are combining (and how) you got the desired output. I'm assuming you are using "public" and "static" flags and....?

How would I go about calling the following with CLR? Does return value matter?

Code: Select all

public static class foo
{
	public static string StrMethod() {
		//do stuff
	}
	
	public static void VoidMethod() {
		//do stuff
	}
}

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

Posted: 28 Aug 2021, 01:48
by lexikos
I have uploaded CLR.ahk to GitHub, along with an updated version for AutoHotkey v2.0-beta.1.

https://github.com/Lexikos/CLR.ahk

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

Posted: 29 Aug 2021, 09:19
by burque505
Thank you very much!

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

Posted: 01 Sep 2021, 08:33
by xroot
Fooling with CLR v2.0-beta.1

My C# code works just fine.

Added this code to CLR AutoHotkey v2.0-beta.1 #include:

Code: Select all

CLR_CompileJS(Code, References:="", AppDomain:=0, FileName:="", CompilerOptions:="")
{
	return CLR_CompileAssembly(Code, References, "System", "Microsoft.JScript.JScriptCodeProvider", AppDomain, FileName, CompilerOptions)
}                                                           
The idea for JScript from here:
https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.codedomprovider?view=netframework-4.8

Get this ERROR message:
Error in #include file "CLR.ahk":
This value of type "String" has no method named "CreateCompiler".

The Line "codeProvider := asmProvider.CreateInstance(ProviderType)" failed, "codeProvider" is "string" instead of "codeDomProvider" object.

Any clue on what is needed for "Microsoft.JScript.JScriptCodeProvider"?

Thanks

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

Posted: 02 Sep 2021, 03:36
by lexikos
You must specify the correct assembly. JScriptCodeProvider is in Microsoft.JScript.dll, not System.dll.

As for actually using JScript.net, don't ask me. Note that it is not JavaScript, and in particular, is not modern JavaScript.