[2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post your working scripts, libraries and tools.
User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

[2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by thqby » 11 Feb 2022, 22:31

This is a way that uses native code to create functions that are the same as built-in functions or to define the properties of an object. This method has higher call performance than DllCall. It can be used to develop AHK third-party library native module, just like using built-in functions.

The c++ header file of ahk2 types

Code: Select all

/************************************************************************
 * @description create native functions or methods from mcode,
 * load ahk modules(write by c/c++) as native classes or fuctions.
 * @author thqby
 * @date 2023/09/09
 * @version 1.1.8
 ***********************************************************************/

class Native extends Func {
	static Prototype.caches := Map()
	static sizes := {
		func: 8 * A_PtrSize + 16,
		method: 9 * A_PtrSize + 16,
		mdfunc: 10 * A_PtrSize + 16,
	}
	static offsets := {
		name: 3 * A_PtrSize + 8,
		buffer_data: 3 * A_PtrSize + 8,
		maxparams: 4 * A_PtrSize + 8,
		outputvars: 5 * A_PtrSize + 16,
		pfn: 6 * A_PtrSize + 16,
	}
	static __New() {
		this.DeleteProp('__New')
		if VerCompare(A_AhkVersion, '2.1-alpha.3') >= 0 {
			for prop in ['sizes', 'offsets']
				for name in (obj := this.%prop%).OwnProps()
					obj.%name% += 2 * A_PtrSize
		}
	}

	; Auto free the NativeFunc object memory, because destructor is overridden, ahk will not free the memory.
	; Freeing memory before func obj is released can cause invalid reads and writes to memory.
	; Delayed free memory, the memory of the last function is freed when the function object is released.
	__Delete() {
		static buffer_data_offset := Native.offsets.buffer_data, pfn_offset := Native.offsets.pfn
		NumPut('ptr', pthis := ObjPtr(this), ObjPtr(this.caches[''] := Buffer()), buffer_data_offset)
		try this.caches.Delete(NumGet(pthis + pfn_offset, 'ptr'))
	}

	; Provides a way for modules to call ahk objects
	static __Get(name, params) => params.Length ? %name%[params*] : %name%
	static __Call(name, params) {
		if name = 'throw' {
			if len := params.Length {
				msg := params[1], extra := len > 1 ? params[2] : '', errobj := len > 2 ? %params[3]% : msg is Integer ? OSError : Error
				throw errobj(msg, -1, extra)
			}
			throw Error('An exception occurred', -1)
		}
		return %name%(params*)
	}

	; create c/c++ function from mcode, and return the function address
	static MCode(hex) {
		static reg := "^([12]?).*" (A_PtrSize = 8 ? "x64" : "x86") ":([A-Za-z\d+/=]+)"
		if (RegExMatch(hex, reg, &m))
			hex := m[2], flag := m[1] = "1" ? 4 : m[1] = "2" ? 1 : hex ~= "[+/=]" ? 1 : 4
		else flag := hex ~= "[+/=]" ? 1 : 4
		if (!DllCall("crypt32\CryptStringToBinary", "str", hex, "uint", 0, "uint", flag, "ptr", 0, "uint*", &s := 0, "ptr", 0, "ptr", 0))
			throw OSError(A_LastError)
		if (DllCall("crypt32\CryptStringToBinary", "str", hex, "uint", 0, "uint", flag, "ptr", p := (code := Buffer(s)).Ptr, "uint*", &s, "ptr", 0, "ptr", 0) && DllCall("VirtualProtect", "ptr", code, "uint", s, "uint", 0x40, "uint*", 0))
			return (this.Prototype.caches[p] := code, p)
		throw OSError(A_LastError)
	}

	/**
	 * Generate a func object with native code
	 * @param BIF Function addresses, `void funcname(ResultToken &aResultToken, ExprTokenType *aParam[], int aParamCount)`
	 * @param MinParams The number of required parameters
	 * @param ParamCount The number of maximum parameters, ParamCount = 255 if the function is variadic
	 * @param OutputVars The array that contains one-based indexs of outputvars, up to seven
	 * @param FID Function ID, `aResultToken.func->mFID`, for code sharing: this function's ID in the group of functions which share the same C++ function
	 */
	static Func(BIF, MinParams := 0, ParamCount := 0, OutputVars := 0, FID := 0) {
		static p__init := ObjPtr(Any.__Init)
		offsets := this.offsets, size := this.sizes.func
		if BIF is String
			BIF := this.MCode(BIF)
		; copy a func object memory
		sbif := Buffer(OutputVars ? size + 7 : size, 0), DllCall('RtlMoveMemory', 'ptr', sbif, 'ptr', p__init, 'uint', size)
		obif := ObjFromPtr(sbif.Ptr)
		if IsVariadic := ParamCount == 255
			ParamCount := MinParams
		else ParamCount := Max(MinParams, ParamCount)
		; init func refcount and base obj
		NumPut('uint', 1, 'uint', 0, 'ptr', ObjPtrAddRef(Native.Prototype), sbif, A_PtrSize)
		; init func infos
		NumPut('ptr', StrPtr('User-BIF'), 'int', ParamCount, 'int', MinParams, 'int', IsVariadic, sbif, offsets.name)
		NumPut('ptr', BIF, 'ptr', FID, sbif, offsets.pfn)
		if OutputVars {
			NumPut('ptr', s := sbif.Ptr + size, sbif, offsets.outputvars)	; mOutputVars
			loop Min(OutputVars.Length, 7)	; MAX_FUNC_OUTPUT_VAR = 7
				s := NumPut('uchar', OutputVars[A_Index], s)
		}
		NumPut('ptr', 0, 'ptr', 0, ObjPtr(sbif), offsets.buffer_data) ; Avoid the memory of func object be freed when buffer is released
		return obif
	}

	/**
	 * Generate a method with native code, is same with `Native.Func`
	 * @param base The base of instance
	 */
	static Method(base, BIM, MIT, MinParams := 0, ParamCount := 0, MID := 0) {
		static pOwnProps := ObjPtr({}.OwnProps)
		offsets := this.offsets, size := this.sizes.method, nameoffset := offsets.name
		if BIM is String
			BIM := this.MCode(BIM)
		sbim := Buffer(size, 0), DllCall('RtlMoveMemory', 'ptr', sbim, 'ptr', pOwnProps, 'uint', size)
		obim := ObjFromPtr(sbim.Ptr), IsVariadic := ParamCount == 255
		switch MIT, false {
			case 'call', 2: ++MinParams, ParamCount := IsVariadic ? MinParams : Max(MinParams, ParamCount + 1), NumPut('ptr', StrPtr('User-BIM'), sbim, nameoffset), MIT := 2
			case 'set', 1: MinParams += 2, ParamCount += 2, MIT := 1, NumPut('ptr', StrPtr('User-BIM.Set'), sbim, nameoffset)
			case 'get', 0: ++MinParams, ParamCount := Max(MinParams, ParamCount + 1), NumPut('ptr', StrPtr('User-BIM.Get'), sbim, nameoffset), MIT := 0
		}
		NumPut('uint', 1, 'uint', 0, 'ptr', ObjPtrAddRef(Native.Prototype), sbim, A_PtrSize)
		NumPut('int', Max(MinParams, ParamCount), 'int', MinParams, 'int', IsVariadic, sbim, offsets.maxparams)
		NumPut('ptr', BIM, 'ptr', base, 'uchar', MID, 'uchar', MIT, sbim, offsets.pfn)
		NumPut('ptr', 0, 'ptr', 0, ObjPtr(sbim), offsets.buffer_data)
		return obim
	}

	/**
	 * Defines a new own property with native code, is similar with `obj.DefineProp`
	 * @param obj Any object
	 * @param name The name of the property
	 * @param desc An object with one of following own properties, or both `Get` and `Set`
	 * 
	 * `Call, Get, Set`: an object with `BIM` property and optional properties `MinParams`, `ParamCount`, `OutputVars`, `MID`, is same with the parameters of `BuiltInFunc`
	 * 
	 * `BIM`: `void (IObject::* ObjectMethod)(ResultToken& aResultToken, int aID, int aFlags, ExprTokenType* aParam[], int aParamCount)`
	 */
	static DefineProp(obj, name, desc) {
		descobj := {}, baseobj := ObjPtr(obj.Base)
		for MIT in ['call', 'set', 'get']
			if desc.HasOwnProp(MIT) {
				t := desc.%MIT%, MinParams := ParamCount := MID := 0, BIM := t.BIM
				for k in ['MinParams', 'ParamCount', 'MID']
					if t.HasOwnProp(k)
						%k% := t.%k%
				descobj.%MIT% := this.Method(baseobj, BIM, MIT, MinParams, ParamCount, MID)
			}
		obj.DefineProp(name, descobj)
	}

	/**
	 * Create a class with constructor function address
	 * @param ctor constructor function address, `void funcname(ResultToken &aResultToken, ExprTokenType *aParam[], int aParamCount)`
	 * 
	 * constructor function used to create an object
	 */
	static Class(name, ctor := 0, FID := 0) {
		cls := Class(), NumPut('uint', 1, ObjPtr(cls.Prototype := { Base: Object.Prototype, __Class: name }), A_PtrSize + 4)
		if ctor
			cls.DefineProp('Call', {call: this.Func(ctor, 1, 255, 0, FID)})
		return cls
	}

	/**
	 * Load a dll file with the specified format to create native functions and classes
	 * @param path ahk module path
	 * @param load_symbols Load symbols that specific names, will overwrite existing global classes
	 * @param loader Create native functions and classes based on the information provided by the module, or do it in the module
	 * @param provider Used to provide the ahk objects required by the module
	 */
	static LoadModule(path, load_symbols := 0, loader := 0, provider := 0) {
		if !(module := DllCall('LoadLibrary', 'str', path, 'ptr'))
			throw OSError(A_LastError)
		module_load_addr := DllCall('GetProcAddress', 'ptr', module, 'astr', 'ahk2_module_load', 'ptr') || DllCall('GetProcAddress', 'ptr', module, 'ptr', 1, 'ptr')
		if !module_load_addr
			throw Error('Export function not found')
		if load_symbols {
			t := Map(), t.CaseSense := false
			for k in load_symbols
				t[k] := true
			load_symbols := t
		}
		if !p := DllCall(module_load_addr, 'ptr', ObjPtr(loader || default_loader), 'ptr', ObjPtr(provider || Native), 'cdecl ptr')
			throw Error('Load module fail', -1, OSError(A_LastError).Message)
		return ObjFromPtr(p)

		default_loader(count, addr) {
			static size := 2 * A_PtrSize + 16
			symbols := Map(), symbols.CaseSense := false, symbols.DefineProp('__Call', { call: (s, n, p) => s[n](p*) })
			loop count {
				name := StrGet(pname := NumGet(addr, 'ptr'))
				if load_symbols && !load_symbols.Has(name) {
					addr += size
					continue
				}
				funcaddr := NumGet(addr += A_PtrSize, 'ptr')
				minparams := NumGet(addr += A_PtrSize, 'uchar')
				maxparams := NumGet(addr += 1, 'uchar')
				id := NumGet(addr += 1, 'ushort')
				if member_count := NumGet(addr += 2, 'uint') {
					try {
						if !load_symbols || !IsObject(symbol := %name%)
							throw
						symbols[name] := symbol
						if !symbol.HasOwnProp('Prototype')
							symbol.Prototype := this.Class(name, 0).Prototype
						if funcaddr
							symbol.DefineProp('Call', {call: me := this.Func(funcaddr, 1, maxparams + 1, 0, id)}), NumPut('ptr', pname, ObjPtr(me), 3 * A_PtrSize + 8)
					} catch
						symbols[name] := symbol := this.Class(name, funcaddr, id)
					pmem := NumGet(addr += 4, 'ptr')
				}
				staticmembers := {}, members := {}
				loop member_count {
					name := StrGet(pname := NumGet(pmem, 'ptr'))
					method := NumGet(pmem += A_PtrSize, 'ptr')
					id := NumGet(pmem += A_PtrSize, 'uchar')
					mit := NumGet(pmem += 1, 'uchar')
					minparams := NumGet(pmem += 1, 'uchar')
					maxparams := NumGet(pmem += 1, 'uchar')
					namearr := StrSplit(name, '.')
					if mit < 2 && namearr.Length > 2
						namearr.Pop()
					name := namearr.Pop()
					sub := mit = 2 ? 'call' : mit = 1 ? 'set' : 'get'
					if namearr.Length < 2
						mems := staticmembers, pbase := ObjPtr(symbol.Base)
					else
						mems := members, pbase := ObjPtr(symbol.Prototype.Base)
					if !mems.HasOwnProp(name)
						t := mems.%name% := {}
					else t := mems.%name%
					t.%sub% := me := this.Method(pbase, method, mit, minparams, maxparams, id)
					NumPut('ptr', pname, ObjPtr(me), 3 * A_PtrSize + 8)
					pmem += A_PtrSize - 3
				} else {
					symbols[name] := symbol := this.Func(funcaddr, minparams, maxparams, 0, id)
					NumPut('ptr', pname, ObjPtr(symbol), 3 * A_PtrSize + 8)
					if NumGet(addr += 4, 'uchar')	; set mOutputVars
						NumPut('ptr', addr, ObjPtr(symbol), 5 * A_PtrSize + 16)
				}
				if member_count {
					if symbol == Map	; Break circular references if Map has define native funcs
						OnExit(onexitapp.Bind(Map(Map, [staticmembers*], Map.Prototype, [members*])))
					for name, desc in staticmembers.OwnProps()
						symbol.DefineProp(name, desc)
					symbol := symbol.Prototype
					for name, desc in members.OwnProps()
						symbol.DefineProp(name, desc)
				}
				addr += 8
			}
			return symbols

			onexitapp(todels, *) {
				for o, arr in todels
					for n in arr
						try o.DeleteProp(n)
			}
		}
	}

	/**
	 * @param fn c/c++ funtion pointer
	 * @param sig Signature of function `rettype(argtypes)`, [rettype, argtypes*]
	 */
	static MdFunc(fn, sig, prototype := 0) {
		static p_mdfunc := ObjPtr(MsgBox), size := this.sizes.mdfunc
		static mdtypes := {
			void: 0,
			int8: 1,
			uint8: 2,
			int16: 3,
			uint16: 4,
			int32: 5,
			uint32: 6,
			int64: 7,
			uint64: 8,
			float64: 9,
			float32: 10,
			string: 11,
			object: 12,
			variant: 13,
			bool32: 14,
			resulttype: 15,
			fresult: 16,
			params: 17,
			optional: 0x80,
			retval: 0x81,
			out: 0x82,
			; thiscall,
			uintptr: A_PtrSize = 8 ? 8 : 6,
			intptr: A_PtrSize = 8 ? 7 : 5,
		}
		offsets := this.offsets, mdfn_offset := offsets.pfn
		if fn is String
			fn := this.MCode(fn)
		; copy a func object memory
		smdf := Buffer(size, 0), DllCall('RtlMoveMemory', 'ptr', smdf, 'ptr', p_mdfunc, 'uint', size)
		p := NumPut('ptr', fn, smdf, mdfn_offset), ac := pc := MinParams := 0
		if prototype
			NumPut('char', 1, NumPut('ptr', IsObject(prototype) ? ObjPtr(prototype) : prototype, p) + A_PtrSize + 3), ac := pc := MinParams := 1
		IsVariadic := false, MaxResultTokens := 0
		if sig is Array {
			if sig.Length > 1
				ret := sig.RemoveAt(1), smdf.Size += sig.Length, ret is String && ret := mdtypes.%ret%
			else ret := 0
			opt := false, retval := false, out := 0
			loop sig.Length {
				c := sig[A_Index]
				if c is String
					sig[A_Index] := c := mdtypes.%c%
				NumPut('uchar', c, smdf, size + A_Index - 1)
				if c >= 128 {
					if c = 128
						opt := true
					else if c = 0x82
						out := c
					else if c = 0x81
						retval := true
					continue
				}
				if A_PtrSize = 4 && c >= 7 && c <= 9 && !out && !opt
					ac++
				ac++
				if c = 17
					IsVariadic := true
				else if !retval {
					++pc
					if !opt && pc - 1 = MinParams
						MinParams := pc
					if c = 13 && out
						++MaxResultTokens
				}
				opt := false, retval := false, out := 0
			}
			NumPut('ptr', smdf.Ptr + size, 'uchar', ret, 'uchar', MaxResultTokens, 'uchar', ac, smdf, 2 * A_PtrSize + mdfn_offset)
		} else throw TypeError()
		ParamCount := pc
		obif := ObjFromPtr(smdf.Ptr)
		; init func refcount and base obj
		NumPut('uint', 1, 'uint', 0, 'ptr', ObjPtrAddRef(Native.Prototype), smdf, A_PtrSize)
		; init func infos
		NumPut('ptr', StrPtr('User-MdFunc'), 'int', ParamCount, 'int', MinParams, 'int', IsVariadic, smdf, offsets.name)
		NumPut('ptr', 0, 'ptr', 0, ObjPtr(smdf), offsets.buffer_data)	; Avoid the memory of func object be freed when buffer is released
		return obif
	}
}
Examples and performance tests

Code: Select all

#Include Native.ahk

if A_PtrSize = 4 {
	MsgBox 'You must run 64-bit AHK, because 32 bit machine code is not provided'
	ExitApp
}

; sum(a, b) => a + b

; void add(ResultToken& aResultToken, ExprTokenType* aParam[], int aParamCount) {
; 	aResultToken.symbol = SYM_INTEGER;
; 	aResultToken.value_int64 = aParam[0]->value_int64 + aParam[1]->value_int64;
; }
sum_native := Native.Func("1,x64:C7411001000000488B4208488B124C8B004C03024C8901C3", 2)

; int add(int a, int b) { return a + b; }
code2 := Native.MCode("1,x64:8D0411C3")
sum_dllcall := DllCall.Bind(code2, 'int', , 'int', , 'int')
sum_mdfunc := Native.MdFunc(code2, ['int32', 'int32', 'int32'])

; class Calculate : public IObject {
; 	void sum(ResultToken& aResultToken, int aID, int aFlags, ExprTokenType* aParam[], int aParamCount);
; };
; void Calculate::sum(ResultToken& aResultToken, int aID, int aFlags, ExprTokenType* aParam[], int aParamCount) {
; 	aResultToken.symbol = SYM_INTEGER;
; 	aResultToken.value_int64 = aParam[0]->value_int64 + aParam[1]->value_int64;
; }
obj := {}
Native.DefineProp(obj, 'sum', {
	call: {
		BIM: "1,x64:488B442428C7421001000000488B48084C8B00488B01490300488902C3",
		MinParams: 2
	}
})

MsgBox 'call method, result: ' obj.sum(2434, 75698)

sum_userfunc(a, b) => a + b

test_sum(funcnames, times := 10000000) {
	result := ''
	for f in funcnames {
		fn := %f%
		t := QPC()
		loop times
			fn(156498, 189298)
		result .= f ': ' (QPC() - t) 'ms`n'
	}
	return result
}

MsgBox 'The performance test, call func ' (times := 10000000) ' times'
MsgBox test_sum(['sum_userfunc', 'sum_dllcall', 'sum_mdfunc', 'sum_native'], times)

QPC() {
	static c := 0, f := (DllCall("QueryPerformanceFrequency", "int64*", &c), c /= 1000)
	return (DllCall("QueryPerformanceCounter", "int64*", &c), c / f)
}

; dll module
MsgBox "test load dll, this dll depends on VCRUNTIME140.dll"
ahkmodule := Native.LoadModule(A_ScriptDir '\ahk2.dll')
m := Map('msg', MsgBox, 'rep', StrReplace)
; call Map.Prototype.__Call native code
m.msg('hello' m.rep(' this a str', 'str', 'string'))
; a c++ class from dll
a := ahkmodule.myclass()
try
	a.err()
catch as e
	MsgBox e.Message
MsgBox a.Value() ' ' a.int()
ahkmodule.myfunc()
ahkmodule.myfunc('qqqq')
Map.Prototype.DeleteProp('__Call')
ahkmodule['myclass'](1)
; myclass only accepts 0-1 param
ahkmodule['myclass'](1, 2)
Attachments
native-test.7z
(22.74 KiB) Downloaded 398 times
Last edited by thqby on 09 Sep 2023, 07:53, edited 14 times in total.

kazhafeizhale
Posts: 77
Joined: 25 Dec 2018, 10:58

Re: create function or method with native code

Post by kazhafeizhale » 12 Feb 2022, 04:57

🚀nice!

neogna2
Posts: 591
Joined: 15 Sep 2016, 15:44

Re: create function or method with native code

Post by neogna2 » 13 Feb 2022, 10:13

@thqby What compiler do you generate the example script mcode with? I tried with TDM-GCC in mcode4gcc.ahk and get errors for the code1 and code3 source (code2 source works). Code1 compiler error:

Code: Select all

SOURCEFILE:1:21: error: expected ')' before '&' token
    1 | void add(ResultToken& aResultToken, ExprTokenType* aParam[], int aParamCount) {
      |                     ^
      |                     )
I don't know C/C++ so maybe there is some easy fix for this.

Also, if you have time do you have a link to some resource on the difference between your native code approach and the old/regular mcode approach? I'm trying to learn. I see that some people distinguish the terms native code and machine code but I'm not sure what that difference means in relation to AutoHotkey and your script.
https://stackoverflow.com/questions/3434202/what-is-the-difference-between-native-code-machine-code-and-assembly-code/

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: create function or method with native code

Post by thqby » 13 Feb 2022, 10:41

I use MSVC2019 compiler with MCodeGen.ahk
I feel this is simpler than GCC, despite the large development environment.
And Web compiler is also a good idea. @neogna2
https://www.godbolt.org/
image.png
image.png (100.1 KiB) Viewed 5475 times

neogna2
Posts: 591
Joined: 15 Sep 2016, 15:44

Re: create function or method with native code

Post by neogna2 » 14 Feb 2022, 05:54

thqby wrote:
13 Feb 2022, 10:41
I use MSVC2019 compiler with MCodeGen.ahk
I feel this is simpler than GCC, despite the large development environment.
Ok, I will try that.
thqby wrote:
13 Feb 2022, 10:41
And Web compiler is also a good idea.
https://www.godbolt.org/
Looks neat, but it too throws error on code1 from your example script.
error.png
error.png (53.19 KiB) Viewed 5469 times
Godbolt works with code2 without error. But can you tell me how to get it to save/output mcode for use in AutoHotkey?

swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: create function or method with native code

Post by swagfag » 14 Feb 2022, 06:42

neogna2 wrote:
14 Feb 2022, 05:54
but it too throws error on code1 from your example script.
because u didnt include any of the headers. ur types are undefined
Godbolt works with code2 without error.
because it contains only primitive types
how to get it to save/output mcode for use in AutoHotkey?
add the /FAc msvc compiler switch(or whatever the equivalent is for the compiler uve chosen), clean the machine code listing with some regexes

neogna2
Posts: 591
Joined: 15 Sep 2016, 15:44

Re: create function or method with native code

Post by neogna2 » 14 Feb 2022, 07:34

swagfag wrote:
14 Feb 2022, 06:42
because u didnt include any of the headers. ur types are undefined
Ok. In a case like this when the C++ source was given without any .h headers file how can we determine which headers are needed?
swagfag wrote:
14 Feb 2022, 06:42
how to get it to save/output mcode for use in AutoHotkey?
add the /FAc msvc compiler switch(or whatever the equivalent is for the compiler uve chosen), clean the machine code listing with some regexes
Thanks. Here is an example of those steps for anyone else following along.
Paste the C++ source int add(int a, int b) { return a + b; } into Godbolt and choose x64 msvc v19.latest and set flags /FAc /O2 to get this output

Code: Select all

a$ = 8
b$ = 16
?add@@YAHHH@Z PROC                                  ; add, COMDAT
  00000 8d 04 11   lea         eax, DWORD PTR [rcx+rdx]
  00003 c3                 ret     0
?add@@YAHHH@Z ENDP                                  ; add
from which we can extract 8d0411c3 which is identical to the mcode for code2 in thqby's example script.

A first attempt at a function to parse a MCode string from Godbolt output

Code: Select all

MCodeFromGodbolt(GodboltText) {
    Local MCode := ""
    ;pattern: <2 spaces><5-digit linenum><space><1+ hex bytes space separated><3+ spaces>
    Loop Parse GodboltText, "`n", "`r"
        If RegExMatch(A_LoopField, "U)^  \w{5} (\w{2} .*)   ", &Match)
            MCode .= StrReplace(Match.1, " ", "")
    return MCode := Format("{:U}", MCode) ;uppercase
}

TestText := "
(
a$ = 8
b$ = 16
?add@@YAHHH@Z PROC                                  ; add, COMDAT
  00000 8d 04 11   lea         eax, DWORD PTR [rcx+rdx]
  00003 c3                 ret     0
?add@@YAHHH@Z ENDP                                  ; add
)"

MsgBox MCodeFromGodbolt(TestText) ;expected output 8D0411C3

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by thqby » 16 Feb 2022, 08:28

The new version now supports loading classes and functions from dlls written using the AHK_API. native-test.7z contains header files, source code and 64-bit dll and example.


lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by lexikos » 21 Apr 2022, 22:03

It should be noted that this script relies on implementation details and may break with any future release.

Noblish
Posts: 28
Joined: 22 Mar 2020, 06:10

Re: Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by Noblish » 08 Jun 2022, 11:57

Can someone give a example..?
I can bind any dll and add its functions ?

BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by BoBo » 08 Jun 2022, 13:27

Noblish wrote:
08 Jun 2022, 11:57
Can someone give a example..?
I can bind any dll and add its functions ?
Let's see if we can find that "any"-word somewhere in here...
...now supports loading classes and functions from dlls written using the AHK_API.
Erm, ...? :think:

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: [rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by thqby » 24 Nov 2022, 19:52

Add MdFunc to rc.1, which is the internal invocation of built-in functions like MsgBox, with a slightly slower overhead than fixed-signed native functions and significantly faster than DllCall.
Currently, only base types are supported, and astr implicit conversions are not supported.

Code: Select all

pmsg := DllCall('GetProcAddress', 'ptr', DllCall('GetModuleHandleW', 'wstr', 'user32.dll', 'ptr'), 'astr', 'MessageBoxW', 'ptr')
msg := Native.MdFunc(pmsg, ['int32','uintptr', 'string', 'string', 'uint32'])
msg(0, 'dasd','title',0)

lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: [2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by lexikos » 24 Nov 2022, 23:13

:thumbsup:

FYI, I plan to implement something like this in future, probably v2.1. At some point MdFunc will be optimized to assemble more efficient machine code for each function.

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: [2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by thqby » 25 Nov 2022, 02:14

@lexikos
Is mdfunc compatible with cdecl and stdcall at the same time? How does this work.

lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: [2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by lexikos » 25 Nov 2022, 03:52

Cdecl and stdcall aren't relevant on x64, so we can set that aside for the moment.

With cdecl, the caller cleans up the stack. For instance, the caller pushes n parameters, calls the function, then pops n parameters. It can do this any way it likes, as long as it restores the stack pointer to the correct value before returning or attempting to use it.

With stdcall, the callee cleans up the stack. For instance, the caller pushes n parameters and calls the function, which pops n parameters when it returns. This is usually achieved with an instruction like ret n*4, which pops the return address off the stack, adjusts the stack pointer by n*4 bytes and then jumps to the return address. The end result is that when execution returns to the callee, the stack no longer includes the parameters.

In both cases, DllCall saves the stack pointer before calling the function. If you did not specify cdecl, it checks whether the stack pointer has changed by an unexpected amount, and if so, it throws an error (or sets ErrorLevel). Then it restores the stack pointer to its previous value. If you specify cdecl, it just restores the stack pointer and ignores any change which might have occurred if the function really wasn't cdecl.

If you pass the correct parameters, it really doesn't matter whether the function is cdecl or stdcall, as long as the stack pointer gets restored to its proper value. For DllCall on x86, the additional checks are useful for detecting when you pass too few or too many parameters, but this is no use on x64 or with cdecl, or for detecting invalid parameter values. For MdFunc, all function signatures are known to be correct, so additional checks are not warranted. When it is eventually used for external functions, I will probably keep it simple and not add these checks, because they are no use on x64. There will be just a slight bit more responsibility being placed on the script author to get the signature correct for stdcall functions on x86.

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: [2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by thqby » 25 Nov 2022, 05:57

I know this, but I'm confused that I didn't see the code of the recovery stack in mdfunc.

In addition, i have implemented the automatic calculation of function signatures at compile time using c++templates. Now (u)intptr is not an independent type, but depends on the compilation bits, which leads to two sets of signatures exported.

crocodile
Posts: 98
Joined: 28 Dec 2020, 13:41

Re: [2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by crocodile » 25 Nov 2022, 06:44

Can you add a call to libcurl.dll or sqlite3.dll, or any of the normal dll examples? Or can it only call dlls that it compiles itself?

User avatar
thqby
Posts: 408
Joined: 16 Apr 2021, 11:18
Contact:

Re: [rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by thqby » 25 Nov 2022, 08:37

thqby wrote:
24 Nov 2022, 19:52

Code: Select all

pmsg := DllCall('GetProcAddress', 'ptr', DllCall('GetModuleHandleW', 'wstr', 'user32.dll', 'ptr'), 'astr', 'MessageBoxW', 'ptr')
msg := Native.MdFunc(pmsg, ['int32','uintptr', 'string', 'string', 'uint32'])
msg(0, 'dasd','title',0)
@crocodile
This is a demonstration of winapi MessageBoxW.

In addition, the current string type only supports utf-16, ansi/utf-8 string is passed by address.
Last edited by thqby on 25 Nov 2022, 10:04, edited 1 time in total.

crocodile
Posts: 98
Joined: 28 Dec 2020, 13:41

Re: [2.0-rc.1] Native.ahk - A way for binding third-party dll libraries to AHK, and create native functions from MCode

Post by crocodile » 25 Nov 2022, 09:50

This is really nice, it seems that modularity is coming. :D
Is there a way to avoid frequent use of GetProcAddress() if I use MdFunc() to load all functions of the whole dll? Is it possible to get all function names and addresses at once?

Post Reply

Return to “Scripts and Functions (v2)”