Difference between revisions of "Extensions"

From Bohemia Interactive Community
Jump to navigation Jump to search
(Created new page about extensions.)
 
 
(102 intermediate revisions by 21 users not shown)
Line 1: Line 1:
This topic is about the dlls addons may supply, that a mission or addon may use with the [[callExtension]] script command. This command is available from an arma2 post-1.60 beta. Thus the first stable version that will support it is arma2 1.61
+
{{TOC|side}}
 +
{{Feature|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 {{HashLink|#FAQ}} section.}}
  
(Reportedly this will be available in TakeOnH and Arma3 as well)
 
  
==For end users==
+
= Historical =
DLLs are natively executed code. It has access to the computer as the user account arma is running as. Anything you can do to the computer (without invoking a UAC prompt, the yellow/blue shield) the dll can do.
 
  
Prior to extensions, an addon maker was quite limited in what harm he could cause to the system. About the worst is bad effects to gameplay whenever the addon is loaded - stop loading it and all will again be fine. With extensions, this is no longer quite the case.
+
* {{GVI|arma2|1.61}} The first stable version of this functionality arrived.
 +
* {{GVI|arma3|1.18}} Linux (server) support was introduced.
 +
* {{GVI|arma3|1.68}} The ability to pass multiple arguments to [[callExtension]] and the interface {{hl|RVExtensionArgs}} got added.
 +
* {{GVI|arma3|1.70}} {{hl|RVExtensionVersion}} became available. It is called when the extension loads. Will be printed into the RPT log.
  
=== Defending against malicious extensions ===
 
  
Establish that the extension writer has reputation on the line. If it's somebody nobody ever heard of, who could just disappear again without a lot of people noticing, then the risk is higher. Even if he were to be exposed, it would happen at no cost to him.
+
= FAQ =
  
Make sure the sources are provided. Even if you can't read them, someone else can, and there is a reasonable chance someone will find any backdoors. This increases the risk of getting the crimes exposed; see above.
+
==== How to use Extensions? ====
 +
Extensions can be accessed using [[callExtension]].
  
Whoever compiles the sources into a dll can add whatever they want, last minute, in a separate copy from the sources added. You'll be trusting whoever compiles them for you, trustworthy or not. (They can also be tampered with after the dll has been built, but this is harder. Forensics should be able to tell the difference.)
+
''The exact way about how to use an extension is up to the developer of said extension''
  
 +
==== Where to put Extensions? ====
 +
{{arma3}} 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 {{arma3}} installation folder (for example: ''..\arma3\yourextension.dll'').
  
The easy way out is to simply trust the people involved, and take the fall if/when you do get hacked from it. Problem is, you might not know.
+
At best, you place your extensions inside some mod folder and load it.
 +
At worst, you just throw it where the {{arma3}} executable is located.
  
=== Defending against vulnerable extensions ===
+
''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.
  
Another concern is when the extension is vulnerable. Security vulnerabilities have plagued a lot of software. An extension may well have buffer overflows and other problems which an attacker can exploit to gain access to your system.
+
==== What are Extensions? ====
 +
Extensions are ''Dynamic Link Library'' (DLL) files written in, for example, C.
  
If the sources are available, it's possible to see if the programmer has employed coding practices which decrease the risk of buffer overflows and similar basic bugs. This provides no guarantee, but can inspire confidence. By default, we should assume that such practices are not used: most tutorials, university courses, etc teach the new programmer to do it in the worst possible way. (I'll spare you the rant.)
+
Linux systems use a different file type: ''Shared Object'' (SO).
  
==For server admins==
+
==== 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.
 +
{{Feature|warning|Extensions can contain malicious code!}}
  
The above concerns wrt the end user is also the case for servers. For any server doing a "persistent world" (*life rpgs come to mind), the above will be even more important.
 
  
Your choice will only get more important when your choice of addons will affect what your users need to install. If you require your users to install an addon containing a malicious or vulnerable extension, you're putting your users at risk.
+
==== 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.
  
At the time of writing, linux servers do not yet support it - beyond not producing an error for code that uses it.
+
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.
  
==For mission makers==
+
==== 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.
  
A few notes in no particular order
+
==== Can I join BattlEye protected servers with my extension? ====
 +
If the extension is whitelisted by BattlEye, it will be allowed to load.
 +
If it is not, it just will be blocked causing any [[callExtension]] attempt to fail.
  
* Depending on what that feature is, it may be desirable to make it optional - if the extension is not available, the mission would still work, but without that feature.
+
Generally speaking, joining BattlEye protected servers should be no problem with or without extensions.
* Given that linux servers do not -yet- support this, means these servers will act as if the server does not have the extension installed.
 
* Using an extension adds a dependency on that extension. At the outset this is no different from a dependency on an addon, except this dependency is never automatically checked unless you're using objects from that addon as well.
 
* A user may have an addon downloaded, but not have the extension enabled even though it's supposed to come as part of that addon.
 
  
=== Scripting ===
+
If in doubt, ask the extensions author.
See [[callExtension]].
 
  
==For addon makers==
+
==== Can I use extensions in my mission? ====
TODO. Suggestions?
+
Yes, you can use extensions in your missions, but you should consider if it is worth it.
How to make? (List of relevant biki pages?)
+
Using extensions requires that all hosts (servers and clients) which use the extension have it installed. Extensions are not packed into the mission pbo.
 +
{{Feature|informative|If only the server is using the extension, the clients do not need it.}}
 +
 
 +
==== What do i have to look out for when deploying my extension? ====
 +
Extensions have to be provided for 32bit and 64bit Arma.
 +
When you deploy your extension, you thus have to build it twice.
 +
Once for the 32bit support and once for the 64bit support.
 +
 
 +
After you are done, copy both output files to some location and add a {{hl|_x64.dll}} suffix to the 64bit file.
 +
 
 +
Example:
 +
* Windows:
 +
** {{hl|MyExtension.dll}} (32bit)
 +
** {{hl|MyExtension_x64.dll}} (64bit)
 +
* Linux:
 +
** {{hl|MyExtension.so}} (32bit)
 +
** {{hl|MyExtension_x64.so}} (64bit)
 +
 
 +
 
 +
= Creating Extensions =
 +
 
 +
 
 +
== Preamble ==
 +
 
 +
Before we start, ask yourself the following questions:
 +
* is SQF unable to fulfill your needs?
 +
* 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 requires usage of [https://docs.microsoft.com/en-us/cpp/build/reference/decorated-names?view=vs-2019 decorated names] following the __stdcall convention'''
 +
* {{hl|_RVExtension@12}}
 +
* {{hl|_RVExtensionArgs@20}}
 +
* {{hl|_RVExtensionVersion@8}}
 +
'''64-bit'''
 +
* {{hl|RVExtension}}
 +
* {{hl|RVExtensionArgs}}
 +
* {{hl|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 *output, int outputSize);
 +
//--- STRING callExtension STRING
 +
__declspec (dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function);
 +
//--- STRING callExtension ARRAY
 +
__declspec (dllexport) int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
 +
//--- Extension Callback
 +
__declspec (dllexport) void __stdcall RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));
 +
//--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.11
 +
__declspec (dllexport) void __stdcall RVExtensionContext(const char **argv, int argc);
 +
</source>
 +
 
 +
==== GCC ====
 +
<source lang="c">
 +
//--- Called by Engine on extension load
 +
__attribute__((dllexport)) void RVExtensionVersion(char *output, int outputSize);
 +
//--- STRING callExtension STRING
 +
__attribute__((dllexport)) void RVExtension(char *output, int outputSize, const char *function);
 +
//--- STRING callExtension ARRAY
 +
__attribute__((dllexport)) int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
 +
//--- Extension Callback
 +
__attribute__((dllexport)) void RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));
 +
//--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.11
 +
__attribute__((dllexport)) void RVExtensionContext(const char **argv, int argc);
 +
</source>
 +
 
 +
See GCC documentation on [https://gcc.gnu.org/wiki/Visibility "How to use the new C++ visibility support"]
 +
 
 +
=== C# ===
 +
 
 +
{{Feature|Informative|You might need to install [https://www.nuget.org/packages/UnmanagedExports UnmanagedExports]. You might also need to additionally set {{hl|WIN64}} and {{hl|WIN32}} in project properties. Go to: {{hl|Project->Properties->Build tab->Conditional compilation symbols}}, add {{hl|WIN32}} for your {{hl|x86}} platform and  {{hl|WIN64}} for your {{hl|x64}} platform. Save. Rename resulting {{hl|WIN64}} {{hl|dllname.dll}} to {{hl|dllname_x64.dll}} }}
 +
<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>
 +
 
 +
 
 +
== Getting your extension whitelisted by BattlEye ==
 +
 
 +
Since{{GVI|arma3|1.46}}, client extensions need to be whitelisted in order to get loaded.
 +
 
 +
The {{ExternalLink|link= https://dev.arma3.com/post/sitrep-00109|text= SitRep #00109}} said the following on that topic:
 +
  The enhanced BattlEye protection in 1.46 will require extensions to be '''white-listed''', or their access to the game's processes will be blocked.
 +
  In order to get your extension white-listed, please contact BattlEye support via [http://www.battleye.com/contact/ its web form] or support[at]battleye.com.
 +
  Be sure to include the file name, a description and possibly a download link.
 +
 
 +
This boils down to the following tasks to get your extension whitelisted:
 +
# Upload the extension in question somewhere.
 +
# Open the [http://www.battleye.com/contact/ BattlEye Contact Page].
 +
# Pick the topic {{hl|Other requests}} (may change in future, select the most appropriate).
 +
# Fill in your Name and E-Mail.
 +
# Set the subject to something like {{hl|Arma extension whitelisting}}.
 +
# Add link to your extension from step 1 (you may include other information too) into the Message.
 +
 
 +
After a certain time, your extension should be whitelisted.
 +
 
 +
 
 +
== Deploying extensions ==
 +
 
 +
To correctly deploy your extensions, you have to make sure to have a 32bit and a 64bit variant of your extension.
 +
The reason for this is because 64bit Arma cannot load 32bit extensions and vice versa.
 +
 
 +
You also should create an addon package (even if you only deploy the actual extension binaries) + add the documentation for your extension.
 +
 
 +
An example might be structured like this:
 +
* {{hl|/@MyExtension/}}
 +
** {{hl|/MyExtension.dll}}
 +
** {{hl|/MyExtension_x64.dll}}
 +
** {{hl|/docs/}}
 +
*** {{hl|/MyExtension_Documentation.txt}}
 +
 
 +
You may also want to provide convenience SQF functions to access your extension.
 +
 
 +
 
 +
== Minimum Viable Example ==
 +
 
 +
The following examples will copy the input into the output. They are provided to show some practical example.
 +
 
 +
{{TabView
 +
|title1= C (Visual C++)
 +
|content1=
 +
<syntaxhighlight lang="c">
 +
__declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function);
 +
__declspec(dllexport) int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
 +
__declspec(dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize);
 +
 
 +
int strncpy_safe(char *output, const char *src, int size)
 +
{
 +
int i;
 +
size--;
 +
for (i = 0; i < size && src[i] != '\0'; i++)
 +
{
 +
output[i] = src[i];
 +
}
 +
output[i] = '\0';
 +
return i;
 +
}
 +
 
 +
void __stdcall RVExtension(char *output, int outputSize, const char *function)
 +
{
 +
strncpy_safe(output, function, outputSize);
 +
}
 +
 
 +
int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
 +
{
 +
int index = 0;
 +
for (int i = 0; i < argc && index < outputSize; i++)
 +
{
 +
index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);
 +
}
 +
return 0;
 +
}
 +
 
 +
void __stdcall RVExtensionVersion(char *output, int outputSize)
 +
{
 +
strncpy_safe(output, "Test-Extension v1.0", outputSize);
 +
}
 +
</syntaxhighlight>
 +
 
 +
|title2= C (GCC)
 +
|content2=
 +
<syntaxhighlight lang="c">
 +
__attribute__((dllexport)) void RVExtension(char *output, int outputSize, const char *function);
 +
__attribute__((dllexport)) int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
 +
__attribute__((dllexport)) void RVExtensionVersion(char *output, int outputSize);
 +
 
 +
int strncpy_safe(char *output, const char *src, int size)
 +
{
 +
int i;
 +
size--;
 +
for (i = 0; i < size && src[i] != '\0'; i++)
 +
{
 +
output[i] = src[i];
 +
}
 +
output[i] = '\0';
 +
return i;
 +
}
 +
 
 +
void RVExtension(char *output, int outputSize, const char *function)
 +
{
 +
strncpy_safe(output, function, outputSize);
 +
}
 +
 
 +
int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
 +
{
 +
int index = 0;
 +
for (int i = 0; i < argc && index < outputSize; i++)
 +
{
 +
index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);
 +
}
 +
return 0;
 +
}
 +
 
 +
void RVExtensionVersion(char *output, int outputSize)
 +
{
 +
strncpy_safe(output, "Test-Extension v1.0", outputSize);
 +
}
 +
</syntaxhighlight>
 +
 
 +
|title3= C++ (Visual C++)
 +
|content3=
 +
<syntaxhighlight lang="cpp">
 +
#include <string>
 +
#include <cstring>
 +
#include <sstream>
 +
extern "C"
 +
{
 +
__declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function);
 +
__declspec(dllexport) int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
 +
__declspec(dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize);
 +
}
 +
void __stdcall RVExtension(char *output, int outputSize, const char *function)
 +
{
 +
std::strncpy(output, function, outputSize - 1);
 +
}
 +
 
 +
int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
 +
{
 +
std::stringstream sstream;
 +
for (int i = 0; i < argc; i++)
 +
{
 +
sstream << argv[i];
 +
}
 +
std::strncpy(output, sstream.str().c_str(), outputSize - 1);
 +
return 0;
 +
}
 +
 
 +
void __stdcall RVExtensionVersion(char *output, int outputSize)
 +
{
 +
std::strncpy(output, "Test-Extension v1.0", outputSize - 1);
 +
}
 +
</syntaxhighlight>
 +
 
 +
|title4= C++ (GCC)
 +
|content4=
 +
<syntaxhighlight lang="c++">
 +
#include <string>
 +
#include <cstring>
 +
#include <sstream>
 +
extern "C"
 +
{
 +
__attribute__((dllexport)) void RVExtension(char *output, int outputSize, const char *function);
 +
__attribute__((dllexport)) int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
 +
__attribute__((dllexport)) void RVExtensionVersion(char *output, int outputSize);
 +
}
 +
void RVExtension(char *output, int outputSize, const char *function)
 +
{
 +
std::strncpy(output, function, outputSize - 1);
 +
}
 +
 
 +
int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
 +
{
 +
std::stringstream sstream;
 +
for (int i = 0; i < argc; i++)
 +
{
 +
sstream << argv[i];
 +
}
 +
std::strncpy(output, sstream.str().c_str(), outputSize - 1);
 +
return 0;
 +
}
 +
 
 +
void RVExtensionVersion(char *output, int outputSize)
 +
{
 +
std::strncpy(output, "Test-Extension v1.0", outputSize - 1);
 +
}
 +
</syntaxhighlight>
 +
 
 +
|title5= C#
 +
|content5=
 +
<syntaxhighlight lang="c#">
 +
using System.Text;
 +
using System.Runtime.InteropServices;
 +
using RGiesecke.DllExport;
 +
 
 +
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;
 +
}
 +
}
 +
</syntaxhighlight>
 +
 
 +
|title6= C# (w/ callback)
 +
|content6=
 +
<syntaxhighlight lang="c#">
 +
using System.Text;
 +
using System.Runtime.InteropServices;
 +
using RGiesecke.DllExport;
 +
 
 +
class MyExtension {
 +
 
 +
public static ExtensionCallback callback;
 +
public delegate int ExtensionCallback([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string function, [MarshalAs(UnmanagedType.LPStr)] string data);
 +
 
 +
#if WIN64
 +
[DllExport("RVExtensionRegisterCallback", CallingConvention = CallingConvention.Winapi)]
 +
#else
 +
[DllExport("_RVExtensionRegisterCallback@4", CallingConvention = CallingConvention.Winapi)]
 +
#endif
 +
public static void RVExtensionRegisterCallback([MarshalAs(UnmanagedType.FunctionPtr)] ExtensionCallback func)
 +
{
 +
callback = func;
 +
}
 +
 
 +
#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;
 +
}
 +
}
 +
</syntaxhighlight>
 +
}}
 +
 
 +
 
 +
== Other Languages ==
 +
 
 +
This section contains other languages which may or may not work. Finding out requirements etc. is up to you.
 +
{{Feature|Informative|Help Wanted!}}
 +
If you know any of the following languages, consider editing this page to add the language to the upper list properly.
 +
 
 +
{{TabView
 +
|title1= Delphi/Pascal
 +
|content1=
 +
<syntaxhighlight 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.
 +
</syntaxhighlight>
 +
 
 +
|title2= D
 +
|content2=
 +
<syntaxhighlight 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';
 +
}
 +
</syntaxhighlight>
 +
}}
 +
 
 +
 
 +
= External References =
 +
<!-- This is supposed to be filled by people who want to provide further examples which do not fit in here due to their complexity etc. -->
 +
<!-- It was requested that such a place would exist for stuff that does not fit in here. -->
 +
The [https://github.com/arma3/RV-Extension-Examples Community-Lead Examples Repository] can be used for further examples which go more into detail.
 +
To get your example on there, just fork the repository and create a Pull-Request afterwards.
 +
 
 +
==== 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]
 +
 
 +
==== Golang ====
 +
* [https://github.com/code34/armago_x64/ Armago An Arma Template Extension In Golang]
 +
 
 +
==== External applications ====
 +
* [http://killzonekid.com/arma-3-extension-tester-callextension-exe-callextension_x64-exe/ KK's Arma 3 Extension Tester]
 +
* [http://killzonekid.com/callextension-v2-0/ KK's Arma 3 Extension Tester v2.0 with callback]
 +
* [https://github.com/SQFvm/vm/releases SQF-VM - Community implementation of SQF]
 +
 
 +
 
 +
{{GameCategory|arma2|Editing}}
 +
{{GameCategory|arma3|Editing}}

Latest revision as of 18:18, 1 August 2022

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.


Historical

  • Logo A2.png1.61 The first stable version of this functionality arrived.
  • Arma 3 logo black.png1.18 Linux (server) support was introduced.
  • Arma 3 logo black.png1.68 The ability to pass multiple arguments to callExtension and the interface RVExtensionArgs got added.
  • Arma 3 logo black.png1.70 RVExtensionVersion became available. It is called when the extension loads. Will be printed into the RPT log.


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 3 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 3 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 3 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.

Extensions 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 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 BattlEye protected servers with my extension?

If the extension is whitelisted by BattlEye, it will be allowed to load. If it is not, it just will be blocked causing any callExtension attempt to fail.

Generally speaking, joining BattlEye 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. Extensions are not packed into the mission pbo.

If only the server is using the extension, the clients do not need it.

What do i have to look out for when deploying my extension?

Extensions have to be provided for 32bit and 64bit Arma. When you deploy your extension, you thus have to build it twice. Once for the 32bit support and once for the 64bit support.

After you are done, copy both output files to some location and add a _x64.dll suffix to the 64bit file.

Example:

  • Windows:
    • MyExtension.dll (32bit)
    • MyExtension_x64.dll (64bit)
  • Linux:
    • MyExtension.so (32bit)
    • MyExtension_x64.so (64bit)


Creating Extensions

Preamble

Before we start, ask yourself the following questions:

  • is SQF unable to fulfill your needs?
  • 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.

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 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 requires usage of decorated names following the __stdcall convention

  • _RVExtension@12
  • _RVExtensionArgs@20
  • _RVExtensionVersion@8

64-bit

  • RVExtension
  • RVExtensionArgs
  • 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++

	//--- Called by Engine on extension load 
	__declspec (dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize);
	//--- STRING callExtension STRING
	__declspec (dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function);
	//--- STRING callExtension ARRAY
	__declspec (dllexport) int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
	//--- Extension Callback
	__declspec (dllexport) void __stdcall RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));
	//--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.11
	__declspec (dllexport) void __stdcall RVExtensionContext(const char **argv, int argc);

GCC

	//--- Called by Engine on extension load 
	__attribute__((dllexport)) void RVExtensionVersion(char *output, int outputSize);
	//--- STRING callExtension STRING
	__attribute__((dllexport)) void RVExtension(char *output, int outputSize, const char *function);
	//--- STRING callExtension ARRAY
	__attribute__((dllexport)) int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
	//--- Extension Callback
	__attribute__((dllexport)) void RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));
	//--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.11
	__attribute__((dllexport)) void RVExtensionContext(const char **argv, int argc);

See GCC documentation on "How to use the new C++ visibility support"

C#

You might need to install UnmanagedExports. You might also need to additionally set WIN64 and WIN32 in project properties. Go to: Project->Properties->Build tab->Conditional compilation symbols, add WIN32 for your x86 platform and WIN64 for your x64 platform. Save. Rename resulting WIN64 dllname.dll to dllname_x64.dll
		/// <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) { }


Getting your extension whitelisted by BattlEye

SinceArma 3 logo black.png1.46, client extensions need to be whitelisted in order to get loaded.

The SitRep #00109 said the following on that topic:

 The enhanced BattlEye protection in 1.46 will require extensions to be white-listed, or their access to the game's processes will be blocked.
 In order to get your extension white-listed, please contact BattlEye support via its web form or support[at]battleye.com.
 Be sure to include the file name, a description and possibly a download link.

This boils down to the following tasks to get your extension whitelisted:

  1. Upload the extension in question somewhere.
  2. Open the BattlEye Contact Page.
  3. Pick the topic Other requests (may change in future, select the most appropriate).
  4. Fill in your Name and E-Mail.
  5. Set the subject to something like Arma extension whitelisting.
  6. Add link to your extension from step 1 (you may include other information too) into the Message.

After a certain time, your extension should be whitelisted.


Deploying extensions

To correctly deploy your extensions, you have to make sure to have a 32bit and a 64bit variant of your extension. The reason for this is because 64bit Arma cannot load 32bit extensions and vice versa.

You also should create an addon package (even if you only deploy the actual extension binaries) + add the documentation for your extension.

An example might be structured like this:

  • /@MyExtension/
    • /MyExtension.dll
    • /MyExtension_x64.dll
    • /docs/
      • /MyExtension_Documentation.txt

You may also want to provide convenience SQF functions to access your extension.


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 (GCC)
C++ (Visual C++)
C++ (GCC)
C#
C# (w/ callback)
__declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function);
__declspec(dllexport) int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
__declspec(dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize);

int strncpy_safe(char *output, const char *src, int size)
{
	int i;
	size--;
	for (i = 0; i < size && src[i] != '\0'; i++)
	{
		output[i] = src[i];
	}
	output[i] = '\0';
	return i;
}

void __stdcall RVExtension(char *output, int outputSize, const char *function)
{
	strncpy_safe(output, function, outputSize);
}

int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
{
	int index = 0;
	for (int i = 0; i < argc && index < outputSize; i++)
	{
		index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);
	}
	return 0;
}

void __stdcall RVExtensionVersion(char *output, int outputSize)
{
	strncpy_safe(output, "Test-Extension v1.0", outputSize);
}
__attribute__((dllexport)) void RVExtension(char *output, int outputSize, const char *function);
__attribute__((dllexport)) int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
__attribute__((dllexport)) void RVExtensionVersion(char *output, int outputSize);

int strncpy_safe(char *output, const char *src, int size)
{
	int i;
	size--;
	for (i = 0; i < size && src[i] != '\0'; i++)
	{
		output[i] = src[i];
	}
	output[i] = '\0';
	return i;
}

void RVExtension(char *output, int outputSize, const char *function)
{
	strncpy_safe(output, function, outputSize);
}

int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
{
	int index = 0;
	for (int i = 0; i < argc && index < outputSize; i++)
	{
		index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);
	}
	return 0;
}

void RVExtensionVersion(char *output, int outputSize)
{
	strncpy_safe(output, "Test-Extension v1.0", outputSize);
}
#include <string>
#include <cstring>
#include <sstream>
extern "C"
{
	__declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function);
	__declspec(dllexport) int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
	__declspec(dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize);
}
void __stdcall RVExtension(char *output, int outputSize, const char *function)
{
	std::strncpy(output, function, outputSize - 1);
}

int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
{
	std::stringstream sstream;
	for (int i = 0; i < argc; i++)
	{
		sstream << argv[i];
	}
	std::strncpy(output, sstream.str().c_str(), outputSize - 1);
	return 0;
}

void __stdcall RVExtensionVersion(char *output, int outputSize)
{
	std::strncpy(output, "Test-Extension v1.0", outputSize - 1);
}
#include <string>
#include <cstring>
#include <sstream>
extern "C"
{
	__attribute__((dllexport)) void RVExtension(char *output, int outputSize, const char *function);
	__attribute__((dllexport)) int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc);
	__attribute__((dllexport)) void RVExtensionVersion(char *output, int outputSize);
}
void RVExtension(char *output, int outputSize, const char *function)
{
	std::strncpy(output, function, outputSize - 1);
}

int RVExtensionArgs(char *output, int outputSize, const char *function, const char **argv, int argc)
{
	std::stringstream sstream;
	for (int i = 0; i < argc; i++)
	{
		sstream << argv[i];
	}
	std::strncpy(output, sstream.str().c_str(), outputSize - 1);
	return 0;
}

void RVExtensionVersion(char *output, int outputSize)
{
	std::strncpy(output, "Test-Extension v1.0", outputSize - 1);
}
using System.Text;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;

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;
		}
}
using System.Text;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;

class MyExtension {

	public static ExtensionCallback callback;
	public delegate int ExtensionCallback([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string function, [MarshalAs(UnmanagedType.LPStr)] string data);

#if WIN64
		[DllExport("RVExtensionRegisterCallback", CallingConvention = CallingConvention.Winapi)]
#else
		[DllExport("_RVExtensionRegisterCallback@4", CallingConvention = CallingConvention.Winapi)]
#endif
		public static void RVExtensionRegisterCallback([MarshalAs(UnmanagedType.FunctionPtr)] ExtensionCallback func)
		{
			callback = func;
		}

#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;
		}
}


Other Languages

This section contains other languages which may or may not work. Finding out requirements etc. is up to you.

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
D
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.
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';
}


External References

The Community-Lead Examples Repository can be used for further examples which go more into detail. To get your example on there, just fork the repository and create a Pull-Request afterwards.

C/C++

C#

Golang

External applications