Expose full epoll_ctl functionality.

Now the user has access to all struct elements in EpollEvent when
using epoll_ctl. This is in particular very intersting when the
user wants to access the ptr field.

The user can use GCHandle.ToIntPtr to give the struct a GCHandle
and then handle the pointer later on when using epoll_wait.

Here is an example of what you can do:

```csharp

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Mono.Unix.Native;

namespace Test
{
	interface IDispatchable
	{
		void Dispatch(EpollEvent ee);
	}

	class Epoll
	{
		public int FileDescriptor { get; protected set; }
		public int Size { get; protected set; }

		public Epoll(int size)
		{
			Size = size;
			FileDescriptor = Syscall.epoll_create(size);
		}

		public int Wait(EpollEvent[] events, int max_events, int timeout)
		{
			return Syscall.epoll_wait(FileDescriptor, events, max_events, timeout);
		}

		public void Add(int fd, EpollEvents events, IntPtr ptr)
		{
			Syscall.epoll_ctl(FileDescriptor, EpollOp.EPOLL_CTL_ADD, fd, events, ptr);
		}

		public void Close()
		{
			Syscall.close(FileDescriptor);
		}

		public void Dispatch(int count, int timeout)
		{
			EpollEvent[] events = new EpollEvent[count];
			int n = Wait(events, events.Length, timeout);

			for (int i = 0; i < n; i++) {
				// and here we can use GCHandle.FromIntPtr to resolve the pointer to an object
				var obj = GCHandle.FromIntPtr(events[i].ptr).Target;
				if (obj is IDispatchable) {
					(obj as IDispatchable).Dispatch(events[i]);
				}
			}
		}
	}

	public class Loop
	{
	}

	class Tcp : IDispatchable
	{
		Socket socket;
		Epoll loop;

		public Tcp(Epoll epoll)
		{
			loop = epoll;
			socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			socket.Blocking = false;
		}

		public bool IsConnecting { get; private set; }
		public bool IsConnected { get; protected set; }

		public void Dispatch(EpollEvent ev)
		{
			if ((ev.events & EpollEvents.EPOLLOUT) > 0) {
				if (IsConnecting) {
					IsConnecting = false;
					IsConnected = true;
					OnConnect(null);
				} else if (IsConnected) {
					Console.WriteLine("Spin");
				}
			}
		}

		protected void OnConnect(Exception exception)
		{
			if (connectCallback != null) {
				connectCallback(exception);
			}
		}

		Action<Exception> connectCallback = null;
		public void Connect(IPAddress ipAddress, int port, Action<Exception> callback)
		{
			connectCallback = callback;
			IsConnecting = true;
			try {
				socket.Connect(IPAddress.Parse("127.0.0.1"), 8000);
				OnConnect(null);
			} catch (SocketException ex) {
				if (ex.ErrorCode != 10035) {
					throw;
				}
			}

			// Here we are passing a gchandle of this object to the event loop
			loop.Add(socket.Handle.ToInt32(), EpollEvents.EPOLLIN | EpollEvents.EPOLLOUT | EpollEvents.EPOLLET, GCHandle.ToIntPtr(GCHandle.Alloc(this)));
		}

		public void Write(byte[] array, int index, int count)
		{
			Write(new ArraySegment<byte>(array, index, count));
		}

		public void Write(ArraySegment<byte> segment)
		{
			int n = socket.Send(segment.Array, segment.Offset, segment.Count, SocketFlags.None);
			if (n < segment.Count) {
				// queue
				Console.WriteLine(n);
			}
		}

	}

	class MainClass
	{
		public static void Main(string[] args)
		{
			var thread = new Thread((_) => {
				var listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);
				Console.WriteLine ("Listening");
				listener.Start();
				var client = listener.AcceptTcpClient();
				var sw = new StreamWriter(client.GetStream());
				sw.Write("Hello World!");
				sw.Flush();

				var sr = new StreamReader(client.GetStream());
				sr.ReadLine();
				client.Close();
				Console.WriteLine("Done");
			});

			thread.Start();


			Thread.Sleep(300);

			var loop = new Epoll(100);
			var t = new Tcp(loop);
			t.Connect(IPAddress.Parse("127.0.0.1"), 8000, (ex) => {
				var bytes = Encoding.ASCII.GetBytes("Hello World!\n");
				t.Write(bytes, 0, bytes.Length);
			});

			while (true) {
				loop.Dispatch(100, 0);
			}

			thread.Join();
		}
	}
}
```