|
|
(25 intermediate revisions by 3 users not shown) |
Line 1: |
Line 1: |
| {{warning|If you came here because you have been said 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 [[A2OA:_Patch_v1.61|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 there is some.
| |
|
| |
| ====What are Extensions?====
| |
| Extensions are ''Dynamic Link Library'' (DLL) files written in, for example, C.
| |
|
| |
| Linux systems use a different extension: ''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 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, he does not even needs to be aware of this.
| |
| The developer himself might be infected already and the virus just writes 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 nature of how actual code works, an extension cannot be suspended even if used from a spawn.
| |
| 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.
| |
|
| |
| If in doubt, ask the extensions author.
| |
|
| |
| Generally spoken, joining BattleEye protected servers should be no problem with or without extensions.
| |
|
| |
| ====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, consider the following:
| |
| * Can, what you're trying to achieve, 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:
| |
|
| |
| '''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/C++ compiler, this should work out of the box. But other compilers might require some manual work.
| |
| ===C/C++===
| |
| ====Visual C/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/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 __stdcall RVExtension(char *dest, int num, const char *fnc)
| |
| {
| |
| strncpy_safe(dest, function, num);
| |
| }
| |
|
| |
| int __stdcall 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;
| |
| }
| |
|
| |
| __declspec(dllexport) void __stdcall 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 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;
| |
| }
| |
|
| |
| __declspec(dllexport) void RVExtensionVersion(char *dest, int num)
| |
| {
| |
| strncpy_safe(dest, "Test-Extension v1.0", num);
| |
| }
| |
| </source>
| |
| |}
| |
| ===C++===
| |
| ====Visual C/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 __stdcall RVExtension(char *dest, int num, const char *fnc)
| |
| {
| |
| strncpy_safe(dest, fnc, num);
| |
| }
| |
|
| |
| int __stdcall 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;
| |
| }
| |
|
| |
| __declspec(dllexport) void __stdcall 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_safe(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;
| |
| }
| |
|
| |
| __attribute__((dllexport)) 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>
| |
| |}
| |
|
| |
| =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]
| |