X39 – User talk

From Bohemia Interactive Community
Jump to navigation Jump to search
m (inlining export names)
No edit summary
Line 55: Line 55:
* Can what you're trying to achieve be done in pure sqf?
* 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 doing so?
* Making an extension requires knowledge of a programming language. Are you capable of doing so?
* Why should a user trust you and use your extension. Is your reputation enough?
* Why should a user trust you and use your extension.
* Is it worth adding an extension for the feature you want to implement?
* Is it worth adding an extension for the feature you want to implement?


Line 65: Line 65:
<!-- If you plan on adding another language, make sure you can provide code snippets for ALL examples required! -->
<!-- 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#'''
This wiki currently covers the following: '''C''', '''C++''', '''C#'''
Snippets contain code that may only work on windows.


===Preparations===
===Preparations===
Line 82: Line 80:


The actual exports need to look like this:
The actual exports need to look like this:
'''32-bit'''
'''32-bit'''
* {{Inline code|_RVExtension@12}}
* {{Inline code|_RVExtension@12}}
Line 93: Line 92:
If you use the Visual C/C++ compiler, this should work out of the box. But other compilers might require some manual work.
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++===
===C/C++===
====Visual C/C++====
<source lang="c">
<source lang="c">
//--- Engine called on extension load  
//--- Called by Engine on extension load  
__declspec (dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize);
__declspec (dllexport) void __stdcall RVExtensionVersion(char *dest, int num);
//--- STRING callExtension STRING
//--- STRING callExtension STRING
__declspec (dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function);
__declspec (dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
//--- STRING callExtension ARRAY
//--- STRING callExtension ARRAY
__declspec (dllexport) int __stdcall RVExtensionArgs(char *output, int outputSize, const char *function, const char **args, int argsCnt);
__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>
</source>
===C#===
===C#===
Line 149: Line 158:
The following examples will copy the input into the output. They are provided to show some practical example.
The following examples will copy the input into the output. They are provided to show some practical example.
===C===
===C===
====Visual C/C++====
{|class="wikitable"
{|class="wikitable"
!|myextension.h
!|myextension.c
|-
|-
| <source lang="c">
| <source lang="c">
__declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *function);
__declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
__declspec(dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *function, const char **args, int argCnt);
__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);
__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 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>
</source>
|}
|}
====GCC====
{|class="wikitable"
{|class="wikitable"
!|myextension.c
!|myextension.c
|-
|-
| <source lang="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 strncpy_safe(char *dest, const char *src, int size)
{
{
Line 175: Line 224:
}
}


void __stdcall RVExtension(char *dest, int num, const char *function)
void RVExtension(char *dest, int num, const char *fnc)
{
{
strncpy_safe(dest, function, num);
strncpy_safe(dest, function, num);
}
}


int __stdcall RVExtensionArgs(char *dest, int num, const char *function, const char **args, int argCnt)
int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
{
{
int index = 0;
int index = 0;
Line 186: Line 235:
for (i = 0; i < argc && index < num; i++)
for (i = 0; i < argc && index < num; i++)
{
{
index += strncpy_safe(dest + index, function, num - 1 - index);
index += strncpy_safe(dest + index, argv[i], num - 1 - index);
}
}
return 0;
return 0;
}
}


__declspec(dllexport) void __stdcall RVExtensionVersion(char *dest, int num)
__declspec(dllexport) void RVExtensionVersion(char *dest, int num)
{
{
strncpy_safe(dest, "Test-Extension v1.0", num);
strncpy_safe(dest, "Test-Extension v1.0", num);
Line 198: Line 247:
|}
|}
===C++===
===C++===
====Visual C/C++====
{|class="wikitable"
{|class="wikitable"
!|myextension.h
!|myextension.cpp
|-
|-
| <source lang="c++">
| <source lang="c++">
#include <string>
#include <sstream>
extern "C"
extern "C"
{
{
__declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *function);
__declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
__declspec(dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *function, const char **args, int argCnt);
__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);
__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>
</source>
|}
|}
====GCC====
{|class="wikitable"
{|class="wikitable"
!|myextension.cpp
!|myextension.cpp
Line 216: Line 291:
#include <string>
#include <string>
#include <sstream>
#include <sstream>
void __stdcall RVExtension(char *dest, int num, const char *function)
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, function, num);
strncpy_safe(dest, fnc, num);
}
}


int __stdcall RVExtensionArgs(char *dest, int num, const char *function, const char **args, int argCnt)
int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
{
{
std::stringstream sstream;
std::stringstream sstream;
for (i = 0; i < argc; i++)
for (i = 0; i < argc; i++)
{
{
sstream << args[i];
sstream << argv[i];
}
}
strncpy(dest, sstream.str(), num - 1);
strncpy(dest, sstream.str(), num - 1);
Line 233: Line 314:
}
}


__declspec(dllexport) void __stdcall RVExtensionVersion(char *dest, int num)
__attribute__((dllexport)) void RVExtensionVersion(char *dest, int num)
{
{
strncpy(dest, "Test-Extension v1.0", num - 1);
strncpy(dest, "Test-Extension v1.0", num - 1);
Line 264: Line 345:
         [DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)]
         [DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)]
#endif
#endif
         public static void RvExtension(StringBuilder output, int outputSize, [MarshalAs(UnmanagedType.LPStr)] string function)
         public static void RvExtension(StringBuilder output, int outputSize,
[MarshalAs(UnmanagedType.LPStr)] string function)
{
{
             output.Append(function);
             output.Append(function);
Line 274: Line 356:
         [DllExport("_RVExtensionArgs@20", CallingConvention = CallingConvention.Winapi)]
         [DllExport("_RVExtensionArgs@20", CallingConvention = CallingConvention.Winapi)]
#endif
#endif
         public static int RvExtensionArgs(StringBuilder output, int outputSize, [MarshalAs(UnmanagedType.LPStr)] string function,
         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)
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, int argCount)
{
{

Revision as of 08:45, 7 May 2018

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


FAQ

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.

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. Technically however, this would make a boring experience, as nothing but stock windows/linux assets would be installed.

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! Even someone with good reputation might add malicious code, though he does not always needs to be aware of that fact as the developer himself might be infected already and another virus just writes itself into the 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?

Joining a BattleEye protected server with extensions loaded will work perfectly fine. 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.

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 doing so?
  • 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 expected to contain an entry point in a form of a function named RVExtension or RVExtensionArgs.

The actual exports need to look like this:

32-bit

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

64-bit

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

	//--- 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);

GCC

	//--- 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);

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) { }

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++

myextension.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);
}

GCC

myextension.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);
}

C++

Visual C/C++

myextension.cpp
#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';
}

GCC

myextension.cpp
#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';
}

C#

MyExtension.cs
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;
        }
}

External References

C/C++

C#