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

Post your working scripts, libraries and tools for AHK v1.1 and older
IMEime
Posts: 750
Joined: 20 Sep 2014, 06:15

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

25 May 2015, 00:10

thanks
Last edited by IMEime on 05 Jul 2017, 18:43, edited 1 time in total.
User avatar
dd900
Posts: 121
Joined: 27 Oct 2013, 16:03

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

03 Aug 2015, 00:10

Wow I wish I would have looked into this sooner. I am wondering if I could get some advice? I'm relatively new to C#.

I am trying to create a class to use XPathNavigator. So far what I have works, but I really don't want to go any further until I get some clarification on a few things.
Here is my code so far:

Code: Select all

c# =
(
	using System.Xml;
	using System.Xml.XPath;

	class XPathNav
	{
		XPathNavigator nav;
		XPathDocument docNav;

		public void Open(string xmlPath)
		{
			docNav = new XPathDocument(@xmlPath);
			nav = docNav.CreateNavigator();
		}

		public XPathNavigator sNode(string xpath)
		{
			return nav.SelectSingleNode(xpath);
		}

		public XPathNodeIterator sNodes(string xpath)
		{
			return nav.Select(xpath);
		}

		public string GetAttrVal(XPathNavigator node, string attrName)
		{
			return node.GetAttribute(attrName, "");
		}
	}
)
asm := CLR_CompileC#(c#, "System.xml.dll" )
obj := CLR_CreateObject( asm, "XPathNav" )
obj.Open( "path to xml" )
node := obj.sNode( "//node" )
msgbox % obj.GetAttrVal( node, "name" )
1.) How do I handle XPathNavigator properties like innerXML? Do I just create another method that returns the value?
2.) How should I handle XPathNodeIterator? Should I create another class to handle its methods?
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

05 Aug 2015, 22:33

If you're wrapping it to make the usage easier for yourself, then the answers are:
1) However you want to handle them.
2) However you want to handle it.

If you're wrapping it just so that you can use it from AutoHotkey, don't. You can use XPathNavigator directly, just as you're using XPathNav, but obviously with different syntax. They're both .NET-based classes. Use CLR_LoadLibrary to load the System.Xml assembly.
User avatar
dd900
Posts: 121
Joined: 27 Oct 2013, 16:03

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

06 Aug 2015, 14:18

lexikos wrote:They're both .NET-based classes. Use CLR_LoadLibrary to load the System.Xml assembly.
I figured as much, but I can't get anything to work loading the assemblies directly.

Code: Select all

asm := CLR_LoadLibrary( "System.Xml" )
XPath := CLR_CreateObject( asm, "XPath" )
xPathDoc := new XPath.XPathDocument("path to xml")
xPathNav := xPathDoc.CreateNavigator()

:headwall: Can't even get MessageBox class to work. What am I doing wrong?

Code: Select all

asm := CLR_LoadLibrary("System.Windows.Forms")
msg := CLR_CreateObject(asm, "MessageBox")
msg.Show("text")
User avatar
Relayer
Posts: 160
Joined: 30 Sep 2013, 13:09
Location: Delaware, USA

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

06 Aug 2015, 21:17

Hi,

I have a dll that was created in .net and want to expose the functions for use in AutoHotkey. I think this thread provides the tools to do that but I need a jump start. Any suggestions? I have a list of all the classes / functions in the dll. I disassembled it with ildasm.exe.

Relayer
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

06 Aug 2015, 21:37

dd900 wrote:What am I doing wrong?
I can't begin to fathom how you came up with that code. Why are you trying to instantiate a namespace (XPath) and an abstract class (MessageBox)? CLR_CreateObject creates an object, as in new classname. You'd never use new XPath() or new MessageBox() in C#.

CLR.ahk has no equivalent of the using statement, therefore you must always specify the full class name, including namespace.

You can't use AutoHotkey's new operator to instantiate a .NET class. If you could there'd be no need for CLR_CreateObject (just some sensible way to get the namespace).

CLR_CreateObject is just a small wrapper around the various overloads of Assembly.CreateInstance. For instance, CLR_CreateObject(asm, "XPathNav") is the same as asm.CreateInstance_2("XPathNav", true). Method #2 is used so that the name can be case-insensitive (but this overload doesn't accept parameters for the constructor).

To call static methods like MessageBox.Show(), you need to get a reference to a Type object representing the class, then call Type.InvokeMember.
Relayer wrote:I have a dll that was created in .net and want to expose the functions for use in AutoHotkey.
First, load the dll with CLR_LoadLibrary. .NET dlls don't expose functions; they expose classes with static and instance methods. Either instantiate a class with CLR_CreateObject, or call static methods using reflection (starting with the Assembly object returned by CLR_LoadLibrary). Refer to the first post for usage of the CLR_ functions.
User avatar
dd900
Posts: 121
Joined: 27 Oct 2013, 16:03

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

06 Aug 2015, 23:14

How do I get a reference to the MessageBox class Type?

Code: Select all

asm := CLR_LoadLibrary( "System.Windows.Forms" )
type := asm.GetType()	;type = System.Reflection.Assembly
I've also tried to create a domain then call domain.Load_2(assemblyName), but the only thing I can get to load is "mscorlib". Anything else just returns a "cannot find" error. Could you maybe provide an example?

Edit: Never mind I got it. Thanks for the Info.

Code: Select all

asm := CLR_LoadLibrary( "System.Windows.Forms" )
msg( "I Finally Got It!!!!!" )
msg( "Thanks For the Help..." )
return

msg( text ) {
	global asm
	args := ComObjArray(0xC, 1),  args[0] := text
	return asm.GetType_2("System.Windows.Forms.MessageBox").InvokeMember_3("Show", 0x158, ComObject(13,0), ComObject(13,0), args)
}

escape::
exitapp
What is 0x158? I understand what the parameter is, but I don't know how you got the value that is being passed.
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

07 Aug 2015, 00:12

The second parameter is "A bitmask comprised of one or more BindingFlags". 0x158 is a bitwise combination of BindingFlags. The values aren't shown in the documentation, but you can print them with a bit of C#, or use an IL disassembler like IL Spy.
User avatar
dd900
Posts: 121
Joined: 27 Oct 2013, 16:03

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

07 Aug 2015, 00:40

One more question. How would I get an object reference to System.Windows.Forms.MessageBoxButtons?
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

07 Aug 2015, 19:04

You wouldn't. Enumerations are marshalled as integers, hence 0x158 for the second parameter of InvokeMember.
User avatar
Relayer
Posts: 160
Joined: 30 Sep 2013, 13:09
Location: Delaware, USA

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

07 Aug 2015, 19:35

IL Spy says my dll has "com visible" set to FALSE. Does that make it incompatible with the tools shown in this thread?

Relayer
iPhilip
Posts: 802
Joined: 02 Oct 2013, 12:21

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

06 Sep 2015, 21:57

Thank you Lexikos! Below is code for sending messages from a Gmail account.

iPhilip

Code: Select all

#NoEnv
SetWorkingDir %A_ScriptDir%

UserName = username                                  ; Gmail login name - don't include "@gmail.com" at the end
Password = password                                  ; Gmail login password
From := UserName "@gmail.com"                        ; Can be different from [email protected] - see https://support.google.com/mail/answer/22370?hl=en
To =   [email protected], [email protected], [email protected]   ; Separators can be "," , ";" or "|". Spaces before or after the separators are allowed.
Cc =   [email protected], [email protected], [email protected]   ; Separators can be "," , ";" or "|". Spaces before or after the separators are allowed.
Bcc = [email protected],[email protected],[email protected]   ; Separators can be "," , ";" or "|". Spaces before or after the separators are allowed.
Subject = Test email
Body = <body><h2>Heading #2</h2><p>Html Body</p></body>
IsBodyHtml := true
Attachments = file 1.ahk, file 2.txt,file 3.jpg      ; Separators can be "," , ";" or "|". Spaces before or after the separators or in the file name are allowed.
Priority = Normal                                    ; Values can be "Low", "Normal", or "High"

global GmailObject := CreateGmailObject()            ; This only has to be done once
Gmail(UserName, Password, From, To, Cc, Bcc, Subject, Body, IsBodyHtml, Attachments, Priority)
return

; ---------------------------------------
;                Functions
; ---------------------------------------

; This function sends out a message from a Gmail account - only the first four parameters are required
; The return values are as follows:
;     0 if there were no problems
;     1 if problems were found but the message was sent anyway
;     2 if there was an error sending the message

Gmail(UserName
    , Password
    , From
    , To
    , Cc := ""
    , Bcc := ""
    , Subject := ""
    , Body := ""
    , IsBodyHtml := false
    , Attachments := ""
    , Priority := "Normal") {

   return GmailObject.SendMail(UserName
                             , Password
                             , From
                             , To
                             , Cc
                             , Bcc
                             , Subject
                             , Body
                             , IsBodyHtml ? true : false
                             , Attachments
                             , Priority)
}

; This function compiles the c# code and returns the Gmail object

CreateGmailObject() {
   c# =
   (
      using System;
      using System.Net;
      using System.Net.Mail;
      using System.Net.Mime;
      
      class Gmail
      {         
         public int SendMail(string UserName,
                             string Password,
                             string From,
                             string To,
                             string Cc,
                             string Bcc,
                             string Subject,
                             string Body,
                             bool   IsBodyHtml,
                             string Attachments,
                             string Priority)
         {
            int      ReturnCode = 0;
            string   FileName;
            string[] Files;
            string[] Addresses;
            string[] StringSeparators = new string[] {"," , ";" , "|"};
            
            // Setup the SMTP client
            
            SmtpClient smtp = new SmtpClient("smtp.gmail.com",25);
            smtp.Credentials = new NetworkCredential(UserName,Password);
            smtp.EnableSsl = true;
            
            // Setup the message
            
            MailMessage message = new MailMessage();
            
            // From field
            try
            {
               message.From = new MailAddress(From);
            }
            catch (Exception exception)
            {
               Console.WriteLine("Error: Invalid From email address: '" + From + "'.\nExplanation: " + exception.Message);
            }
            
            // To field - allows multiple email addresses separated by anyone of the StringSeparators
            Addresses = To.Split(StringSeparators,StringSplitOptions.RemoveEmptyEntries);
            foreach (string Address in Addresses)
               try
               {
                  message.To.Add(Address);
               }
               catch (Exception exception)
               {
                  Console.WriteLine("Warning: Invalid To email address: '" + Address + "'.\nExplanation: " + exception.Message);
                  ReturnCode++;
               }
            if (ReturnCode == Addresses.Length)
               Console.WriteLine("Error: At least one To email address must be valid!");
            else if (ReturnCode > 1)
               ReturnCode = 1;
               
            // Cc field - allows multiple email addresses separated by anyone of the StringSeparators
            Addresses = Cc.Split(StringSeparators,StringSplitOptions.RemoveEmptyEntries);
            foreach (string Address in Addresses)
               try
               {
                  message.CC.Add(Address);
               }
               catch (Exception exception)
               {
                  Console.WriteLine("Warning: Invalid Cc email address: '" + Address + "'.\nExplanation: " + exception.Message);
                  ReturnCode = 1;
               }
            
            // Bcc field - allows multiple email addresses separated by anyone of the StringSeparators
            Addresses = Bcc.Split(StringSeparators,StringSplitOptions.RemoveEmptyEntries);
            foreach (string Address in Addresses)
               try
               {
                  message.Bcc.Add(Address);
               }
               catch (Exception exception)
               {
                  Console.WriteLine("Warning: Invalid Bcc email address: '" + Address + "'.\nExplanation: " + exception.Message);
                  ReturnCode = 1;
               }
            
            // Subject field
            message.Subject = Subject;
            
            // Body field
            message.Body = Body;
            if (IsBodyHtml)
               message.IsBodyHtml = true;
            
            // Attachments - allows multiple files separated by anyone of the StringSeparators
            Files = Attachments.Split(StringSeparators,StringSplitOptions.RemoveEmptyEntries);
            foreach (string sFile in Files)
               try
               {
                  Attachment attachment = new Attachment(sFile.Trim(),MediaTypeNames.Application.Octet);
                  message.Attachments.Add(attachment);
               }
               catch (Exception exception)
               {
                  Console.WriteLine("Warning: " + exception.Message);
                  ReturnCode = 1;
               }
            
            // Priority field
            if (Priority == "High")
               message.Priority = MailPriority.High;
            else if (Priority == "Low")
               message.Priority = MailPriority.Low;
            
            // Send the message
            
            try
            {  
               smtp.Send(message);
            }
            catch (Exception exception)
            {
               Console.WriteLine("Result: Message was not sent!\nExplanation: " + exception.Message);
               ReturnCode = 2;
            }
            
            if (ReturnCode == 0)
               Console.WriteLine("Result: Message was sent successfully.");
            else if (ReturnCode == 1)
               Console.WriteLine("Result: Message was sent with warnings.");
            
            // Pass ReturnCode back to the calling function
            
            return ReturnCode;
         }
      }
   )
   asm := CLR_CompileC#(c#, "System.dll")
   return CLR_CreateObject(asm, "Gmail")
}
Note: If you get an authentication error, try any of the solutions here.

Edit: Added error checking for email addresses.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
MstrGareth
Posts: 2
Joined: 03 Nov 2015, 22:08

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

03 Nov 2015, 22:20

Hiyas,

I am having a problem getting CLR.ahk working with my project/test and was hoping someone can help me get my head out of my ...... :lol:


my c# dll

Code: Select all

using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Media;

namespace FSPixel
{    
    public class FSPixel
    {

        public FSPixel()
        {

        }

        public string test()
        {
            return "test";
        }

    }
}


My ahk script:

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.

#include CLR.ahk

f12::
	CLR_Start()
	asm := CLR_LoadLibrary("FSPixel.dll")
	obj := CLR_CreateObject(asm, "FSPixel")
	s := obj.test()
	msgbox %s%
return


f11::

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()

return
The results on the f12 key is an empty msgbox, but the f11 runs perfectly.

For some reason I am not getting a return value from the test function and I don't have a clue why.

The FSPixel.dll and CLR.ahk are in the same directory as the ahk script. I am using the most up to date U64 version of ahk as well.

Any help would be appreciated.
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

04 Nov 2015, 03:13

As I have not executed your code, the following is based on educated guesswork:
For some reason I am not getting a return value from the test function and I don't have a clue why.
You are not getting a return value from the function that you are not calling. obj.Test() will return nothing if obj is not an object.
obj := CLR_CreateObject(asm, "FSPixel")
FSPixel is a namespace, not a class. How about FSPixel.FSPixel?
MstrGareth
Posts: 2
Joined: 03 Nov 2015, 22:08

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

04 Nov 2015, 15:10

lexikos wrote:FSPixel is a namespace, not a class. How about FSPixel.FSPixel?

Excellent, I knew it was a stupid newb mistake :)


Thanks..
hotkeyguy
Posts: 170
Joined: 11 Oct 2014, 12:22

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

22 Jul 2016, 11:39

Hello Lexikos,

I'm trying to get your old XPtable example running: .NET Framework Interop - Scripts and Functions. How must I replace function COM_Invoke which is unkown? Your Simple C# example is working.


Many thanks and greetings
hotkeyguy
lexikos
Posts: 9560
Joined: 30 Sep 2013, 04:07
Contact:

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

22 Jul 2016, 19:05

The control can do a lot more than just what my example shows. To do anything useful you're going to have to learn the control's own API and how to use .NET objects via CLR.ahk.

For CLR.ahk v1.2, you basically only need to know how to do a few things:
  1. Load the assembly. This is pretty obvious. Just give CLR_LoadLibrary the assembly name (if it is registered in the Global Assembly Cache) or full path. However, it might fail if not built for the latest .NET version. In that case, you need to call CLR_Start first with the appropriate .NET version. In my case, I just recompiled XPTable since I don't have .NET 1.1 installed.
  2. Create an object. Just call CLR_CreateObject(asm, typename), where asm is the assembly object and typename is the type name, including namespace. Any additional parameters are passed to the object's constructor. If there are none, you can use the standard Assembly.CreateInstance method instead; i.e. asm.CreateInstance(typename).
  3. In a few cases you need to specify parameter types. For instance, AutoHotkey has no boolean type so you may need to use ComObject(0xB, false) and ComObject(0xB, -1) instead.
  4. Call overloaded methods. Unfortunately COM-callable wrappers in .NET don't really support method overloads. Each one is instead explicitly numbered, like CreateInstance, CreateInstance_2, CreateInstance_3. When you call an overloaded method, you need to figure out which number you want. (However, this doesn't apply to calling methods via .NET Reflection.)
Anything else, like calling static methods, is a matter of finding and using the appropriate .NET API (i.e. Reflection). There's an example in CLR_LoadLibrary (calling the static method Assembly.LoadFrom).


As for translating my old example, it's simple:
  • COM_Invoke(xx, "yy") is hopefully very obvious. Since xx is now an object, just do xx.yy or xx.yy().
  • COM_Invoke(xx, "yy=", ...) means an assignment. Since xx is now an object, just do an assignment: xx.yy := ...
  • "+" xx as a COM_Invoke parameter passes the pointer xx as an object. Since xx is now an object and not a pointer, just use xx.
  • COM_Invoke_(xx, yy, ...) accepts parameters in pairs, with the COM variant type (a number) followed by the value. You can just wrap each pair of parameters in a single ComObject( ), as in 11, false -> ComObject(11, false). There's no need to specify the type of an object (9 or 13) or string (8); you can pass the value directly (remove the number).
  • The old version of CLR_CreateObject had usage similar to COM_Invoke_. Again, you can just remove 8,, but replace 11,true with ComObject(11, -1) (the original code should have used -1).
  • COM_Release is no longer needed since we're using object references, not pointers.

Code: Select all

#NoEnv

asm := CLR_LoadLibrary("XPTable.dll")

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

table.ColumnModel := columnModel
table.TableModel := tableModel

Columns := columnModel.Columns
Columns.Add(col1:=CLR_CreateObject(asm, "XPTable.Models.TextColumn", "Text"))
Columns.Add(col2:=CLR_CreateObject(asm, "XPTable.Models.CheckBoxColumn", "CheckBox"))
Columns.Add(col3:=CLR_CreateObject(asm, "XPTable.Models.ButtonColumn", "Button"))

Rows := tableModel.Rows

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

Rows.Add(row:=CLR_CreateObject(asm,"XPTable.Models.Row"))
Cells := row.Cells

Cells.Add(cel1:=CLR_CreateObject(asm, "XPTable.Models.Cell", "Text 1"))
Cells.Add(cel2:=CLR_CreateObject(asm, "XPTable.Models.Cell", "CheckBox 1", ComObject(0xB,-1)))
Cells.Add(cel3:=CLR_CreateObject(asm, "XPTable.Models.Cell", "Button 1", ComObject(0xB,false)))

Rows.Add(row:=CLR_CreateObject(asm, "XPTable.Models.Row"))
Cells := row.Cells

Cells.Add(cel1:=CLR_CreateObject(asm, "XPTable.Models.Cell", "Text 2"))
Cells.Add(cel2:=CLR_CreateObject(asm, "XPTable.Models.Cell", "CheckBox 2", ComObject(0xB,false)))
Cells.Add(cel3:=CLR_CreateObject(asm, "XPTable.Models.Cell", "Button 2"))

; END CODE BASED ON CODEPROJECT SAMPLE

table.Left := 10
table.Top := 10
table.Width := 300
table.Height := 200

hwnd := table.Handle

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

return

GuiClose:
ExitApp
hotkeyguy
Posts: 170
Joined: 11 Oct 2014, 12:22

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

22 Jul 2016, 19:58

Hello Lexikos,

thank you so much for your again superb support! Your updated example is working. For me the .NET Framework Interop is really a new adventure. Thanks again for get me started. Asap some updated links to XPTable will follow.


Greetings
hotkeyguy

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: MiM and 128 guests