A Ten-Thousand Foot View of SimConnect - Part 3
By this point, we have a basic operation that connects to SimConnect at the click of a button...and does nothing. So it's about time we start looking at the framework of how to actually interact with FSX.
You may have read, heard or gotten involved in the conversations about multithreading, hyperthreading, fibers, etc., but all you really need to know about the whole concept is the message queue. As we said before, your SimConnect program (like other Windows applications) is Event Driven. This means that the program acts on events sent to it from the system. Say for instance, you click on our "Connect" button. The operating system registers that mouse click, and sends a message to the program. The program gets the message, containing the click event, and fires off our function in response. A lot of this is transparent, because it is built into Windows and the .NET framework, but we have the ability to expose some of the functionality and expand upon it, which is exactly what we plan to do. In SimConnect, events and data are sent to our program via system messages.
For an in-depth look at the Windows System Message system, check out this page:
Messages and Message Queues
The core of our application's message system is the "Windows Procedures" or WinProc. It is a function that receives system messages and figures out what to do with them. We want to have our program handle the SimConnect messages when they come down the pipe, so we use an ability in programming to "override" the default function. This just means that we are taking an already defined function and creating our own version of it. In the case of the WinProc, it will look something like this:
protected override void DefWndProc(ref Message m)
{ if (m.Msg == WM_USER_SIMCONNECT) {if (simconnect != null)
{simconnect.ReceiveMessage();
}
}
else {base.DefWndProc(ref m);
}
}
As you can see, we are defining the function called DefWndProc, or Default Windows Procedures. "Protected" refers to the accessibility of the function in a way, meaning who can access or alter it. We have already talked about override, that tells us that we are making our own version of an existing function, and void is the return type. Functions can return a value, such as if you have a function that adds two integers together, the return value could be the sum. When you see void, it means that there is no return value.
Looking closer at the meat of the function, we discover our old friend WM_USER_SIMCONNECT. You will recall that we defined this constant variable back at the beginning and sent him along with our connection function. This variable identifies the system messages coming from FSX. When we connect to SimConnect, we pass along this identifier, and whenever FSX sends a message back to us, it has that same value attached to it. So by looking for that value, we know that it has come from SimConnect. Then, the function continues: "if the simconnect object is not null (meaning we have connected to FSX already), then run the method simconnect.ReceiveMessage( )". This is the function within the SimConnect framework that processes the messages sent to us and figures out what we want to do with them. If it's not a message from FSX, process it as normal. Basically, there's no reason why we would ever need to change this. We can just stick it in our Form1 class under the button click function and know that it will do what we want.
So now we are ready to handle messages from SimConnect. The first one we want to look for is the message telling us that our add-on has successfully connected to FSX. The messages contain data structures, which are just variables grouped together into a consolidated object. Think of them like classes, but without the functions. All we need to know about them is that they are little bundles of data wrapped into one object. The SIMCONNECT_RECV_OPEN data structure contains a whole slew of information about the connection, the version of SimConnect, all of that lovely nonsense. Right now, we don't need any of that, we just need to know what happens, and when.
Luckily, ACES likes us, and has put that functionality into the SimConnect wrapper. After we have our connection to SimConnect, and therefore, our simconnect object is defined, we can start adding stuff to it. The first thing is event handlers. This doesn't require a whole lot of explanation. It's just a statement of what function to run when a certain event is received.
NOTE: You may have noticed already that Visual C# Express has an "autocomplete" function. This is incredibly useful (although sometimes it gets in your way when you are coding quickly), in that it automates certain procedures based on what you are probably trying to do. In the case of these event handlers, they will allow you to press the tab key to enter the default values, and will even create the new function for you, like what happened with our button click function.
Speaking of our button click function, here it is as it stands right now:
private void button_Connect_Click(object sender, EventArgs e)
{ try {simconnect = new SimConnect("SimConnect Tutorial", this.Handle, WM_USER_SIMCONNECT, null, 0);
textBox_Connect.Text = "Connected to FSX";}
catch (COMException ex) { textBox_Connect.Text = "Unable to Connect";}
}
Right after our the line that sets the textbox text to "Connected to FSX" but before the } (curly bracket), we are going to enter this line:
simconnect.OnRecvOpen += new SimConnect.RecvOpenEventHandler(simconnect_OnRecvOpen);As soon as you type "simconnect.", a drop down box will appear with all of the properties and methods of this object, as I mentioned above. You can type in "OnRecvOpen" or find it on the list. So far, we've only used the equal sign by itself (=), but here we use plus sign equal sign (+=). This adds a value to a definition, as opposed to just assigning a value. In this case, it is adding something new to the function that processes when the "Open" message is received (which happens when SimConnect is connected). Immediately after typing "+=", you should be presented with an option to press tab to enter the default value, and then to press tab again to create the new function automagically. That's the method I followed here. As you can see, we are adding a new event handler to the OnRecvOpen event, that fires off the function "simconnect_OnRecvOpen". In this function, we define what we want to do when FSX has told us that we are connected.
Everyone with me still? Just in case more clarification is needed, I mapped it out in another of the "Worst Flowcharts Ever" (click for full-size)
As you can see, SimConnect sends messages (events) into the message queue, which the OS sends to our program. The DefWndProc passes them along to the simconnect object, which sees that there is an event handler for this event. On top of that, we have added our own event handler to the mix, and in that we do whatever we need to do.
If you tabbed through the creation of our event handler line, you will notice that it added the function "simconnect_OnRecvOpen" to the code. There's an exception in there right now, telling us that we haven't done anything with it yet. We want to remove that. In it's place, just cut and paste the following line from our button click function:
textBox_Connect.Text = "Connected to FSX";Because, really, we don't really want to tell the user that we are connected until we are sure that we are connected. This waits until we receive notification from SimConnect that the connection is made, then changes the status. Make sure you remove that line from it's original position as well. If you want to be fancy, you can leave it in, but change the text to "Connecting..." or something like that. In most cases, it won't be up very long anyways.
We're going to add another event handler right under our first one. This time, it's to catch the event that SimConnect sends out if the user quits out of FSX. The handler is OnRecvQuit, and the line looks like this:
simconnect.OnRecvQuit += new SimConnect.RecvQuitEventHandler(simconnect_OnRecvQuit);If we use tab and autocomplete again, it will create another function called simconnect_OnRecvQuit. We're just going to add a line in there to tell the user that we have been disconnected.
void simconnect_OnRecvQuit(SimConnect sender, SIMCONNECT_RECV data) { textBox_Connect.Text = "Disconnected";}
Now, run the program (once again, click the green arrow or use the menu option Debug -> Start Debugging. The shortcut key is F5) without FSX running. If we press the connect button, we should get a message telling us "Unable to Connect". Load FSX, start a flight, and press connect again. Now it should report that we are connected. Leave the program running, and exit out of FSX. Now we are informed that SimConnect has disconnected.
And then we get an exception.
Go back up to the debug menu and select "Stop Debugging". VC# will take you back to the basic code view. What went wrong? Well, we don't have SimConnect running anymore, but we still have the simconnect object waiting for messages. So we need to get rid of the simconnect object within our program. Luckily, again, they give us a simple way to do this, called the Dispose method. Add these two lines of code to dispose of the simconnect object and set it back to null where we started:
void simconnect_OnRecvQuit(SimConnect sender, SIMCONNECT_RECV data) { textBox_Connect.Text = "Disconnected";simconnect.Dispose();
simconnect = null;}
Try it again and everything should work as expected.
Now we have a good framework for the program, have the connection and some events running, but we also want to think about what the user might want to do. For our last step tonight, we are going to expand our button click function to turn the connect button into a disconnect button when we connect.
This is going to take a few lines of code throughout our program. First, in our simconnect_OnRecvOpen function, we want to change the text of the button to "Disconnect". When the program connects to FSX, this will just change what the button says. Right after the textbox line, add the following line of code:
button_Connect.Text = "Disconnect";Same deal with the simconnect_OnRecvQuit function:
button_Connect.Text = "Connect";So now, when the program connects or disconnects, the button changes. Now to implement the functionality, we use a if - then - else loop. This is called a "conditional". If this is true, do this, else do this. I'll paste the new button click code and then explain it.
private void button_Connect_Click(object sender, EventArgs e)
{if (simconnect == null)
{ try {simconnect = new SimConnect("SimConnect Tutorial", this.Handle, WM_USER_SIMCONNECT, null, 0);
textBox_Connect.Text = "Connecting..."; simconnect.OnRecvOpen += new SimConnect.RecvOpenEventHandler(simconnect_OnRecvOpen); simconnect.OnRecvQuit += new SimConnect.RecvQuitEventHandler(simconnect_OnRecvQuit);}
catch (COMException ex) { textBox_Connect.Text = "Unable to Connect";}
}
else { textBox_Connect.Text = "Disconnected"; button_Connect.Text = "Connect";simconnect.Dispose();
simconnect = null;}
}
If our simconnect object equals (== compares two values, as opposed to = which just sets a value) null, meaning that we do not yet have a connected simconnect object, then run through our connection procedure. ELSE, if it does not equals null, meaning that we are connected, then do the same thing as if FSX just quit. Set our text to show that there is no connection and dispose of the SimConnect object.
Try it out. Run your program and FSX and you should find yourself able to connect and disconnect as many times as you want, both by using the button and by closing FSX.
By now, hopefully a lot of the theory behind this is understood. I've tried to put a lot of detail into a very small bit of actual operation so that those who aren't familiar with the procedures of programming or C# or even just SimConnect will be able to ride along smoothly down the line.
As of now, we have a program that is able to connect and disconnect from SimConnect, can detect and handle events sent from FSX, and has the beginnings of a useable interface. Not bad for a few lessons, I think. Now that we have a framework for events, we are going to use that ability to send and listen to events, so that we can control the toggling of landing gear from outside the program, and then to detect when the gear are up or down.
Could this happen tomorrow? Maybe. I'll certainly try my best to get it done by the end of the day (Friday).
And finally, here is the full code as I have it right now for Form1.cs:
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using Microsoft.FlightSimulator.SimConnect;using System.Runtime.InteropServices;namespace SimConnect_Tutorial{public partial class Form1 : Form
{SimConnect simconnect;
const int WM_USER_SIMCONNECT = 0x0402;
public Form1() { InitializeComponent();
}
private void button_Connect_Click(object sender, EventArgs e)
{if (simconnect == null)
{ try {simconnect = new SimConnect("SimConnect Tutorial", this.Handle, WM_USER_SIMCONNECT, null, 0);
textBox_Connect.Text = "Connecting..."; simconnect.OnRecvOpen += new SimConnect.RecvOpenEventHandler(simconnect_OnRecvOpen); simconnect.OnRecvQuit += new SimConnect.RecvQuitEventHandler(simconnect_OnRecvQuit);}
catch (COMException ex) { textBox_Connect.Text = "Unable to Connect";}
}
else { textBox_Connect.Text = "Disconnected"; button_Connect.Text = "Connect";simconnect.Dispose();
simconnect = null;}
}
void simconnect_OnRecvQuit(SimConnect sender, SIMCONNECT_RECV data) { textBox_Connect.Text = "Disconnected"; button_Connect.Text = "Connect";simconnect.Dispose();
simconnect = null;}
void simconnect_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data) { textBox_Connect.Text = "Connected to FSX"; button_Connect.Text = "Disconnect";}
protected override void DefWndProc(ref Message m)
{ if (m.Msg == WM_USER_SIMCONNECT) {if (simconnect != null)
{simconnect.ReceiveMessage();
}
}
else {base.DefWndProc(ref m);
}
}
}
}
Labels: FSX, Programming, SimConnect
11 Comments:
Thank you for this tutorial. I used it for a starting point to get a small switch panel interfaced to FSX. You can check out my progress on my blog. (http://workbench.freetcp.com/
I'm looking forward to the next part in the series!
I got the Part 1 and Part 2 to work for me :) But when I came to part 3, no matter how closely and perfectly i followed the tutorial I simply couldn't get the program to work! I hadn't set up the simconnect debug window, so I did that. Then ran the program from Part 1 of the tutorial and instead of giving the line you said it would I keep getting "XXX.XXXXX [63] Inactive!" (The X's seem to be random numbers that change every time, the 63 is the same every time. I have re-coded the program several times and keep getting this message. How Ideas on what could be wrong? I thinking if the sdk isn't installed properly or something like that? Thanks
First thing thank you for your great work.
I still have a question :
On Part3 there is a "OnRecvQuit", but it never enter this function.
Could you explain why.
Thank you in advance, and again continue the great work.
Mike,
We have noticed this with a number of people. It is possible, from what Pete Dowson has said, that it might be an error with SimConnect, or in some way, the transfer is being blocked. For some reason, even on the local machine, SimConnect still uses TCP/IP to transfer between FSX and your application. Make sure you don't have any firewall applications, including Windows Firewall running and try it again.
Joe:
Check out the lines
simconnect.OnRecvOpen += new SimConnect.RecvOpenEventHandler(simconnect_OnRecvOpen);
simconnect.OnRecvQuit += new SimConnect.RecvQuitEventHandler(simconnect_OnRecvQuit);
These two lines set up the event handlers. OnRecvQuit is called when FSX exits out, and in the program so far, it just sets the button back to the connect stage.
Thank you! Yep, that was the problem, Disabled all the firewalls and got everything Working!! Looking forward to the next part.
Thanks
Great tutorials! Thanks for your efforts. I am a Delphi programmer and I see in the AVSIM forums that somebody has created an interface to SimConnect with that, so this gives me a starting point. One small criticism though, whatever you have done in tut3 to format the code examples make the text appear about 1mm high in my browser(i.e.). Can you check what is different between tuts1-2 and 3 because it's very hard to read in 3
Thnx heaps for your efforts :)
Scott,
I've been using a code formatter for Windows Live Writer. I took a look in IE 7 and I'm getting the same effect. I'll take a look at changing the formatting size. I also notice that the line numbers and color formatting doesn't show either. I'll go back to my old manual method, because if you guys can't read it, what's the point.
By the way, give it a shot in Firefox if it's available to you. The font size is at least a bit better.
Great tutorial !
Your blog is an excellent vehicle to learn some C# with FSX as the motivator.
So far I'm with you... ;).
Some remarks :
- I had to create a new user under XP, as Visual C# doesn't like a XP-user like "Mom & Dad". (error message when opening a new project)
- I cannot use F5 or the green arrow, it results in an error message. CTRL-F5 however works.
Thanx !
I like to thank you for this tutorial. I need help in part three I keep getting an error.
Error 1
The type or namespace name 'SimConnect_Recv' could not be found (are you missing a using directive or an assembly reference?)
what went wrong? please help.
Hi and thank's for this great Tutorial i was looking for !
i used it to work with VB.Net because i'm not able to use correctly C#.. :)
It works great and it's the things i need to understand how simconnect works !
I wait for the other parts
Good Job
Post a Comment
<< Home