Difference between revisions of "Creating Natives (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
(Changed AskPluginLoad to AskPluginLoad2.)
m (pawn -> sourcepawn tag)
(14 intermediate revisions by 6 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:
<pawn>/** Double-include prevention */
+
<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 Float:My_AddNumbers(num1, num2);</pawn>
+
native float My_AddNumbers(int num1, int 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).
Line 28: Line 28:
 
==Creating the Native==
 
==Creating the Native==
 
To actually create the native, first let's define the native handler itself.  From <tt>functions.inc</tt> we can see that this follows the prototype:
 
To actually create the native, first let's define the native handler itself.  From <tt>functions.inc</tt> we can see that this follows the prototype:
<pawn>functag NativeCall public(Handle:plugin, numParams);</pawn>
+
<sourcepawn>typedef NativeCall = function int (Handle plugin, int numParams);</sourcepawn>
  
 
Thus, we have a function like:
 
Thus, we have a function like:
<pawn>public Native_My_AddNumbers(Handle:plugin, numParams)
+
<sourcepawn>public int Native_My_AddNumbers(Handle plugin, int numParams)
 
{
 
{
}</pawn>
+
}</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.   
<pawn>public Native_My_AddNumbers(Handle:plugin, numParams)
+
<sourcepawn>public int Native_My_AddNumbers(Handle plugin, int numParams)
 
{
 
{
  /* Retrieve the first parameter we receive */
+
/* Retrieve the first parameter we receive */
  new num1 = GetNativeCell(1)
+
int num1 = GetNativeCell(1);
  /* Retrieve the second parameter we receive */
+
/* Retrieve the second parameter we receive */
  new Float:num2 = Float:GetNativeCell(2)
+
float num2 = view_as<float>(GetNativeCell(2));
  /* Add them together */
+
/* Add them together */
  new Float:value = num1 + num2
+
float value = num1 + num2;
  /* Return the value */
+
/* Return the value */
  return _:value
+
return value;
}</pawn>
+
}</sourcepawn>
  
 
You should note four 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>.   
 
You should note four 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>.   
Line 59: Line 59:
  
 
==Registering the Native==
 
==Registering the Native==
Natives must be registered in <tt>AskPluginLoad</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>.
  
<pawn>public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
+
<sourcepawn>public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
 
{
 
{
  CreateNative("My_AddNumbers", Native_My_AddNumbers);
+
CreateNative("My_AddNumbers", Native_My_AddNumbers);
  return APLRes_Success;
+
return APLRes_Success;
}</pawn>
+
}</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 72:
 
==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:
<pawn>/**
+
<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 78:
 
  * @return        Anything that CloseHandle() returns.
 
  * @return        Anything that CloseHandle() returns.
 
  */
 
  */
native bool:AltCloseHandle(&Handle:hndl);</pawn>
+
native bool AltCloseHandle(Handle &hndl);</sourcepawn>
  
 
An implementation of this would look like:
 
An implementation of this would look like:
<pawn>public Native_AltCloseHandle(Handle:plugin, numParams)
+
<sourcepawn>public int Native_AltCloseHandle(Handle plugin, int numParams)
 
{
 
{
  new Handle:hndl = Handle:GetNativeCellRef(1);
+
Handle hndl = view_as<Handle>(GetNativeCellRef(1));
  SetNativeCellRef(1, 0);  /* Zero out the variable by reference */
+
SetNativeCellRef(1, 0);  /* Zero out the variable by reference */
  return CloseHandle(hndl);
+
return CloseHandle(hndl);
}</pawn>
+
}</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 92:
 
==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:
<pawn>/**
+
<sourcepawn>/**
 
  * Converts a string to upper case.
 
  * Converts a string to upper case.
 
  *
 
  *
Line 98: Line 98:
 
  * @noreturn
 
  * @noreturn
 
  */
 
  */
native StringToUpper(String:str[]);</pawn>
+
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:
<pawn>public Native_StringToUpper(Handle:plugin, numParams)
+
<sourcepawn>public int Native_StringToUpper(Handle plugin, int numParams)
 
{
 
{
  new len
+
int len;
  GetNativeStringLength(1, len)
+
GetNativeStringLength(1, len);
  
  if (len <= 0)
+
if (len <= 0)
  {
+
{
      return
+
return;
  }
+
}
  
  new String:str[len+1]
+
char[] str = new char[len + 1];
  GetNativeString(1, str, len+1)
+
GetNativeString(1, str, len + 1);
  
  for (new i=0; i<len; i++)
+
for (int i = 0; i < len; i++)
  {
+
{
      /* 5th bit specifies case in ASCII --  
+
/* 5th bit specifies case in ASCII --  
      * this is not UTF-8 compatible.
+
* this is not UTF-8 compatible.
      */
+
*/
      if ((str[i] & (1<<5)) == (1<<5))
+
if ((str[i] & (1 << 5)) == (1 << 5))
      {
+
{
        str[i] &= ~(1<<5)
+
str[i] &= ~(1 << 5)
      }
+
}
  }
+
}
 
+
 
  SetNativeString(1, str, len+1, false)
+
SetNativeString(1, str, len+1, false);
}</pawn>
+
}</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:
<pawn>/**
+
<sourcepawn>/**
 
  * Sums an array of numbers and returns the sum.
 
  * Sums an array of numbers and returns the sum.
 
  *
 
  *
Line 137: Line 137:
 
  * @return          Sum of the array elements.
 
  * @return          Sum of the array elements.
 
  */
 
  */
native SumArray(const array[], size);</pawn>
+
native int SumArray(const int array[], int size);</sourcepawn>
  
 
An implementation of this could look like:
 
An implementation of this could look like:
<pawn>public Native_SumArray(Handle:plugin, numParams)
+
<sourcepawn>public int Native_SumArray(Handle plugin, int numParams)
 
{
 
{
  new size = GetNativeCell(2)
+
int size = GetNativeCell(2);
  if (size < 1)
+
if (size < 1)
  {
+
{
      return 0
+
return 0;
  }
+
}
  
  new array[size], total
+
int array[size], total;
  GetNativeArray(1, array, size)
+
GetNativeArray(1, array, size);
  
  for (new i=0; i<size; i++)
+
for (int i = 0; i < size; i++)
  {
+
{
      total += array[i]
+
total += array[i];
  }
+
}
  return total
+
return total;
}</pawn>
+
}</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 164:
  
 
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:
<pawn>/**
+
<sourcepawn>/**
 
  * Sums a list of numbers.
 
  * Sums a list of numbers.
 
  *
 
  *
Line 171: Line 171:
 
  * @return      Sum of all numbers passed.
 
  * @return      Sum of all numbers passed.
 
  */
 
  */
native SumParams(num, ...);</pawn>
+
native int SumParams(int num, ...);</sourcepawn>
  
 
An implementation of this would look like:
 
An implementation of this would look like:
<pawn>public Native_SumParams(Handle:plugin, numParams)
+
<sourcepawn>public int Native_SumParams(Handle plugin, int numParams)
 
{
 
{
  new base = GetNativeCell(1)
+
int base = GetNativeCell(1);
  for (new i=2; i<=numParams; i++)
+
for (int i = 2; i <= numParams; i++)
  {
+
{
      base += GetNativeCellRef(i)
+
base += GetNativeCellRef(i);
  }
+
}
  return base
+
return base;
}</pawn>
+
}</sourcepawn>
  
 
==Format Functions==
 
==Format Functions==
Line 188: Line 188:
  
 
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
<pawn>/**
+
<sourcepawn>/**
 
  * Prints a message with a [DEBUG] prefix to the server.
 
  * Prints a message with a [DEBUG] prefix to the server.
 
  *
 
  *
Line 194: Line 194:
 
  * @param ...        Format arguments.
 
  * @param ...        Format arguments.
 
  */
 
  */
native DebugPrint(const String:fmt[], any:...);</pawn>
+
native void DebugPrint(const char[] fmt, any ...);</sourcepawn>
  
 
This could be easily implemented as:
 
This could be easily implemented as:
<pawn>native DebugPrint(Handle:plugin, numParams)
+
<sourcepawn>native int DebugPrint(Handle plugin, int numParams)
 
{
 
{
  decl String:buffer[1024], written
+
char buffer[1024];
 +
int written;
  
  FormatNativeString(0, /* Use an output buffer */
+
FormatNativeString(0, /* Use an output buffer */
      1, /* Format param */
+
1, /* Format param */
      2, /* Format argument #1 */
+
2, /* Format argument #1 */
      sizeof(buffer), /* Size of output buffer */
+
sizeof(buffer), /* Size of output buffer */
      written, /* Store # of written bytes */
+
written, /* Store # of written bytes */
      buffer /* Use our buffer */
+
buffer /* Use our buffer */
      )
+
);
 
+
 
  PrintToServer("[DEBUG] %s", buffer)
+
PrintToServer("[DEBUG] %s", buffer);
}</pawn>
+
}</sourcepawn>
  
 
==Throwing Errors==
 
==Throwing Errors==
Line 216: Line 217:
  
 
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:
<pawn>public MyFunction(Handle:plugin, numParams)
+
<sourcepawn>public 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;
 +
}
 +
 
 +
public int Native_PrintNumberToClient(Handle plugin, int numParams)
 +
{
 +
int number = GetNativeCell(1);
 +
int client = GetNativeCell(2);
 +
 
 +
PrintToChat(client, "%d", number);
 +
return;
 +
}
 +
 
 +
/* sharedplugin.inc */
 +
methodmap Number
 
{
 
{
  new client = GetNativeCell(1)
+
public Number(int number)
  if (client < 1 || client > GetMaxClients())
+
{
  {
+
return view_as<Number>(number);
      return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client)
+
}
  }
+
public native void PrintToClient(int client);
  if (!IsClientConnected(client))
+
}</sourcepawn>
  {
 
      return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected", client)
 
  }
 
  /* Code */
 
}</pawn>
 
  
 
[[Category:SourceMod Scripting]]
 
[[Category:SourceMod Scripting]]

Revision as of 14:49, 26 March 2020

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.

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, int 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 functions.inc we can see that this follows the prototype:

typedef NativeCall = function int (Handle plugin, int numParams);

Thus, we have a function like:

public int 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.

public int 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 four 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 change the tag to Float manual. 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 tag (i.e., no tag at all).

Similarly, the third detail is that we cannot simply put return value or return FloatRound(value). The reason is that our native is supposed to be returning a float. If we return value, we'll get a tag mismatch. If we return FloatRound(value), we'll be returning an integer when a float is expected. The solution is to change the tag to the default tag, which will retain the value but remove the tag warning.

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:

public 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:

public 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:

public int Native_SumArray(Handle plugin, int numParams)
{
	int size = GetNativeCell(2);
	if (size < 1)
	{
		return 0;
	}
 
	int array[size], 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:

public 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:

public 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:

  1. The CreateNative string argument is in the format Tag.Function.
  2. The function is declared as a "member" of the methodmap.
  3. 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;
}
 
public 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);
}