|
|
(16 intermediate revisions by 3 users not shown) |
Line 1: |
Line 1: |
| {{warning|If you came here because you have been told to install an extension, be warned! Extensions are just like executables. They may contain viruses etc. More on that, in the [[#FAQ]] section}}
| |
|
| |
|
|
| |
| __TOC__
| |
| =Historical=
| |
| This functionality arrived in an ArmA 2 post-1.60 beta, making 1.61 the first stable version to support it. Linux (server) support was not available until [[:Category:Introduced_with_Arma_3_version_1.08|Arma 3 v1.18]].
| |
|
| |
| =FAQ=
| |
| ====How to use Extensions?====
| |
| Extensions can be accessed using [[callExtension]].
| |
|
| |
| ''The exact way about how to use an extension is up to the developer of said extension''
| |
|
| |
| ====Where to put Extensions?====
| |
| Arma will look at the following places to find your extension files:
| |
| * In all loaded mods root folders (for example: ''..\arma3\@ModXYZ\yourextension.dll'').
| |
| * In your arma installation folder (for example: ''..\arma3\yourextension.dll'').
| |
|
| |
| At best, you place your extensions inside some mod folder and load it.
| |
| At worst, you just throw it where the arma executable is located.
| |
|
| |
| ''Extensions may require other libraries and runtimes to be installed on the computer it is to run on.''
| |
| Consult the authors installation manual if it exists.
| |
|
| |
| ====What are Extensions?====
| |
| Extensions are ''Dynamic Link Library'' (DLL) files written in, for example, C.
| |
|
| |
| Linux systems use a different file type: ''Shared Object'' (SO).
| |
|
| |
| ====What do i have to look out for?====
| |
| Extensions fall under the same rules as executable files.
| |
| They may require additional runtime libraries or cause the game to crash.
| |
|
| |
| Worst case: They can contain malicious code.
| |
|
| |
| ====How to know which extensions can be trusted?====
| |
| Theoretically, never trust anything! Including Arma and your OS itself.
| |
| Technically however, this would make a boring experience, as all you would be left with is an expensive heater.
| |
|
| |
| Thus a better rule is: If the are sources freely available, you probably can trust the extension.
| |
| Even if you cannot read the source code, chances are that somebody out there can and would find something odd.
| |
| That will not protect you!
| |
| Developers might add malicious code that is not exposed in the available source files, they not even need to be aware of this.
| |
| They may be infected already and the virus is just writing itself into the compiled extension.
| |
|
| |
| ====When i call the extension method, Arma freezes.====
| |
| This is most likely caused by the extension taking too much time.
| |
| Due to the low-level nature of extensions, an extension cannot be suspended like [[Scheduler|scheduled]] scripts can be.
| |
| You should notify the developer of said extension about this freezing issue or use the extension less extensively.
| |
|
| |
| ====Can i join BattleEye protected servers with my extension?====
| |
| If the extension is whitelisted by BattleEye, it will be allowed to load.
| |
| If it is not, it just will be blocked causing any [[callExtension]] attempt to fail.
| |
|
| |
| Generally speaking, joining BattleEye protected servers should be no problem with or without extensions.
| |
|
| |
| If in doubt, ask the extensions author.
| |
|
| |
| ====Can i use extensions in my mission?====
| |
| Yes, you can use extensions in your missions, but you should consider if it is worth it.
| |
| Using extensions requires that all hosts (servers and clients) which use the extension have it installed.
| |
| Important to know here: If only the server is using the extension, the clients do not need it.
| |
|
| |
| Extensions are not packed into the mission pbo.
| |
|
| |
| =Creating Extensions=
| |
| ==Preamble==
| |
| Before we start, ask yourself the following questions:
| |
| * Can, what you're trying to achieve, not be done in pure sqf?
| |
| * Making an extension requires knowledge of a programming language. Are you capable of performing that programming task?
| |
| * Why should a user trust you and use your extension?
| |
| * Is it worth adding an extension for the feature you want to implement?
| |
|
| |
| If the answer to any question was no, you probably should not proceed to create an extension but rather use plain SQF.
| |
|
| |
| ==Getting Started==
| |
| At first, we have to choose what language we want to use.
| |
|
| |
| <!-- If you plan on adding another language, make sure you can provide code snippets for ALL examples required! -->
| |
| This wiki currently covers the following: '''C''', '''C++''', '''C#'''
| |
|
| |
| ===Preparations===
| |
| ====C/C++====
| |
| Create a new library project in the IDE of your choice.
| |
|
| |
| ====C#====
| |
| C# requires you to install additional dependencies to work out of the box.
| |
| The snippets in here all are using [https://www.nuget.org/packages/UnmanagedExports DllExport] ([https://github.com/3F/DllExport sources]) which can be installed using NuGet.
| |
|
| |
| Create a new Class Library project in the IDE of your choice.
| |
|
| |
| ==Available Interfaces==
| |
| The Extension is required to contain at least the entry point in a form of a method named RVExtension or RVExtensionArgs.
| |
| Everything else is optional.
| |
|
| |
| The actual exports need to look like this (Windows only):
| |
|
| |
| '''32-bit'''
| |
| * {{Inline code|_RVExtension@12}}
| |
| * {{Inline code|_RVExtensionArgs@20}}
| |
| * {{Inline code|_RVExtensionVersion@8}}
| |
| '''64-bit'''
| |
| * {{Inline code|RVExtension}}
| |
| * {{Inline code|RVExtensionArgs}}
| |
| * {{Inline code|RVExtensionVersion}}
| |
|
| |
| If you use the Visual C++ compiler, this should work out of the box. But other compilers might require some manual work.
| |
| ===C/C++===
| |
| ====Visual C++====
| |
| <source lang="c">
| |
| //--- Called by Engine on extension load
| |
| __declspec (dllexport) void __stdcall RVExtensionVersion(char *dest, int num);
| |
| //--- STRING callExtension STRING
| |
| __declspec (dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
| |
| //--- STRING callExtension ARRAY
| |
| __declspec (dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
| |
| </source>
| |
| ====GCC====
| |
| <source lang="c">
| |
| //--- Called by Engine on extension load
| |
| __attribute__((dllexport)) void RVExtensionVersion(char *dest, int num);
| |
| //--- STRING callExtension STRING
| |
| __attribute__((dllexport)) void RVExtension(char *dest, int num, const char *fnc);
| |
| //--- STRING callExtension ARRAY
| |
| __attribute__((dllexport)) int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
| |
| </source>
| |
| ===C#===
| |
| <source lang="c#">
| |
| /// <summary>
| |
| /// Gets called when arma starts up and loads all extension.
| |
| /// It's perfect to load in static objects in a seperate thread so that the extension doesn't needs any seperate initalization
| |
| /// </summary>
| |
| /// <param name="output">The string builder object that contains the result of the function</param>
| |
| /// <param name="outputSize">The maximum size of bytes that can be returned</param>
| |
| #if WIN64
| |
| [DllExport("RVExtensionVersion", CallingConvention = CallingConvention.Winapi)]
| |
| #else
| |
| [DllExport("_RVExtensionVersion@8", CallingConvention = CallingConvention.Winapi)]
| |
| #endif
| |
| public static void RvExtensionVersion(StringBuilder output, int outputSize) { }
| |
|
| |
| /// <summary>
| |
| /// The entry point for the default callExtension command.
| |
| /// </summary>
| |
| /// <param name="output">The string builder object that contains the result of the function</param>
| |
| /// <param name="outputSize">The maximum size of bytes that can be returned</param>
| |
| /// <param name="function">The string argument that is used along with callExtension</param>
| |
| #if WIN64
| |
| [DllExport("RVExtension", CallingConvention = CallingConvention.Winapi)]
| |
| #else
| |
| [DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)]
| |
| #endif
| |
| public static void RvExtension(StringBuilder output, int outputSize,
| |
| [MarshalAs(UnmanagedType.LPStr)] string function) { }
| |
|
| |
| /// <summary>
| |
| /// The entry point for the callExtensionArgs command.
| |
| /// </summary>
| |
| /// <param name="output">The string builder object that contains the result of the function</param>
| |
| /// <param name="outputSize">The maximum size of bytes that can be returned</param>
| |
| /// <param name="function">The string argument that is used along with callExtension</param>
| |
| /// <param name="args">The args passed to callExtension as a string array</param>
| |
| /// <param name="argsCount">The size of the string array args</param>
| |
| /// <returns>The result code</returns>
| |
| #if WIN64
| |
| [DllExport("RVExtensionArgs", CallingConvention = CallingConvention.Winapi)]
| |
| #else
| |
| [DllExport("_RVExtensionArgs@20", CallingConvention = CallingConvention.Winapi)]
| |
| #endif
| |
| public static int RvExtensionArgs(StringBuilder output, int outputSize,
| |
| [MarshalAs(UnmanagedType.LPStr)] string function,
| |
| [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, int argCount) { }
| |
| </source>
| |
|
| |
| ==Minimum Viable Example==
| |
| The following examples will copy the input into the output. They are provided to show some practical example.
| |
| ===C===
| |
| ====Visual C++====
| |
| {|class="wikitable"
| |
| !|myextension.c
| |
| |-
| |
| | <source lang="c">
| |
| __declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
| |
| __declspec(dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
| |
| __declspec(dllexport) void __stdcall RVExtensionVersion(char *dest, int num);
| |
| int strncpy_safe(char *dest, const char *src, int size);
| |
|
| |
| int strncpy_safe(char *dest, const char *src, int size)
| |
| {
| |
| int i;
| |
| size--;
| |
| for (i = 0; i < size && src[i] != '\0'; i++)
| |
| {
| |
| dest[i] = src[i];
| |
| }
| |
| dest[i] = '\0';
| |
| return i;
| |
| }
| |
|
| |
| void RVExtension(char *dest, int num, const char *fnc)
| |
| {
| |
| strncpy_safe(dest, function, num);
| |
| }
| |
|
| |
| int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
| |
| {
| |
| int index = 0;
| |
| int i;
| |
| for (i = 0; i < argc && index < num; i++)
| |
| {
| |
| index += strncpy_safe(dest + index, argv[i], num - 1 - index);
| |
| }
| |
| return 0;
| |
| }
| |
|
| |
| void RVExtensionVersion(char *dest, int num)
| |
| {
| |
| strncpy_safe(dest, "Test-Extension v1.0", num);
| |
| }
| |
| </source>
| |
| |}
| |
| ====GCC====
| |
| {|class="wikitable"
| |
| !|myextension.c
| |
| |-
| |
| | <source lang="c">
| |
| __attribute__((dllexport)) void RVExtension(char *dest, int num, const char *fnc);
| |
| __attribute__((dllexport)) int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
| |
| __attribute__((dllexport)) void RVExtensionVersion(char *dest, int num);
| |
|
| |
| int strncpy_safe(char *dest, const char *src, int size)
| |
| {
| |
| int i;
| |
| size--;
| |
| for (i = 0; i < size && src[i] != '\0'; i++)
| |
| {
| |
| dest[i] = src[i];
| |
| }
| |
| dest[i] = '\0';
| |
| return i;
| |
| }
| |
|
| |
| void RVExtension(char *dest, int num, const char *fnc)
| |
| {
| |
| strncpy_safe(dest, function, num);
| |
| }
| |
|
| |
| int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
| |
| {
| |
| int index = 0;
| |
| int i;
| |
| for (i = 0; i < argc && index < num; i++)
| |
| {
| |
| index += strncpy_safe(dest + index, argv[i], num - 1 - index);
| |
| }
| |
| return 0;
| |
| }
| |
|
| |
| void RVExtensionVersion(char *dest, int num)
| |
| {
| |
| strncpy_safe(dest, "Test-Extension v1.0", num);
| |
| }
| |
| </source>
| |
| |}
| |
| ===C++===
| |
| ====Visual C++====
| |
| {|class="wikitable"
| |
| !|myextension.cpp
| |
| |-
| |
| | <source lang="c++">
| |
| #include <string>
| |
| #include <sstream>
| |
| extern "C"
| |
| {
| |
| __declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
| |
| __declspec(dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
| |
| __declspec(dllexport) void __stdcall RVExtensionVersion(char *dest, int num);
| |
| }
| |
| void RVExtension(char *dest, int num, const char *fnc)
| |
| {
| |
| strncpy(dest, fnc, num);
| |
| }
| |
|
| |
| int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
| |
| {
| |
| std::stringstream sstream;
| |
| for (i = 0; i < argc; i++)
| |
| {
| |
| sstream << argv[i];
| |
| }
| |
| strncpy(dest, sstream.str(), num - 1);
| |
| dest[num - 1] = '\0';
| |
| return 0;
| |
| }
| |
|
| |
| void RVExtensionVersion(char *dest, int num)
| |
| {
| |
| strncpy(dest, "Test-Extension v1.0", num - 1);
| |
| dest[num - 1] = '\0';
| |
| }
| |
| </source>
| |
| |}
| |
| ====GCC====
| |
| {|class="wikitable"
| |
| !|myextension.cpp
| |
| |-
| |
| | <source lang="c++">
| |
| #include <string>
| |
| #include <sstream>
| |
| extern "C"
| |
| {
| |
| __attribute__((dllexport)) void RVExtension(char *dest, int num, const char *fnc);
| |
| __attribute__((dllexport)) int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
| |
| __attribute__((dllexport)) void RVExtensionVersion(char *dest, int num);
| |
| }
| |
| void RVExtension(char *dest, int num, const char *fnc)
| |
| {
| |
| strncpy(dest, fnc, num);
| |
| }
| |
|
| |
| int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
| |
| {
| |
| std::stringstream sstream;
| |
| for (i = 0; i < argc; i++)
| |
| {
| |
| sstream << argv[i];
| |
| }
| |
| strncpy(dest, sstream.str(), num - 1);
| |
| dest[num - 1] = '\0';
| |
| return 0;
| |
| }
| |
|
| |
| void RVExtensionVersion(char *dest, int num)
| |
| {
| |
| strncpy(dest, "Test-Extension v1.0", num - 1);
| |
| dest[num - 1] = '\0';
| |
| }
| |
| </source>
| |
| |}
| |
| ===C#===
| |
| {|class="wikitable"
| |
| !|MyExtension.cs
| |
| |-
| |
| | <source lang="c#">
| |
| using System.Runtime.InteropServices;
| |
|
| |
| class MyExtension {
| |
|
| |
| #if WIN64
| |
| [DllExport("RVExtensionVersion", CallingConvention = CallingConvention.Winapi)]
| |
| #else
| |
| [DllExport("_RVExtensionVersion@8", CallingConvention = CallingConvention.Winapi)]
| |
| #endif
| |
| public static void RvExtensionVersion(StringBuilder output, int outputSize)
| |
| {
| |
| output.Append("Test-Extension v1.0");
| |
| }
| |
|
| |
| #if WIN64
| |
| [DllExport("RVExtension", CallingConvention = CallingConvention.Winapi)]
| |
| #else
| |
| [DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)]
| |
| #endif
| |
| public static void RvExtension(StringBuilder output, int outputSize,
| |
| [MarshalAs(UnmanagedType.LPStr)] string function)
| |
| {
| |
| output.Append(function);
| |
| }
| |
|
| |
| #if WIN64
| |
| [DllExport("RVExtensionArgs", CallingConvention = CallingConvention.Winapi)]
| |
| #else
| |
| [DllExport("_RVExtensionArgs@20", CallingConvention = CallingConvention.Winapi)]
| |
| #endif
| |
| public static int RvExtensionArgs(StringBuilder output, int outputSize,
| |
| [MarshalAs(UnmanagedType.LPStr)] string function,
| |
| [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, int argCount)
| |
| {
| |
| foreach(var arg in args)
| |
| {
| |
| output.Append(arg);
| |
| }
| |
| return 0;
| |
| }
| |
| }
| |
| </source>
| |
| |}
| |
| ==Other Languages==
| |
| This section contains other languages which may or may not work. Finding out requirements etc. is up to you.
| |
| {{warning|Help Wanted}}
| |
| If you know any of the following languages, consider editing this page to add the language to the upper list properly.
| |
| ===Delphi/Pascal===
| |
| {|class="wikitable"
| |
| !|<unknown>
| |
| |-
| |
| | <source lang="delphi">
| |
| library dllTest;
| |
|
| |
| uses
| |
| SysUtils;
| |
|
| |
| {Return value is not used.}
| |
| procedure RVExtension(toArma: PAnsiChar; outputSize: Integer; fromArma: PAnsiChar); stdcall; export;
| |
| begin
| |
| StrCopy(toArma, fromArmA);
| |
| end;
| |
|
| |
| exports
| |
| { 32-bit }
| |
| RVExtension name '_RVExtension@12';
| |
| { 64-bit }
| |
| RVExtension name 'RVExtension';
| |
| begin
| |
| end.
| |
| </source>
| |
| |}
| |
| ===D===
| |
| {|class="wikitable"
| |
| !|<unknown>
| |
| |-
| |
| | <source lang="d">
| |
| import std.c.windows.windows;
| |
| import core.sys.windows.dll;
| |
|
| |
| __gshared HINSTANCE g_hInst;
| |
|
| |
| extern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {
| |
| final switch (ulReason) {
| |
| case DLL_PROCESS_ATTACH:
| |
| g_hInst = hInstance;
| |
| dll_process_attach(hInstance, true);
| |
| break;
| |
|
| |
| case DLL_PROCESS_DETACH:
| |
| dll_process_detach(hInstance, true);
| |
| break;
| |
|
| |
| case DLL_THREAD_ATTACH:
| |
| dll_thread_attach(true, true);
| |
| break;
| |
|
| |
| case DLL_THREAD_DETACH:
| |
| dll_thread_detach(true, true);
| |
| break;
| |
| }
| |
|
| |
| return true;
| |
| }
| |
|
| |
| import std.conv;
| |
| import std.exception;
| |
|
| |
| export extern (Windows) void RVExtension(char* output, int output_size, const char* cinput) {
| |
| auto dinput = to!string(cinput);
| |
| auto doutput = output[0 .. output_size];
| |
| string result;
| |
|
| |
| // ...
| |
|
| |
| enforce(result.length <= output_size, "Output length too long");
| |
| doutput[0 .. result.length] = result[];
| |
| doutput[result.length] = '\0';
| |
| }
| |
| </source>
| |
| |}
| |
|
| |
| =External References=
| |
| ====C/C++====
| |
| * [http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-1/ ArmA Scripting Tutorials: How To Make ArmA Extension (Part 1)]
| |
| * [http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-2/ ArmA Scripting Tutorials: How To Make ArmA Extension (Part 2)]
| |
| * [http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-3/ ArmA Scripting Tutorials: How To Make ArmA Extension (Part 3)]
| |
| * [http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-4/ ArmA Scripting Tutorials: How To Make ArmA Extension (Part 4)]
| |
| ====C#====
| |
| * [http://maca134.co.uk/tutorial/write-an-arma-extension-in-c-sharp-dot-net/ Write An ARMA Extension In C#/.NET]
| |