Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
guides:machine_code [2022-02-23 02:09] – created geek | guides:machine_code [2024-01-02 16:18] (current) – Add section 6, Importing functions geek | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Machine Code ====== | ====== Machine Code ====== | ||
- | Machine Code (referred to as " | + | With great appreciation |
- | MCode is especially useful for performance critical operations like physics calculation or image processing, since compiled C code is much faster than the compiled C++ code which is used to interpret regular AHK code. | + | ===== What is Machine Code? ===== |
- | However, due to the unique constraints | + | [[https:// |
- | ===== Libraries ===== | + | In the AutoHotkey community the term " |
- | See the libraries at [[libraries:start# | + | MCode is important for // |
- | ===== Online Tools ===== | + | * Encoding or decoding data more than a few kilobytes in size, in formats like [[libraries: |
+ | * Hashing files or large amounts of text | ||
+ | * Manipulating images like GDI+ bitmaps (especially for custom ImageSearch algorithms) | ||
+ | * Performing real-time calculations, | ||
- | [[https:// | + | MCode is not the only way to achieve these performance goals. It is possible, and sometimes more flexible, to use the normal tooling of those compiled languages to produce a standard machine code DLL that can be used from AutoHotkey. However, a script that comes with a custom DLL is harder to share because it takes multiple files, it can take up more disk space than a normal script, and is more likely to be blocked by antivirus and corporate application filters. |
- | ===== Other Tutorials | + | ===== Requirements |
- | [[https:// | + | Before you can get started with MCode, you will need: |
+ | |||
+ | * Some understanding of how to read and write C or %%C++%% code | ||
+ | * A basic understanding of how to use '' | ||
+ | * An MCode library which can create and load MCode | ||
+ | * A compiler compatible with your chosen MCode library | ||
+ | |||
+ | If you are not very familiar with programming in C or %%C++%%, a great place to start is on [[https:// | ||
+ | |||
+ | There are not very many MCode libraries, but the most comprehensive is [[libraries: | ||
+ | |||
+ | If using MCL, the easiest compiler to install is likely [[https:// | ||
+ | |||
+ | ===== Learning MCode from Scratch ===== | ||
+ | |||
+ | ==== 1. Return a Number ==== | ||
+ | |||
+ | One of the simplest MCode projects you can do is to have a bit of MCode that can return a number. | ||
+ | |||
+ | For example, the C code: | ||
+ | |||
+ | <code c> | ||
+ | int alpha() { return 42; } | ||
+ | </ | ||
+ | |||
+ | With a function this trivial, it is possible to manually convert the C code to MCode using a compiler like [[https:// | ||
+ | |||
+ | {{: | ||
+ | |||
+ | The hex code on every other line of the output can be entered into a script to form MCode. The simplest method is using a '' | ||
+ | |||
+ | 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 and add the '' | ||
+ | |||
+ | Now you can assemble your mcode: | ||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | mcode := Buffer(16) | ||
+ | NumPut(' | ||
+ | |||
+ | ; 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> | ||
+ | int StringLen(short *str) | ||
+ | { | ||
+ | int i = 0; | ||
+ | for (; str[i] != 0; i++) {} | ||
+ | return i; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | |||
+ | code := Buffer(64) | ||
+ | NumPut( | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | code | ||
+ | ) | ||
+ | |||
+ | if !DllCall(" | ||
+ | throw Error(" | ||
+ | |||
+ | MsgBox "The string is " DllCall(code, | ||
+ | </ | ||
+ | |||
+ | As the functions get more complicated, | ||
+ | |||
+ | When provided to joedf' | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | |||
+ | ptr := MCode(' | ||
+ | |||
+ | MsgBox "The string is " DllCall(ptr, | ||
+ | |||
+ | MCode(mcode) { | ||
+ | static e := Map(' | ||
+ | if (!regexmatch(mcode, | ||
+ | return | ||
+ | if (!DllCall(" | ||
+ | return | ||
+ | p := DllCall(" | ||
+ | if (c=" | ||
+ | DllCall(" | ||
+ | if (DllCall(" | ||
+ | return p | ||
+ | DllCall(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Similarly, when provided to the MCL compiler it will output the loader itself, with the 32 and 64 bit MCode already baked in: | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | |||
+ | lib := MCode() | ||
+ | |||
+ | MsgBox "The string is " DllCall(lib, | ||
+ | |||
+ | MCode() { | ||
+ | static lib := false | ||
+ | if lib | ||
+ | return lib | ||
+ | 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(" | ||
+ | if !DllCall(" | ||
+ | throw Error(" | ||
+ | for k, v in exports.OwnProps() | ||
+ | exports.%k% := code.Ptr + v | ||
+ | return lib := { | ||
+ | exports: exports, | ||
+ | code: code, | ||
+ | Ptr: exports.StringLen, | ||
+ | StringLen: | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | With the MCL library on a system which has a compatible compiler installed, you can skip the need to separately compile and then paste a loader into your script to test. Instead, compiling and loading can be done as a single step inside your script: | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #Include < | ||
+ | |||
+ | library := MCL.FromC(" | ||
+ | ( | ||
+ | int StringLen(short *str) | ||
+ | { | ||
+ | int i = 0; | ||
+ | for (; str[i] != 0; i++) {} | ||
+ | return i; | ||
+ | } | ||
+ | )") | ||
+ | |||
+ | MsgBox "The string is " DllCall(library, | ||
+ | </ | ||
+ | |||
+ | It is only once you are done developing your MCode, you would use the '' | ||
+ | |||
+ | |||
+ | ==== 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 MCode tools like MCode4GCC do nothing to address these problems. Instead, you have to compile and load each function separately, managing and passing around function pointers manually to everywhere they are used. | ||
+ | |||
+ | <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 resolve correctly instead of remaining '' | ||
+ | |||
+ | 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> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #Include < | ||
+ | |||
+ | lib := MCL.FromC(" | ||
+ | ( | ||
+ | #include < | ||
+ | |||
+ | MCL_EXPORT(Factorial) | ||
+ | unsigned int Factorial(unsigned int a, unsigned int b) | ||
+ | { | ||
+ | if (a > 0) | ||
+ | return Factorial(a - 1, b * a); | ||
+ | else | ||
+ | return b; | ||
+ | } | ||
+ | |||
+ | MCL_EXPORT(FactorialCaller) | ||
+ | unsigned int FactorialCaller(unsigned int a) | ||
+ | { | ||
+ | return Factorial(a, | ||
+ | } | ||
+ | )") | ||
+ | |||
+ | loop 5 { | ||
+ | MsgBox ( | ||
+ | " | ||
+ | " | ||
+ | ) | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Optionally, the export macro can accept DllCall types as parameters. When these are specified, the exports will be exported as //pre-made wrappers// that can be called without having to pull out DllCall and specify types in your AutoHotkey code. | ||
+ | |||
+ | <runner ahk2> | ||
+ | #Requires AutoHotkey v2.0 | ||
+ | #Include < | ||
+ | |||
+ | lib := MCL.FromC(" | ||
+ | ( | ||
+ | #include < | ||
+ | |||
+ | MCL_EXPORT(Factorial, | ||
+ | unsigned int Factorial(unsigned int a, unsigned int b) | ||
+ | { | ||
+ | if (a > 0) | ||
+ | return Factorial(a - 1, b * a); | ||
+ | else | ||
+ | return b; | ||
+ | } | ||
+ | |||
+ | MCL_EXPORT(FactorialCaller, | ||
+ | unsigned int FactorialCaller(unsigned int a) | ||
+ | { | ||
+ | return Factorial(a, | ||
+ | } | ||
+ | )") | ||
+ | |||
+ | loop 5 { | ||
+ | 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 < | ||
+ | |||
+ | 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 < | ||
+ | |||
+ | 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 < | ||
+ | |||
+ | 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 < | ||
+ | |||
+ | 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 < | ||
+ | |||
+ | 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 ===== | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | <code c> | ||
+ | int _stdcall SomeFunc(int arg1, int arg2) {} | ||
+ | </ | ||
+ | |||
+ | When exporting functions using the '' |