|
1 | 1 | # UnityNativeTool |
2 | | -Tool that allows manipulating native libraries at runtime in Unity3d |
| 2 | +Tool designed mainly to solve old problem with unloading native plugins without reopening Unity editor, with vary little changes to code base and low overhead. It enables to replace plugin files between game executions or even while playing. |
| 3 | +Doing so involves mocking original native method calls with these made to manually loaded libraries by P/Invokes. Although works in builded game, it's intended to use in edtior. |
| 4 | + |
| 5 | +## Requirements and dependencies |
| 6 | +- Windows or Linux |
| 7 | +- [Harmony](https://github.com/pardeike/Harmony) (already included in release package) |
| 8 | +- Api Compatibility Level >= .NET 4.x |
| 9 | + |
| 10 | +## Limitations |
| 11 | +- All `[DllImport]` properties except for `dllName` and `EntryPoint` are not supported |
| 12 | +- All marshaling attributes such as `[MarshalAs]` or `[In]` are not supported |
| 13 | +- Multithreading is not supported (native calls from any but Unity main thread) |
| 14 | + |
| 15 | +## Preformance |
| 16 | +Tested on old gaming laptop, Windows 10, 2 plugins with 10 functions each. Target function is simple addition of 2 floats and was called 1000000 times. |
| 17 | + |
| 18 | +| Test case | Avarage time | |
| 19 | +| --- |:---:| |
| 20 | +| Without using this tool | ~70ms | |
| 21 | +| Lazy mode | ~135ms | |
| 22 | +| Preload mode | ~105ms | |
| 23 | + |
| 24 | +## Instalation |
| 25 | +1. Download and add unity package from releases. |
| 26 | + |
| 27 | +2. Set _Api Compatibility Level_ to .NET 4.x or above. |
| 28 | + Edit > Project Settings > Player > Other Settings > Api Compatibility Level |
| 29 | + |
| 30 | +3. Set execution order of script `DllManipulator` to be the lowest of all scripts in game (at least of scripts that use native functions), e.g -10000. |
| 31 | + Edit > Project Settings > Script Execution Order |
| 32 | + |
| 33 | +4. One game object in the scene needs to have `DllManipulator` script on it. By default this script has `DontDestroayOnLoad(gameObject)` call, and deletes itself when dupliacate is found. You can simply disable that object if you don't want this tool to run. |
| 34 | + |
| 35 | +## Usage |
| 36 | +To native function to be mocked, both function declaration (extern method with `[DllImport]` attribute) and method which calls that function need to be suitably tagged with use of attributes below, alternatively `DllManipulator` options can be used. |
| 37 | + |
| 38 | +All plugins under controll of this tool will be unloaded once game stops. To unload/reloaded them manually use `DllManipulator` script in editor. |
| 39 | + |
| 40 | +#### __Attributes__ |
| 41 | + * `[MockNativeDeclarations]` - Tags all native functions within type with this attribute. |
| 42 | + * `[MockNativeDeclaration]` - Tags native function with this attribute. |
| 43 | + * `[MockNativeCalls]` - Tags calls to native functions made in all methods within type with this attribute. |
| 44 | + * `[DisableMocking]` - Disables mocking of native function with this attribute. |
| 45 | + |
| 46 | +#### __Options__ |
| 47 | +These options are editable via `DllManipulator` script. |
| 48 | + * __DLL path pattern__ - Path at which mocked plugin files are located. Default is *Assets/Plugins/\__NameOfPlugin[.dll|.so]*. Can be the same as path that Unity uses for plugins. |
| 49 | + * __DLL loading mode__ - Specifies how DLLs and functions will be loaded. |
| 50 | + + _Lazy_ - All DLLs and functions are loaded as they're first called. This allows them to be easily unloaded and loaded within game execution. |
| 51 | + + _Preloaded_ - Slight preformance benefit over _Lazy_ mode. All DLLs and functions are loaded at startup. Calls to unloaded DLLs lead to crash, so mid-execution it's safest to manipulate DLLs if game is paused. |
| 52 | + * __[Linux only] dlopen flags__ - Flags used in dlopen() P/Invoke on Linux systems. Has minor meaning unless library is large. |
| 53 | + * __Mock all native functions__ - If true, all native functions in current assembly will be mocked. |
| 54 | + * __Mock native calls in all types__ - If true, calls of native funcions in all methods in current assembly will be mocked. This however can cause significant preformance issues at startup in big code base. |
| 55 | + |
| 56 | +## Examples |
| 57 | + |
| 58 | +```C# |
| 59 | +using System.Runtime.InteropServices; |
| 60 | +using UnityEngine; |
| 61 | +using DllManipulator; |
| 62 | + |
| 63 | +[MockNativeDeclarations] |
| 64 | +class NativeFunctionsHolder |
| 65 | +{ |
| 66 | + [DllImport("MyNativePlugin")] |
| 67 | + public static extern int BigThought(); |
| 68 | + |
| 69 | + [DllImport("MyNativePlugin", EntryPoint="complex_calc")] |
| 70 | + public static extern System.IntPtr ComplexCalc(float param1, Vector3 param2); |
| 71 | + |
| 72 | + [DisableMocking] |
| 73 | + [DllImport("MyNativePlugin")] |
| 74 | + public static extern void IWontBeMocked(bool ohReally); |
| 75 | +} |
| 76 | + |
| 77 | +class SomeMoreNatives |
| 78 | +{ |
| 79 | + [MockNativeDeclaration] |
| 80 | + [DllImport("MyNativePlugin")] |
| 81 | + public static extern void HereIAm(string where); |
| 82 | + |
| 83 | + [DllImport("MyNativePlugin")] |
| 84 | + public static extern bool WillIBeMocked(); |
| 85 | +} |
| 86 | + |
| 87 | +[MockNativeCalls] |
| 88 | +class FunctionsInvoker : MonoBehaviour |
| 89 | +{ |
| 90 | + void Start() |
| 91 | + { |
| 92 | + int answear = NativeFunctionsHolder.BigThought(); //This call will be mocked |
| 93 | + var calc = NativeFunctionsHolder.ComplexCalc(2, Vector3.up); //So will this |
| 94 | + NativeFunctionsHolder.IWontBeMocked(true); //As it says |
| 95 | + } |
| 96 | + |
| 97 | + void Update() |
| 98 | + { |
| 99 | + SomeMoreNatives.HereIAm("Wherever"); //This mocked too |
| 100 | + bool res = SomeMoreNatives.WillIBeMocked(); //Mocked only if "Mock all native functions" option is true |
| 101 | + MyOwnNative(); //As above |
| 102 | + } |
| 103 | + |
| 104 | + [DllImport("MyNativePlugin")] |
| 105 | + private static extern void MyOwnNative(); |
| 106 | +} |
| 107 | + |
| 108 | +[MockNativeDeclarations] |
| 109 | +[MockNativeCalls] |
| 110 | +class AllInOne : MonoBehaviour |
| 111 | +{ |
| 112 | + void Start() |
| 113 | + { |
| 114 | + MyVeryOwnNative(); //Mocked |
| 115 | + } |
| 116 | + |
| 117 | + [DllImport("MyNativePlugin")] |
| 118 | + private static extern void MyVeryOwnNative(); |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +## Planed features |
| 123 | +- Window editor |
| 124 | +- Native calls inlining |
| 125 | +- Pausing on dll/function load error, allowing to fix depencency without restarting game |
| 126 | +- Thread safety |
| 127 | +- Mac support |
| 128 | +- Possibly break depencency on Harmony |
0 commit comments