From AS3 to C#, Part 21: Unsafe Code

Datetime:2016-08-23 02:54:24          Topic: C#  ActionScript           Share

The seriesis nearing an end! In today’s article we’ll cover so-called “unsafe” code that gives you unprecedented access to system memory. You can use this to optimize your app or integrate with native (e.g. C, C++) code and APIs. Read on to learn more about this powerful C# tool!

First let’s look at AS3′s support for unsafe code. Essentially, there is none. However, AIR does have the ability to access native code through the native extensions API. This is not an AS3 feature, but rather an AIR Player feature. Along with this there are a host of other APIs like File and NativeProcess that allow you to gain access to functionality that would be considered a security breach for a web plugin to have. This access is always through a normal—”safe”—AS3 API.

In C#, there are several language features that are classified as “unsafe”. If you use any of them, your app will need to run in an environment that allows unsafe code. Like with the Flash browser plugin, Unity’s browser plugin doesn’t allow unsafe code. For standalone apps—Windows, Mac, iOS, Android, Linux, etc.—it should work fine. Just like with Flash and AIR, you’ll need to keep your target platforms in mind when developing your code.

With that in mind, let’s start looking at how to use code that’s considered “unsafe”. To enable it, you can add the unsafe keyword to a whole function, class, struct, interface, delegate, event, or property like so:

unsafe void Danger()
unsafe class Danger {}
unsafe struct Danger {}
unsafe interface IDanger {}
unsafe delegate void Danger();
unsafe event Danger OnDanger;
unsafe int Danger { get; set; }

If you use the unsafe keyword this way then all your code within the unsafe item will be considered unsafe. If you’d rather mark just part of a function as unsafe, you can use an unsafe block:

void PartlySafe()
{
	// Safe
	int x = 2;
 
	unsafe
	{
		// Unsafe
		x = 20;
	}
 
	// Safe again
	x = 200;
}

So what can you actually do in an unsafe context? Well, the first thing to do is use pointers like you would in C or C++. As a very quick primer, a pointer is a variable that stores a memory address. It “points” to an actual value, but isn’t the actual value. This is very similar to references in C# or AS3, but at at lower level where pesky middlemen like the GC are not involved. Here’s a very simple function that doubles an integer using pointers. It shows off three new operators!

// Declare function as unsafe so we can use pointers
unsafe int DangerousDouble(int val)
{
	// Declare pInt as a pointer to an integer using *
	// Get the memory address of val using &
	int* pInt = &val;
 
	// "Dereference" pInt using * on the left side
	// This returns the actual value
	*pInt = (*pInt) * 2;
 
	// Another dereference
	return *pInt;
}
 
int val = 10;
int ret = DangerousDouble(10);
Debug.Log(val + " doubled is: " + ret);
 
/* output:
10 doubled is: 20
*/

Dereferencing pointers gives the value they point to, unless the pointer is null and there is an error. It’s really common to have pointers to structures rather than just simple types like int . In this case, it’s a bit cumbersome to access the structure’s variables:

struct Point2
{
	public float X;
	public float Y;
}
 
// Note: we can take pointers as parameters
unsafe float GetX(Point2* point)
{
	// Can't access fields of point since it's just a memory address
	// Need to dereference (*point) to get the actual Point2
	return (*point).X;
}

This can be cleaned up by using the “arrow” operator which is just a combination of a pointer dereference and a field access like above. Here’s how GetX would have looked if we used that:

unsafe float GetX(Point2* point)
{
	return point->X;
}

Another common pattern is to use a pointer as not just the address of a single instance of some type but rather an array of contiguous instances of that type. In this case, accessing the array can be done using the same index ( [] ) operator as with normal C# arrays. Here’s a little function that doubles all the values in an array:

// Pointers treated like arrays don't have a built-in Length
// Need to pass that in addition
unsafe void Double(float* val, int len)
{
	for (int i = 0; i < len; ++i)
	{
		val[i] *= 2;
	}
}

Lastly, since pointers are just memory addresses and memory addresses are just integers, you can perform “pointer arithmetic” on them. The normal integer operators like + , - , * , ++ , < , == , and so forth are all fully supported. The Double function above could have been implemented by manipulating the pointer to “march” it along the array:

unsafe void Double(float* val, int len)
{
	for (int i = 0; i < len; ++i)
	{
		*val *= 2;
		val++;
	}
}

There are two more “unsafe” things you can do in C#. First, you can allocate memory directly on the stack rather than using the heap like normal. This is very efficient as long as you don’t need to allocate much memory as the stack is usually quite limited. As an example, here’s some code that uses the stackalloc operator instead of new to allocate an array of values for Double to work on:

unsafe void TryDouble()
{
	// Allocate an array of four floats
	// A pointer to the first one is returned
	float* vals = stackalloc float[4];
 
	// Loop over the array assigning initial values
	for (int i = 0; i < 4; ++i)
	{
		vals[i] = i;
	}
 
	// Double the values in the array
	Double(vals, 4);
 
	// Print out the values in the array
	for (int i = 0; i < 4; ++i)
	{
		Debug.Log(vals[i]);
	}
}
 
/* output:
0
2
4
6
*/

The final thing you can do is use the fixed keyword in a couple of different ways. The first way is to create a fixed-size buffer as a field of a structure. The size of instances of this structure will grow by the size of the fixed-size buffer rather than just the size of a pointer, as would happen with a normal array. In this way, it’s like you declared a bunch of individual values as fields. Here’s a quick example:

unsafe struct Huge
{
	// 1024 * 1024 = 1 MB of memory for one instance of this struct!
	public fixed byte Data[1024 * 1024];
}

The other way to use the fixed keyword is to create a “fixed block”. Syntactically, this is similar to the “using block” with the using keyword. The “fixed block” tells the garbage collector to not move a variable that you want to “fix”. Like with “using blocks”, you also get to assign a variable that’s scoped to just the block. That variable is also “fixed”. Here’s an example to illustrate:

// Function to initialize an array of data so that all
// elements of the array are the same value
unsafe void InitData(byte* data, byte initialVal)
{
	byte* p = data;
	byte* end = p + 1024 * 1024;
	while (p < end)
	{
		*p = initialVal;
		p++;
	}
}
 
// Function to initialize a Huge so that all elements
// of its Data array are the same value
// Using a 'ref' parameter here to avoid copying the struct
unsafe void InitHuge(ref Huge huge, byte initialVal)
{
	// Need to "fix" the Huge so the GC doesn't move it
	// Also fixes the pHuge pointer
	fixed (Huge* pHuge = &huge)
	{
		// Safe to do lots of operations on the Huge now
		InitData(pHuge->Data, initialVal);
	}
}
 
unsafe
{
	// Make a Huge
	Huge huge;
 
	// Initialize the Huge so all values of its Data are 10
	// Remember to pass by reference with 'ref' keyword
	InitHuge(ref huge, 10);
 
	// Too much to print. Just spot check.
	Debug.Log(huge.Data[0] + " " + huge.Data[20]);
}
 
/* output:
10 10
*/

You aren’t limited to “fixing” just structures. You can also “fix” strings, arrays, fixed-length buffers, and single variables like double* .

That wraps up “unsafe” code in C#. It’s powerful and can be very fast, but you should be quite careful when dealing with it. Usually your C# code shouldn’t use it unless you have a very good reason to. Dealing with native C or C++ code can be one of those reasons, as can optimization, but you should try to keep the unsafe portion of your codebase as small as possible for, well, safety.

Since there is no AS3 equivalent—even a distant one—to today’s topic, we’ll skip the side-by-side comparison for today’s article. Next week we’ll cover the last set of topics before wrapping up the following week. Stay tuned!

Spot a bug? Have a question or suggestion? Post acomment!





About List