Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
guides:machine_code [2023-12-20 15:53] – [Recursive Function] Fix missing parenthesis geek | guides:machine_code [2024-01-02 16:18] (current) – Add section 6, Importing functions geek | ||
---|---|---|---|
Line 33: | Line 33: | ||
If using MCL, the easiest compiler to install is likely [[https:// | If using MCL, the easiest compiler to install is likely [[https:// | ||
- | ===== Examples | + | ===== Learning MCode from Scratch |
- | Once you have MCL in your Lib folder and a compatible compiler installed, you are ready to begin creating MCode functions. | + | ==== 1. Return a Number ==== |
- | ==== Return | + | One of the simplest MCode projects you can do is to have a bit of MCode that can return a number. |
- | The simplest function you can create will be one that returns a number, like this: | + | For example, the C code: |
<code c> | <code c> | ||
- | int MyFunction() | + | int alpha() { return 42; } |
- | { | + | |
- | | + | |
- | } | + | |
</ | </ | ||
- | With the MCL library, MCode functions must be //exported// to AutoHotkey. You can export | + | With a function this trivial, it is possible to manually convert the C code to MCode using a compiler like [[https://godbolt.org/|godbolt]]. From godbolt the C code can be entered, |
+ | |||
+ | {{: | ||
+ | |||
+ | The hex code on every other line of the output | ||
+ | |||
+ | To do this, construct a buffer large enough to hold the bytes of machine code, rounded up to the nearest multiple of 8. Note that we can safely ignore any '' | ||
+ | |||
+ | Next, split the bytes into groups of at most 8: '' | ||
+ | |||
+ | Now reverse the bytes in each group: '' | ||
+ | |||
+ | Join the bytes together | ||
+ | |||
+ | Now you can assemble your mcode: | ||
+ | <runner ahk2> | ||
+ | # | ||
+ | mcode := Buffer(16) | ||
+ | NumPut('Int64', 0x00002ab8e5894855, | ||
+ | |||
+ | ; Once you have your buffer populated, you must use '' | ||
+ | ; the protection mode of the RAM held in the buffer to allow code within it to | ||
+ | ; be executed. By default, memory is not executable for security reasons. | ||
+ | if !DllCall(" | ||
+ | throw Error(" | ||
+ | |||
+ | ; Now that your buffer is executable, you may execute it using DllCall | ||
+ | MsgBox DllCall(mcode, | ||
+ | </ | ||
+ | |||
+ | By calling the buffer with DllCall, execution will jump to the first byte of the machine code and execute whatever function is at that position. For code compiling only one function, it will normally be the start of that one function. | ||
+ | |||
+ | ==== 2. Find the Length of a String ==== | ||
+ | |||
+ | A function that just returns one number is not very useful. A more useful function could be one to find the length of a string: | ||
<code c> | <code c> | ||
- | #include < | + | int StringLen(short *str) |
- | MCL_EXPORT(MyFunction, | + | |
- | int MyFunction() | + | |
{ | { | ||
- | | + | int i = 0; |
+ | for (; str[i] != 0; i++) {} | ||
+ | return | ||
} | } | ||
</ | </ | ||
- | Once you have your appropriate C code, you may give it to '' | + | This function loops over the characters of a unicode string until it reaches the end, then returns the count of characters. |
+ | |||
+ | When fed into godbolt, we get an output like '' | ||
+ | |||
+ | Performing the same manipulations as before, we can write the MCode as follows: | ||
<runner ahk2> | <runner ahk2> | ||
#Requires AutoHotkey v2.0 | #Requires AutoHotkey v2.0 | ||
- | #Include <MCL> | ||
- | c := " | + | code := Buffer(64) |
- | ( | + | NumPut( |
- | #include < | + | ' |
- | MCL_EXPORT(MyFunction, Int) | + | ' |
- | int MyFunction() | + | ' |
- | { | + | ' |
- | return 42; | + | code |
- | } | + | ) |
- | )" | + | |
- | MsgBox MCL.StandaloneAHKFromC(c) | + | if !DllCall(" |
+ | throw Error(" | ||
+ | |||
+ | MsgBox "The string is " DllCall(code, " | ||
</ | </ | ||
- | This will output | + | As the functions get more complicated, |
+ | |||
+ | When provided to joedf' | ||
<runner ahk2> | <runner ahk2> | ||
#Requires AutoHotkey v2.0 | #Requires AutoHotkey v2.0 | ||
- | MyC() { | + | ptr := MCode(' |
- | static | + | |
- | . "DzC4KgAAAMOQkJCQkJCQkJCQ" | + | MsgBox "The string is " DllCall(ptr, |
- | if (64 != A_PtrSize | + | |
- | throw Error("$Name does not support | + | MCode(mcode) { |
- | ; MCL standalone | + | static |
- | ; Copyright | + | if (!regexmatch(mcode, |
- | ; https:// | + | return |
- | if IsSet(lib) | + | if (!DllCall(" |
+ | | ||
+ | p := DllCall("GlobalAlloc", " | ||
+ | if (c=" | ||
+ | DllCall(" | ||
+ | if (DllCall(" | ||
+ | | ||
+ | DllCall(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Similarly, when provided to the MCL compiler it will output the loader | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | |||
+ | lib := MCode() | ||
+ | |||
+ | MsgBox "The string is " DllCall(lib, " | ||
+ | |||
+ | MCode() { | ||
+ | static lib := false | ||
+ | if lib | ||
return lib | return lib | ||
- | if !DllCall(" | + | switch A_PtrSize { |
+ | case 4: code := Buffer(20), exports := {StringLen: 0}, b64 := "" | ||
+ | . " | ||
+ | case 8: code := Buffer(32), exports := {StringLen: 0}, b64 := "" | ||
+ | . " | ||
+ | default: throw Error(A_ThisFunc " does not support " A_PtrSize * 8 " bit AHK" | ||
+ | } | ||
+ | if !DllCall(" | ||
throw Error(" | throw Error(" | ||
- | if (r := DllCall(" | ||
- | throw Error(" | ||
if !DllCall(" | if !DllCall(" | ||
throw Error(" | throw Error(" | ||
- | lib := { | + | for k, v in exports.OwnProps() |
+ | exports.%k% := code.Ptr + v | ||
+ | return | ||
+ | exports: exports, | ||
code: code, | code: code, | ||
- | MyFunction: (this) => | + | Ptr: exports.StringLen, |
- | DllCall(this.code.Ptr + 0, " | + | StringLen: exports.StringLen, |
} | } | ||
- | return lib | ||
} | } | ||
- | |||
- | library := MyC() | ||
- | |||
- | MsgBox library.MyFunction() | ||
</ | </ | ||
- | ==== Find the End of a String ==== | + | With the MCL library on a system which has a compatible compiler installed, |
- | + | ||
- | The functions | + | |
- | + | ||
- | When testing complicated functions with MCL, it is not always necessary | + | |
- | + | ||
- | Here, we are writing | + | |
<runner ahk2> | <runner ahk2> | ||
Line 126: | Line 183: | ||
library := MCL.FromC(" | library := MCL.FromC(" | ||
( | ( | ||
- | #include < | + | int StringLen(short *str) |
- | MCL_EXPORT(StringLen, | + | |
- | int _stdcall | + | |
{ | { | ||
int i = 0; | int i = 0; | ||
Line 136: | Line 191: | ||
)") | )") | ||
- | MsgBox " | + | MsgBox " |
</ | </ | ||
- | Once you are done developing your C library, you may use '' | + | It is only once you are done developing your MCode, you would use the '' |
- | ==== Recursive Function ==== | ||
- | In MCode tools other than MCL, recursive | + | ==== 3. Multiple Functions ==== |
+ | |||
+ | When working with C code that defines multiple functions, MCode becomes much trickier to generate. Consider the code as follows: | ||
+ | |||
+ | <code c> | ||
+ | int alpha() { return 42; } | ||
+ | int beta() { return alpha(); } | ||
+ | </ | ||
+ | |||
+ | When [[https:// | ||
+ | |||
+ | - When the machine code is generated, only '' | ||
+ | - If beta //were// to be called, //it doesn' | ||
+ | |||
+ | Traditional | ||
+ | |||
+ | <runner ahk2> | ||
+ | alpha := MCode(' | ||
+ | beta := MCode(' | ||
+ | MsgBox DllCall(beta, | ||
+ | |||
+ | MCode(mcode) { | ||
+ | static e := Map(' | ||
+ | if (!regexmatch(mcode, | ||
+ | return | ||
+ | if (!DllCall(" | ||
+ | return | ||
+ | p := DllCall(" | ||
+ | if (c=" | ||
+ | DllCall(" | ||
+ | if (DllCall(" | ||
+ | return p | ||
+ | DllCall(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This kind of workaround is not necessary when using MCL, which uses its built-in linker and loader to automatically identify function offsets and adjust the MCode at run-time so that references to other functions | ||
+ | |||
+ | For code that defines multiple functions to be called from AHK, those functions must be // | ||
+ | |||
+ | To export a function, you must include the MCL header file and then use the '' | ||
<runner ahk2> | <runner ahk2> | ||
Line 152: | Line 246: | ||
( | ( | ||
#include < | #include < | ||
- | MCL_EXPORT(Factorial, UInt, a, UInt, b, UInt) | + | |
+ | MCL_EXPORT(Factorial) | ||
unsigned int Factorial(unsigned int a, unsigned int b) | unsigned int Factorial(unsigned int a, unsigned int b) | ||
{ | { | ||
Line 159: | Line 254: | ||
else | else | ||
return b; | return b; | ||
+ | } | ||
+ | |||
+ | MCL_EXPORT(FactorialCaller) | ||
+ | unsigned int FactorialCaller(unsigned int a) | ||
+ | { | ||
+ | return Factorial(a, | ||
} | } | ||
)") | )") | ||
- | loop 5 | + | loop 5 { |
- | MsgBox A_Index "! = " lib.Factorial(A_Index, | + | MsgBox |
+ | " | ||
+ | "Factorial(" | ||
+ | ) | ||
+ | } | ||
</ | </ | ||
- | + | Optionally, the export macro can accept DllCall types as parameters. When these are specified, | |
- | ==== Multiple Functions ==== | + | |
- | + | ||
- | In MCode tools other than MCL, having multiple functions would not work properly without complicated workarounds. You can read more about those workarounds in the [[https://www.autohotkey.com/boards/viewtopic.php? | + | |
<runner ahk2> | <runner ahk2> | ||
Line 178: | Line 280: | ||
( | ( | ||
#include < | #include < | ||
- | MCL_EXPORT(Factorial, | + | |
+ | MCL_EXPORT(Factorial, | ||
unsigned int Factorial(unsigned int a, unsigned int b) | unsigned int Factorial(unsigned int a, unsigned int b) | ||
{ | { | ||
Line 187: | Line 290: | ||
} | } | ||
- | MCL_EXPORT(FactorialCaller, | + | MCL_EXPORT(FactorialCaller, |
unsigned int FactorialCaller(unsigned int a) | unsigned int FactorialCaller(unsigned int a) | ||
{ | { | ||
Line 197: | Line 300: | ||
MsgBox ( | MsgBox ( | ||
" | " | ||
- | " | + | " |
) | ) | ||
} | } | ||
</ | </ | ||
+ | ==== 4. Global Variables ==== | ||
+ | |||
+ | Although traditional MCode compilers like MCode4GCC did not have any tools for constructing and managing global variables inside your MCode, MCL supports this readily. | ||
+ | |||
+ | Like exporting functions when working with multiple functions, you can export global variables with the '' | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #Include <MCL> | ||
+ | |||
+ | lib := MCL.FromC(" | ||
+ | ( | ||
+ | #include < | ||
+ | |||
+ | MCL_EXPORT_GLOBAL(myGlobal) | ||
+ | int myGlobal = 1; | ||
+ | |||
+ | MCL_EXPORT(alpha) | ||
+ | int alpha() { return myGlobal; } | ||
+ | )") | ||
+ | |||
+ | MsgBox DllCall(lib.alpha, | ||
+ | NumPut(" | ||
+ | MsgBox DllCall(lib.alpha, | ||
+ | </ | ||
+ | |||
+ | Like with exporting functions, exporting a global can also export with typing information to form a wrapper. | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #Include <MCL> | ||
+ | |||
+ | lib := MCL.FromC(" | ||
+ | ( | ||
+ | #include < | ||
+ | |||
+ | MCL_EXPORT_GLOBAL(myGlobal, | ||
+ | int myGlobal = 1; | ||
+ | |||
+ | MCL_EXPORT(alpha, | ||
+ | int alpha() { return myGlobal; } | ||
+ | )") | ||
+ | |||
+ | MsgBox lib.alpha() | ||
+ | lib.myGlobal := 42 | ||
+ | MsgBox lib.alpha() | ||
+ | </ | ||
+ | |||
+ | ==== 5. Callback to AHK ==== | ||
+ | |||
+ | Although there are many things you can do with MCode, sometimes it is helpful or even necessary to callback to AutoHotkey for some actions. For example, it can be extremely valuable to call back to AutoHotkey for OutputDebug, | ||
+ | |||
+ | AutoHotkey provides a tool to export its own functions to be consumed as a callback from C/%%C++%% APIs, [[https:// | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #Include <MCL> | ||
+ | |||
+ | lib := MCL.FromC(" | ||
+ | ( | ||
+ | void alpha(void (*MsgBoxInt)(int), | ||
+ | MsgBoxInt(123); | ||
+ | MsgBoxStr(L" | ||
+ | MsgBoxInt(456); | ||
+ | } | ||
+ | )") | ||
+ | |||
+ | pMsgBoxInt := CallbackCreate((i) => MsgBox(i), " | ||
+ | pMsgBoxStr := CallbackCreate((p) => MsgBox(StrGet(p)), | ||
+ | DllCall( | ||
+ | lib, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ) | ||
+ | </ | ||
+ | |||
+ | When working with MCL, you can do a little better by passing the functions as global variables instead of parameters. This lets you more easily add and remove debug functions to be used all throughout the MCode without having to update all your function calls each time. | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #Include <MCL> | ||
+ | |||
+ | lib := MCL.FromC(" | ||
+ | ( | ||
+ | #include < | ||
+ | |||
+ | MCL_EXPORT_GLOBAL(MsgBoxInt, | ||
+ | void (*MsgBoxInt)(int); | ||
+ | |||
+ | MCL_EXPORT_GLOBAL(MsgBoxStr, | ||
+ | void (*MsgBoxStr)(short*); | ||
+ | |||
+ | MCL_EXPORT(alpha, | ||
+ | void alpha() { | ||
+ | MsgBoxInt(123); | ||
+ | MsgBoxStr(L" | ||
+ | MsgBoxInt(456); | ||
+ | } | ||
+ | )") | ||
+ | |||
+ | lib.MsgBoxInt := CallbackCreate((i) => MsgBox(i), " | ||
+ | lib.MsgBoxStr := CallbackCreate((p) => MsgBox(StrGet(p)), | ||
+ | lib.alpha() | ||
+ | </ | ||
+ | |||
+ | It may be tempting to try to use CallbackCreate in order to wrap mathematical functions like Sqrt, however this should be avoided normally because it will absolutely destroy any performance gains that you were aiming to achieve by using MCode in the first place. Instead, native machine code implementations of sqrt and other mathematical functions should be embedded or imported. | ||
+ | |||
+ | ==== 6. Importing Functions ==== | ||
+ | |||
+ | Writing C or %%C++%% code in an MCode environment, | ||
+ | |||
+ | The basic strategy is this: | ||
+ | |||
+ | - Load the DLL using AutoHotkey | ||
+ | - Fetch the function pointer using AutoHotkey | ||
+ | - Pass that function pointer using the same strategy we did for CallbackCreate | ||
+ | - Call that function from your MCode | ||
+ | |||
+ | For example, we can import '' | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | |||
+ | if !(hDll := DllCall(" | ||
+ | throw OSError(,, " | ||
+ | if !(pFunction := DllCall(" | ||
+ | throw Error(,, " | ||
+ | |||
+ | ;double hypotenuse(double (*sqrt)(double), | ||
+ | ; return sqrt(a * a + b * b); | ||
+ | ;} | ||
+ | lib := MCode(" | ||
+ | |||
+ | MsgBox DllCall(lib, | ||
+ | |||
+ | MCode(mcode) { | ||
+ | static e := Map(' | ||
+ | if (!regexmatch(mcode, | ||
+ | return | ||
+ | if (!DllCall(" | ||
+ | return | ||
+ | p := DllCall(" | ||
+ | if (c=" | ||
+ | DllCall(" | ||
+ | if (DllCall(" | ||
+ | return p | ||
+ | DllCall(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | When working with MCL, importing functions like this can be handled automatically by the '' | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #include <MCL> | ||
+ | |||
+ | lib := MCL.FromC(" | ||
+ | ( | ||
+ | #include < | ||
+ | MCL_IMPORT(double, | ||
+ | |||
+ | double hypotenuse(double a, double b) { | ||
+ | return sqrt(a * a + b * b); | ||
+ | } | ||
+ | )") | ||
+ | |||
+ | MsgBox DllCall(lib, | ||
+ | </ | ||
+ | |||
+ | MCL can also be used to import functions from third-party DLLs, such as lua54.dll. For more information about that, please refer to the [[libraries: | ||
===== Compatibility Notes ===== | ===== Compatibility Notes ===== | ||
- | MCode libraries generally support generating both 32 and 64 bit machine code, but they may not generate both at once. In MCL, for example, the default is to generate machine code that is the same as the edition of AutoHotkey being used. Running MCL with AutoHotkey U32 will generate 32 bit machine code, and AutoHotkey U64 will generate 64 bit machine code. | + | MCode libraries generally support generating both 32 and 64 bit machine code, but they may not generate both at once. |
- | Although it is generally recommended to use AutoHotkey U64, when 32 bit is required there are some things to keep in mind. C functions in 32 bit machine code default to the "CDecl" calling convention, which will cause memory leaks if not accounted for. To account for that, you must either adjust any '' | + | Although it is generally recommended to use AutoHotkey U64, when 32 bit is required there are some things to keep in mind. C functions in 32 bit machine code default to the "Cdecl" calling convention, which will cause memory leaks if not accounted for. To account for that, you must either adjust any '' |
<code c> | <code c> | ||
Line 212: | Line 486: | ||
</ | </ | ||
- | Because MCL writes | + | When exporting functions using the '' |