Difference between revisions of "Creating Natives (SourceMod Scripting)"
m (→Creating the Native) |
(Fix incorrect type for My_AddNumbers parameter) |
||
(17 intermediate revisions by 7 users not shown) | |||
Line 9: | Line 9: | ||
==Include File== | ==Include File== | ||
For our first example, let's start off with a simple native that adds two numbers, a float and a non-float. It might look like this: | For our first example, let's start off with a simple native that adds two numbers, a float and a non-float. It might look like this: | ||
− | < | + | <sourcepawn>/** Double-include prevention */ |
#if defined _mynatives_included_ | #if defined _mynatives_included_ | ||
#endinput | #endinput | ||
Line 22: | Line 22: | ||
* @return The float value of the integer and float added together. | * @return The float value of the integer and float added together. | ||
*/ | */ | ||
− | native | + | native float My_AddNumbers(int num1, float num2);</sourcepawn> |
The documentation and commenting style above is a SourceMod Development Team convention. While you are not required to follow it for your own code, it may be a good idea to enhance readability (as readers will expect the style to conform). | The documentation and commenting style above is a SourceMod Development Team convention. While you are not required to follow it for your own code, it may be a good idea to enhance readability (as readers will expect the style to conform). | ||
==Creating the Native== | ==Creating the Native== | ||
− | To actually create the native, first let's define the native handler itself. From | + | To actually create the native, first let's define the native handler itself. From the [https://sm.alliedmods.net/new-api/functions/NativeCall API reference] we can see that there are two prototypes. We will use the second one: |
− | < | + | <sourcepawn>function any (Handle plugin, int numParams)</sourcepawn> |
Thus, we have a function like: | Thus, we have a function like: | ||
− | < | + | <sourcepawn>any Native_My_AddNumbers(Handle plugin, int numParams) |
{ | { | ||
− | }</ | + | }</sourcepawn> |
Note that we don't use the same function name as we did in our include file. This is to prevent a possible ''name clash''. Otherwise, two different symbols (one a native, another a function) could have the same name, and that's not allowed. We'll see later how we bind the native to its actual name. | Note that we don't use the same function name as we did in our include file. This is to prevent a possible ''name clash''. Otherwise, two different symbols (one a native, another a function) could have the same name, and that's not allowed. We'll see later how we bind the native to its actual name. | ||
Next, we need to implement our native. | Next, we need to implement our native. | ||
− | < | + | <sourcepawn>any Native_My_AddNumbers(Handle plugin, int numParams) |
{ | { | ||
− | + | /* Retrieve the first parameter we receive */ | |
− | + | int num1 = GetNativeCell(1); | |
− | + | /* Retrieve the second parameter we receive */ | |
− | + | float num2 = view_as<float>(GetNativeCell(2)); | |
− | + | /* Add them together */ | |
− | + | float value = num1 + num2; | |
− | + | /* Return the value */ | |
− | + | return value; | |
− | }</ | + | }</sourcepawn> |
− | You should note | + | You should note three important details. The first is that we have to manually grab the parameters -- they're not easily sent via arguments. This is for technical reasons that go beyond the scope of this document. That means you have to know exactly ''how'' to grab each parameter. For our example, we know that both parameters are simply cells, and thus we can use <tt>GetNativeCell</tt>. |
− | However, the second detail is that there is no <tt>GetNativeCellFloat</tt>. This is because a <tt> | + | However, the second detail is that there is no <tt>GetNativeCellFloat</tt>. This is because a <tt>float</tt> is still a cell; the data is just interpreted differently. Thus, we ''cast'' the type to <tt>float</tt> manually. '''This is different from calling float(GetNativeCell(2)).''' In fact, that would not work; the <tt>float()</tt> call would interpret the cell as an integer, when in fact it's already a float, just with the wrong type (i.e., no type at all). |
− | |||
− | |||
Lastly, we did not need to verify <tt>numParams</tt>. The compiler will always send the correct amount of parameters - the user can't mess this up in any easy way. Only if you use variadic arguments, or add parameters and need to support older binaries, do you need to handle <tt>numParams</tt> variations. | Lastly, we did not need to verify <tt>numParams</tt>. The compiler will always send the correct amount of parameters - the user can't mess this up in any easy way. Only if you use variadic arguments, or add parameters and need to support older binaries, do you need to handle <tt>numParams</tt> variations. | ||
Line 61: | Line 59: | ||
Natives must be registered in <tt>AskPluginLoad2</tt>. Although they can be registered elsewhere, only this function guarantees that all other plugins will see your native. The definition for this forward is in <tt>sourcemod.inc</tt>. | Natives must be registered in <tt>AskPluginLoad2</tt>. Although they can be registered elsewhere, only this function guarantees that all other plugins will see your native. The definition for this forward is in <tt>sourcemod.inc</tt>. | ||
− | < | + | <sourcepawn>public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) |
{ | { | ||
− | + | CreateNative("My_AddNumbers", Native_My_AddNumbers); | |
− | + | return APLRes_Success; | |
− | }</ | + | }</sourcepawn> |
Now, any plugin will be able to use our native as if it were from a Core or a C++ extension. | Now, any plugin will be able to use our native as if it were from a Core or a C++ extension. | ||
Line 72: | Line 70: | ||
==By-Reference== | ==By-Reference== | ||
Sometimes, it may be desirable to return data through by-reference parameters. For example, observe the following native prototype: | Sometimes, it may be desirable to return data through by-reference parameters. For example, observe the following native prototype: | ||
− | < | + | <sourcepawn>/** |
* Closes a Handle, and sets the variable to INVALID_HANDLE by reference. | * Closes a Handle, and sets the variable to INVALID_HANDLE by reference. | ||
* | * | ||
Line 78: | Line 76: | ||
* @return Anything that CloseHandle() returns. | * @return Anything that CloseHandle() returns. | ||
*/ | */ | ||
− | native bool | + | native bool AltCloseHandle(Handle &hndl);</sourcepawn> |
An implementation of this would look like: | An implementation of this would look like: | ||
− | < | + | <sourcepawn>int Native_AltCloseHandle(Handle plugin, int numParams) |
{ | { | ||
− | + | Handle hndl = view_as<Handle>(GetNativeCellRef(1)); | |
− | + | SetNativeCellRef(1, 0); /* Zero out the variable by reference */ | |
− | + | return CloseHandle(hndl); | |
− | }</ | + | }</sourcepawn> |
Now, we have a native that is "safer" than a normal <tt>CloseHandle</tt>, as it ensures we don't hold onto invalid Handles. | Now, we have a native that is "safer" than a normal <tt>CloseHandle</tt>, as it ensures we don't hold onto invalid Handles. | ||
Line 92: | Line 90: | ||
==Strings== | ==Strings== | ||
Strings can be retrieved and altered via dynamic natives. Observe the following native prototype: | Strings can be retrieved and altered via dynamic natives. Observe the following native prototype: | ||
− | < | + | <sourcepawn>/** |
* Converts a string to upper case. | * Converts a string to upper case. | ||
* | * | ||
Line 98: | Line 96: | ||
* @noreturn | * @noreturn | ||
*/ | */ | ||
− | native StringToUpper( | + | native void StringToUpper(char[] str);</sourcepawn> |
An implementation of this is very easy, because of dynamic arrays: | An implementation of this is very easy, because of dynamic arrays: | ||
− | < | + | <sourcepawn>int Native_StringToUpper(Handle plugin, int numParams) |
{ | { | ||
− | + | int len; | |
− | + | GetNativeStringLength(1, len); | |
+ | |||
+ | if (len <= 0) | ||
+ | { | ||
+ | return; | ||
+ | } | ||
− | + | char[] str = new char[len + 1]; | |
− | + | GetNativeString(1, str, len + 1); | |
− | |||
− | |||
− | + | for (int i = 0; i < len; i++) | |
− | + | { | |
+ | /* 5th bit specifies case in ASCII -- | ||
+ | * this is not UTF-8 compatible. | ||
+ | */ | ||
+ | if ((str[i] & (1 << 5)) == (1 << 5)) | ||
+ | { | ||
+ | str[i] &= ~(1 << 5) | ||
+ | } | ||
+ | } | ||
− | + | SetNativeString(1, str, len+1, false); | |
− | + | }</sourcepawn> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | }</ | ||
==Arrays== | ==Arrays== | ||
Working with arrays is very similar to strings. For example, observe the following native prototype: | Working with arrays is very similar to strings. For example, observe the following native prototype: | ||
− | < | + | <sourcepawn>/** |
* Sums an array of numbers and returns the sum. | * Sums an array of numbers and returns the sum. | ||
* | * | ||
Line 137: | Line 135: | ||
* @return Sum of the array elements. | * @return Sum of the array elements. | ||
*/ | */ | ||
− | native SumArray(const | + | native int SumArray(const int[] array, int size);</sourcepawn> |
An implementation of this could look like: | An implementation of this could look like: | ||
− | < | + | <sourcepawn>int Native_SumArray(Handle plugin, int numParams) |
{ | { | ||
− | + | int size = GetNativeCell(2); | |
− | + | if (size < 1) | |
− | + | { | |
− | + | return 0; | |
− | + | } | |
− | + | int[] array = new int[size]; | |
− | + | int total; | |
+ | GetNativeArray(1, array, size); | ||
− | + | for (int i = 0; i < size; i++) | |
− | + | { | |
− | + | total += array[i]; | |
− | + | } | |
− | + | return total; | |
− | }</ | + | }</sourcepawn> |
Similarly, there is a <tt>SetNativeArray</tt>, which is used to alter array contents. | Similarly, there is a <tt>SetNativeArray</tt>, which is used to alter array contents. | ||
Line 164: | Line 163: | ||
As an example, let's convert our function from above to use variable arguments: | As an example, let's convert our function from above to use variable arguments: | ||
− | < | + | <sourcepawn>/** |
* Sums a list of numbers. | * Sums a list of numbers. | ||
* | * | ||
Line 171: | Line 170: | ||
* @return Sum of all numbers passed. | * @return Sum of all numbers passed. | ||
*/ | */ | ||
− | native SumParams(num, ...);</ | + | native int SumParams(int num, ...);</sourcepawn> |
An implementation of this would look like: | An implementation of this would look like: | ||
− | < | + | <sourcepawn>int Native_SumParams(Handle plugin, int numParams) |
{ | { | ||
− | + | int base = GetNativeCell(1); | |
− | + | for (int i = 2; i <= numParams; i++) | |
− | + | { | |
− | + | base += GetNativeCellRef(i); | |
− | + | } | |
− | + | return base; | |
− | }</ | + | }</sourcepawn> |
==Format Functions== | ==Format Functions== | ||
Line 188: | Line 187: | ||
The most common usage of this is to accept a user-formatted string and to not | The most common usage of this is to accept a user-formatted string and to not | ||
− | < | + | <sourcepawn>/** |
* Prints a message with a [DEBUG] prefix to the server. | * Prints a message with a [DEBUG] prefix to the server. | ||
* | * | ||
Line 194: | Line 193: | ||
* @param ... Format arguments. | * @param ... Format arguments. | ||
*/ | */ | ||
− | native DebugPrint(const | + | native void DebugPrint(const char[] fmt, any ...);</sourcepawn> |
This could be easily implemented as: | This could be easily implemented as: | ||
− | < | + | <sourcepawn>native int DebugPrint(Handle plugin, int numParams) |
{ | { | ||
− | + | char buffer[1024]; | |
+ | int written; | ||
+ | |||
+ | FormatNativeString(0, /* Use an output buffer */ | ||
+ | 1, /* Format param */ | ||
+ | 2, /* Format argument #1 */ | ||
+ | sizeof(buffer), /* Size of output buffer */ | ||
+ | written, /* Store # of written bytes */ | ||
+ | buffer /* Use our buffer */ | ||
+ | ); | ||
− | + | PrintToServer("[DEBUG] %s", buffer); | |
− | + | }</sourcepawn> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | }</ | ||
==Throwing Errors== | ==Throwing Errors== | ||
Line 216: | Line 216: | ||
For example, let's say we have a native that operates on clients. We want to throw an error if we get a client index that is invalid or disconnected. We could do this like so: | For example, let's say we have a native that operates on clients. We want to throw an error if we get a client index that is invalid or disconnected. We could do this like so: | ||
− | < | + | <sourcepawn>int MyFunction(Handle plugin, int numParams) |
+ | { | ||
+ | int client = GetNativeCell(1); | ||
+ | if (client < 1 || client > MaxClients) | ||
+ | { | ||
+ | return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client); | ||
+ | } | ||
+ | if (!IsClientConnected(client)) | ||
+ | { | ||
+ | return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected", client); | ||
+ | } | ||
+ | /* Code */ | ||
+ | }</sourcepawn> | ||
+ | |||
+ | ==Creating Natives for Methodmaps== | ||
+ | Declaring natives for methodmaps is similar to other natives, with a few requirements: | ||
+ | |||
+ | # The <tt>CreateNative</tt> string argument is in the format <tt>Tag.Function</tt>. | ||
+ | # The function is declared as a "member" of the methodmap. | ||
+ | # The magic "this" parameter is implicitly passed in first. | ||
+ | |||
+ | Here's an example of a shared plugin and its corresponding include file: | ||
+ | |||
+ | <sourcepawn>/* sharedplugin.sp */ | ||
+ | public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) | ||
+ | { | ||
+ | CreateNative("Number.PrintToClient", Native_PrintNumberToClient); | ||
+ | return APLRes_Success; | ||
+ | } | ||
+ | |||
+ | int Native_PrintNumberToClient(Handle plugin, int numParams) | ||
+ | { | ||
+ | int number = GetNativeCell(1); | ||
+ | int client = GetNativeCell(2); | ||
+ | |||
+ | PrintToChat(client, "%d", number); | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | /* sharedplugin.inc */ | ||
+ | methodmap Number | ||
{ | { | ||
− | + | public Number(int number) | |
− | + | { | |
− | + | return view_as<Number>(number); | |
− | + | } | |
− | + | public native void PrintToClient(int client); | |
− | + | }</sourcepawn> | |
− | |||
− | |||
− | |||
− | |||
− | }</ | ||
[[Category:SourceMod Scripting]] | [[Category:SourceMod Scripting]] |
Latest revision as of 08:18, 20 April 2022
SourceMod allows plugins to create their own natives that other plugins can use. This is very similar to AMX Mod X's register_native function. It is a powerful inter-plugin communication resource that can greatly enhance the functionality you provide.
Prerequisites: You should have a good grasp of Pawn or SourcePawn scripting, as well as how Tags work.
Note: Most functions herein can be found in the SourceMod SDK, under plugins/include/functions.inc.
Contents
Introduction
Before creating natives, you should always document and specify exactly how they will work. This means writing your "include file" beforehand. Not only does this guide you in writing your natives, but it has the added benefit of completing a large portion of documentation at the same time.
Include File
For our first example, let's start off with a simple native that adds two numbers, a float and a non-float. It might look like this:
/** Double-include prevention */ #if defined _mynatives_included_ #endinput #endif #define _mynatives_included_ /** * Adds two numbers together. * * @param num1 An integer. * @param num2 A float. * @return The float value of the integer and float added together. */ native float My_AddNumbers(int num1, float num2);
The documentation and commenting style above is a SourceMod Development Team convention. While you are not required to follow it for your own code, it may be a good idea to enhance readability (as readers will expect the style to conform).
Creating the Native
To actually create the native, first let's define the native handler itself. From the API reference we can see that there are two prototypes. We will use the second one:
function any (Handle plugin, int numParams)
Thus, we have a function like:
any Native_My_AddNumbers(Handle plugin, int numParams) { }
Note that we don't use the same function name as we did in our include file. This is to prevent a possible name clash. Otherwise, two different symbols (one a native, another a function) could have the same name, and that's not allowed. We'll see later how we bind the native to its actual name.
Next, we need to implement our native.
any Native_My_AddNumbers(Handle plugin, int numParams) { /* Retrieve the first parameter we receive */ int num1 = GetNativeCell(1); /* Retrieve the second parameter we receive */ float num2 = view_as<float>(GetNativeCell(2)); /* Add them together */ float value = num1 + num2; /* Return the value */ return value; }
You should note three important details. The first is that we have to manually grab the parameters -- they're not easily sent via arguments. This is for technical reasons that go beyond the scope of this document. That means you have to know exactly how to grab each parameter. For our example, we know that both parameters are simply cells, and thus we can use GetNativeCell.
However, the second detail is that there is no GetNativeCellFloat. This is because a float is still a cell; the data is just interpreted differently. Thus, we cast the type to float manually. This is different from calling float(GetNativeCell(2)). In fact, that would not work; the float() call would interpret the cell as an integer, when in fact it's already a float, just with the wrong type (i.e., no type at all).
Lastly, we did not need to verify numParams. The compiler will always send the correct amount of parameters - the user can't mess this up in any easy way. Only if you use variadic arguments, or add parameters and need to support older binaries, do you need to handle numParams variations.
Registering the Native
Natives must be registered in AskPluginLoad2. Although they can be registered elsewhere, only this function guarantees that all other plugins will see your native. The definition for this forward is in sourcemod.inc.
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { CreateNative("My_AddNumbers", Native_My_AddNumbers); return APLRes_Success; }
Now, any plugin will be able to use our native as if it were from a Core or a C++ extension.
Usage
By-Reference
Sometimes, it may be desirable to return data through by-reference parameters. For example, observe the following native prototype:
/** * Closes a Handle, and sets the variable to INVALID_HANDLE by reference. * * @param hndl Handle to close and clear. * @return Anything that CloseHandle() returns. */ native bool AltCloseHandle(Handle &hndl);
An implementation of this would look like:
int Native_AltCloseHandle(Handle plugin, int numParams) { Handle hndl = view_as<Handle>(GetNativeCellRef(1)); SetNativeCellRef(1, 0); /* Zero out the variable by reference */ return CloseHandle(hndl); }
Now, we have a native that is "safer" than a normal CloseHandle, as it ensures we don't hold onto invalid Handles.
Strings
Strings can be retrieved and altered via dynamic natives. Observe the following native prototype:
/** * Converts a string to upper case. * * @param str String to convert. * @noreturn */ native void StringToUpper(char[] str);
An implementation of this is very easy, because of dynamic arrays:
int Native_StringToUpper(Handle plugin, int numParams) { int len; GetNativeStringLength(1, len); if (len <= 0) { return; } char[] str = new char[len + 1]; GetNativeString(1, str, len + 1); for (int i = 0; i < len; i++) { /* 5th bit specifies case in ASCII -- * this is not UTF-8 compatible. */ if ((str[i] & (1 << 5)) == (1 << 5)) { str[i] &= ~(1 << 5) } } SetNativeString(1, str, len+1, false); }
Arrays
Working with arrays is very similar to strings. For example, observe the following native prototype:
/** * Sums an array of numbers and returns the sum. * * @param array Array of numbers to sum. * @param size Size of array. * @return Sum of the array elements. */ native int SumArray(const int[] array, int size);
An implementation of this could look like:
int Native_SumArray(Handle plugin, int numParams) { int size = GetNativeCell(2); if (size < 1) { return 0; } int[] array = new int[size]; int total; GetNativeArray(1, array, size); for (int i = 0; i < size; i++) { total += array[i]; } return total; }
Similarly, there is a SetNativeArray, which is used to alter array contents.
Variable Arguments
Variable arguments are usually used when dealing with format class functions, however it is also possible to use them for general functionality. The only trick is that unlike normal parameters, every variable argument parameter is passed by-reference. This adds no change for arrays and strings, but for floats, handles, numbers, or anything else that fits in a cell, GetNativeCellRef must be used instead.
As an example, let's convert our function from above to use variable arguments:
/** * Sums a list of numbers. * * @param num First number to use as a base. * @param ... Any number of additional numbers to add. * @return Sum of all numbers passed. */ native int SumParams(int num, ...);
An implementation of this would look like:
int Native_SumParams(Handle plugin, int numParams) { int base = GetNativeCell(1); for (int i = 2; i <= numParams; i++) { base += GetNativeCellRef(i); } return base; }
Format Functions
It is also possible to create your own format class functions. This topic is much more complicated than the previous ones, as the native string formatter allows for many different configurations. This is mainly for optimization. Note that a formatting routine deals with two buffers: the input format string, and the output buffer. FormatNativeString, by default, uses parameter indexes directly so you don't have to copy or locally store any of these buffers.
The most common usage of this is to accept a user-formatted string and to not
/** * Prints a message with a [DEBUG] prefix to the server. * * @param fmt Format string. * @param ... Format arguments. */ native void DebugPrint(const char[] fmt, any ...);
This could be easily implemented as:
native int DebugPrint(Handle plugin, int numParams) { char buffer[1024]; int written; FormatNativeString(0, /* Use an output buffer */ 1, /* Format param */ 2, /* Format argument #1 */ sizeof(buffer), /* Size of output buffer */ written, /* Store # of written bytes */ buffer /* Use our buffer */ ); PrintToServer("[DEBUG] %s", buffer); }
Throwing Errors
Often, your native will encounter an unrecoverable error, and you wish to trigger a fatal exception. This is equivalent to IPluginContext::ThrowNativeError, and will halt the current function execution in the plugin. The debugger will be invoked and a backtrace will be printed out (if the plugin is in debug mode). Although the current function is cancelled, the plugin will continue to operate normally afterward.
For example, let's say we have a native that operates on clients. We want to throw an error if we get a client index that is invalid or disconnected. We could do this like so:
int MyFunction(Handle plugin, int numParams) { int client = GetNativeCell(1); if (client < 1 || client > MaxClients) { return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client); } if (!IsClientConnected(client)) { return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected", client); } /* Code */ }
Creating Natives for Methodmaps
Declaring natives for methodmaps is similar to other natives, with a few requirements:
- The CreateNative string argument is in the format Tag.Function.
- The function is declared as a "member" of the methodmap.
- The magic "this" parameter is implicitly passed in first.
Here's an example of a shared plugin and its corresponding include file:
/* sharedplugin.sp */ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { CreateNative("Number.PrintToClient", Native_PrintNumberToClient); return APLRes_Success; } int Native_PrintNumberToClient(Handle plugin, int numParams) { int number = GetNativeCell(1); int client = GetNativeCell(2); PrintToChat(client, "%d", number); return; } /* sharedplugin.inc */ methodmap Number { public Number(int number) { return view_as<Number>(number); } public native void PrintToClient(int client); }