Michael's Random Technology Posts
Everything about my daily life as a programmer/Electrical Engineer!
Switched to engrstephens.blogspot.com
I have reposted all these blogs to my new blog!
Silverlight 4 Communicating with Matlab
I have taken it on myself to try and expand and inspire people’s view of what a web application can do. In this installment I’m leveraging Silverlight 4 and Matlab to build a simple Matlab console.
Again I am using COM automation to communicate with Silverlight. Why would anyone want to do this? Well, I’m slowly porting some Matlab code to c# so it is nice to check my code by communicating with Matlab. Matlab also has an insane amount of its own functions so if you wanted to build a neural network but use Silverlight to display and distribute your code then Silverlight 4 is for you!
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
namespace SLMatlab
{
public partial class MainPage : UserControl
{
dynamic matlab;
public MainPage()
{
InitializeComponent();
input.IsEnabled = false;
output.IsEnabled = false;
Install.Visibility = Visibility.Collapsed;
if (Application.Current.InstallState != InstallState.Installed)
{
MessageBox.Show("To run this, this application must be installed, Please click install.");
Install.Visibility = Visibility.Visible;
Connect.IsEnabled = false;
}
else if (!Application.Current.IsRunningOutOfBrowser)
{
MessageBox.Show("This application is installed but is running inside the browser. Please launch this from the desktop!");
Connect.IsEnabled = false;
}
}
private void Connect_Click(object sender, RoutedEventArgs e)
{
try
{
matlab = ComAutomationFactory.CreateObject("Matlab.Application");
matlab.Visible = 0;
input.IsEnabled = true;
output.IsEnabled = true;
}
catch { }
}
private void input_KeyDown(object sender, KeyEventArgs e)
{
try
{
if (e.Key == Key.Enter)
{
dynamic result = matlab.Execute(input.Text);
input.Text = "";
output.Text = result.ToString() + Environment.NewLine + output.Text;
}
}
catch { }
}
private void Install_Click(object sender, RoutedEventArgs e)
{
App.Current.Install();
}
}
}
<UserControl x:Class="SLMatlab.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Button Name="Install" Content="Install" Grid.Row="0" Click="Install_Click"></Button>
<Button Name="Connect" Content="Connect" Grid.Row="1" Click="Connect_Click"></Button>
<TextBox Name="input" Grid.Row="2" KeyDown="input_KeyDown"></TextBox>
<TextBox Name="output" Grid.Row="3"></TextBox>
</Grid>
</UserControl>
Twitter->SL4->Arduino Light Controller
This is a preliminary post about a new project I just finished (a variation of CoffeeTrack). I wanted to test the new capabilities of Silverlight 4 and push the boundaries of what people think of as a web application. I made a point to not have a server portion of my application. This application only requires SL4 and a COM library (trying to avoid).
I wanted to be able to control my lights from the internet. This system takes twitter posts (now possible in SL4) and interprets them as commands and commands the Arduino via COM automation. Once a command is received the web cam snaps a photo and posts that to twitter to confirm the results.
The Arduino code is DEAD simple. The Arduino is linked to a wireless light controller found at Walmart for $10. I found one online that is similar. I used relays to close the switches.
const int ledPin1 = 13; // the pin that the LED is attached to
const int ledPin2 = 12; // the pin that the LED is attached to
int incomingByte; // a variable to read incoming serial data into
void setup() {
// initialize serial communication:
Serial.begin(9600);
// initialize the LED pin as an output:
pinMode(ledPin1, OUTPUT);
pinMode(ledPin2, OUTPUT);
}
void loop() {
// see if there's incoming serial data:
if (Serial.available() > 0)
{
// read the oldest byte in the serial buffer:
incomingByte = Serial.read();
Serial.println((char)incomingByte);
if (incomingByte == '0') {
digitalWrite(ledPin1, HIGH);
delay(1000);
digitalWrite(ledPin1, LOW);
Serial.println("Lights are off");
}
else if (incomingByte == '1') {
digitalWrite(ledPin2, HIGH);
delay(1000);
digitalWrite(ledPin2, LOW);
Serial.println("Lights are on!");
}
}
}
The Silverlight 4 code is a combination of many, many different libraries. I was able to successfully talk to twitter as long as I ran as an out of browser application (dumb client access policy). I used the FJCore library to encode images to Jpeg. Special thanks to VisiFire for some helpful code using FJCore. I spent about 3 days working with the code from this post (which works in a Console App). Once I figured out how the encodings worked I was able to update a photo on twitter via Silverlight. The rest of the code I stole from CoffeeTrack (stay tuned!).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Xml.Linq;
using System.Text;
using System.Threading;
using System.Windows.Threading;
using System.Net.Browser;
using System.Windows.Media.Imaging;
using FluxJpeg.Core.Encoder;
using FluxJpeg.Core;
namespace XmasLightController
{
public partial class MainPage : UserControl
{
DispatcherTimer t = new DispatcherTimer() { Interval = new TimeSpan(0, 1, 0) };
byte[] currentImage = null;
string value = "";
public MainPage()
{
InitializeComponent();
t.Tick += new EventHandler(t_Tick);
//t.Start();
MakeRequest();
//UpdateTwitterImage();
}
void t_Tick(object sender, EventArgs e)
{
MakeRequest();
}
private void MakeRequest()
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://twitter.com/statuses/user_timeline/uwstephens.atom?&count=1"));
request.BeginGetResponse(new AsyncCallback(ReadCallback), request);
WriteText("Contacting twitter");
}
int currentcommand = 0;
private void ReadCallback(IAsyncResult asynchronousResult)
{
this.Dispatcher.BeginInvoke(delegate()
{
t.Stop();
});
WriteText("Recieved response from twitter");
HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
using (StreamReader streamReader1 = new StreamReader(response.GetResponseStream()))
{
string resultString = streamReader1.ReadToEnd();
XDocument doc = XDocument.Parse(resultString, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
List<XElement> elems = doc.Elements("entry").ToList();
StringBuilder sb = new StringBuilder();
int oldcommand = currentcommand;
foreach (var a in doc.Descendants().ToList())
{
if (a.Name.LocalName == "entry")
{
if(value!=a.Value)
{
UpdateTwitterImage();
}
value=a.Value;
if (a.Value.ToLower().Contains("action"))
{
currentcommand = 1;
}
else if (a.Value.ToLower().Contains("cut"))
{
currentcommand = 2;
}
else if (a.Value.ToLower().Contains("snap"))
{
currentcommand = 3;
}
}
}
this.Dispatcher.BeginInvoke(delegate()
{
if (currentcommand == 1)
{
WriteText("Start command received");
input.Text = "1";
SendMessage();
UpdateTwitterImage();
}
else if (currentcommand == 2)
{
WriteText("Stop command received");
input.Text = "0";
SendMessage();
UpdateTwitterImage();
}
});
this.Dispatcher.BeginInvoke(delegate()
{
t.Start();
});
}
}
public void WriteText(string txt)
{
this.Dispatcher.BeginInvoke(delegate() { this.outputWindow.Text += Environment.NewLine + txt; });
}
public void UpdateTwitterImage()
{
WriteText("Updating image");
if (currentImage == null) { return; }
String avatarUrl = "http://twitter.com/account/update_profile_image.xml";
String file = "xmas";
string imageType = "png";
WebRequest.RegisterPrefix("http://", System.Net.Browser.WebRequestCreator.ClientHttp);
string contentBoundaryBase = DateTime.Now.Ticks.ToString("x");
string beginContentBoundary = string.Format("--{0}\r\n", contentBoundaryBase);
var contentDisposition = string.Format("Content-Disposition:form-data); name=\"image\"); filename=\"{0}\"\r\nContent-Type: image/{1}\r\n\r\n", file, imageType);
var endContentBoundary = string.Format("\r\n--{0}--\r\n", contentBoundaryBase);
byte[] fileBytes = null;
Encoding encoding = Encoding.UTF8;
MemoryStream test = new MemoryStream();
byte[] data = encoding.GetBytes(beginContentBoundary);
test.Write(data, 0, data.Length);
data = encoding.GetBytes(contentDisposition);
test.Write(data, 0, data.Length);
data = currentImage;
test.Write(data, 0, data.Length);
data = encoding.GetBytes(endContentBoundary);
test.Write(data, 0, data.Length);
fileBytes = test.GetBuffer();
var req = (HttpWebRequest)HttpWebRequest.Create(new Uri(avatarUrl, UriKind.Absolute));
req.ContentType = "multipart/form-data;boundary=" + contentBoundaryBase;
req.AllowReadStreamBuffering = true;
req.Method = "POST";
req.UseDefaultCredentials = false;
req.Credentials = new NetworkCredential("uwstephens", "-");
File.WriteAllBytes(@"C:\Users\michael\Documents\test.txt", fileBytes);
req.BeginGetRequestStream(delegate(IAsyncResult result)
{
Stream reqStream = req.EndGetRequestStream(result);
reqStream.Write(fileBytes, 0, fileBytes.Length);
//reqStream.wr(fileBytes, 0, fileBytes.Length);
reqStream.Close();
req.BeginGetResponse(delegate(IAsyncResult result1)
{
WriteText("Image updated");
if (req.HaveResponse)
{
try
{
WebResponse resp = req.EndGetResponse(result1);
WriteText(resp.Headers["status"]);
}
catch (Exception e) { WriteText(e.Message); }
}
}, req);
}, null);
}
VideoCaptureDevice webcam = null;
CaptureSource captureSource = null;
private void loadCamera_Click(object sender, RoutedEventArgs e)
{
webcam = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
if (CaptureDeviceConfiguration.RequestDeviceAccess())
{
captureSource = new CaptureSource();
captureSource.VideoCaptureDevice = webcam;
captureSource.Start();
captureSource.AsyncCaptureImage(snap);
}
}
public void snap(WriteableBitmap b)
{
currentImage = GetImageStream(b).GetBuffer();
webcamImg.Source = b;
File.WriteAllBytes(@"C:\Users\michael\Documents\test\test.png", currentImage);
captureSource.AsyncCaptureImage(snap);
}
/// <summary>
/// Reads raster information from WriteableBitmap
/// </summary>
/// <param name="bitmap">WriteableBitmap</param>
/// <returns>Array of bytes</returns>
public static byte[][,] ReadRasterInformation(WriteableBitmap bitmap)
{
int width = bitmap.PixelWidth;
int height = bitmap.PixelHeight;
int bands = 3;
byte[][,] raster = new byte[bands][,];
for (int i = 0; i < bands; i++)
{
raster[i] = new byte[width, height];
}
for (int row = 0; row < height; row++)
{
for (int column = 0; column < width; column++)
{
int pixel = bitmap.Pixels[width * row + column];
raster[0][column, row] = (byte)(pixel >> 16);
raster[1][column, row] = (byte)(pixel >> 8);
raster[2][column, row] = (byte)pixel;
}
}
return raster;
}
/// <summary>
/// Encode raster information to MemoryStream
/// </summary>
/// <param name="raster">Raster information (Array of bytes)</param>
/// <param name="colorSpace">ColorSpace used</param>
/// <returns>MemoryStream</returns>
public static MemoryStream EncodeRasterInformationToStream(byte[][,] raster, ColorSpace colorSpace)
{
ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);
//Encode the Image as a JPEG
MemoryStream stream = new MemoryStream();
FluxJpeg.Core.Encoder.JpegEncoder encoder = new FluxJpeg.Core.Encoder.JpegEncoder(img, 100, stream);
encoder.Encode();
// Back to the start
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
/// <summary>
/// Get image MemoryStream from WriteableBitmap
/// </summary>
/// <param name="bitmap">WriteableBitmap</param>
/// <returns>MemoryStream</returns>
public static MemoryStream GetImageStream(WriteableBitmap bitmap)
{
byte[][,] raster = ReadRasterInformation(bitmap);
return EncodeRasterInformationToStream(raster, ColorSpace.RGB);
}
dynamic com;
private void serialConnect_Click(object sender, RoutedEventArgs e)
{
com = System.Windows.Interop.ComAutomationFactory.CreateObject("ActiveXperts.ComPort");
dynamic count = com.GetDeviceCount();
StringBuilder sb = new StringBuilder();
com.Device = "COM1";
com.Open();
MessageBox.Show(com.GetErrorDescription(com.LastError));
string buffer = "";
System.Threading.Thread t = new Thread(new ThreadStart(delegate()
{
while (1 == 1)
{
com.Sleep(200);
buffer = com.ReadString();
if (buffer == "") { continue; }
serialOutput.Dispatcher.BeginInvoke(delegate()
{
serialOutput.Text += "\r\n" + buffer;
});
}
}));
t.Start();
}
void SendMessage()
{
if (com != null)
{
foreach (char c in input.Text)
{
com.WriteByte((byte)c);
}
input.Text = "";
}
}
private void input_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
SendMessage();
}
}
}
}
Silverlight talking to Arduino
This is an initial rough post, but I have managed to get Silverlight 4 beta talking to an ActiveXperts COM+ interface that talks to an Arduino over a serial connection.
Here is my first attempt to do a video of it. Looks like I’m going to need to learn how to make a screencast.
The C# code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interop;
using System.Text;
using System.Threading;
namespace TestSerial
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if (App.Current.InstallState == InstallState.NotInstalled)
{
App.Current.Install();
}
}
dynamic com;
private void button2_Click(object sender, RoutedEventArgs e)
{
com=ComAutomationFactory.CreateObject("ActiveXperts.ComPort");
dynamic count = com.GetDeviceCount();
StringBuilder sb = new StringBuilder();
List<dynamic> devices = new List<dynamic>();
for(int i=1;i<=9;i++)
{
devices.Add("COM" + i);
}
for (int i = 0; i < count; i++)
{
devices.Add(com.GetDevice(i));
}
devicelst.ItemsSource = devices;
}
private void button3_Click(object sender, RoutedEventArgs e)
{
//string device = devicelst.SelectedItem.ToString();
if (devicelst.SelectedItem == null) { MessageBox.Show("Please pick a port"); return; }
com.Device = devicelst.SelectedItem.ToString();
com.Open();
MessageBox.Show(com.GetErrorDescription(com.LastError));
string buffer = "";
System.Threading.Thread t = new Thread(new ThreadStart(delegate()
{
while (1 == 1)
{
com.Sleep(200);
buffer = com.ReadString();
if (buffer == "") { com.Close(); return; }
tb.Dispatcher.BeginInvoke(delegate()
{
tb.Text += "\r\n" + com.ReadString();
});
}
}));
t.Start();
}
}
}
The Xaml.
<UserControl x:Class="TestSerial.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Content="Install" Height="23" HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" Grid.Row="0"/>
<Button Content="Get Devices" Height="23" HorizontalAlignment="Left" Name="button2" VerticalAlignment="Top" Width="75" Grid.Row="1" Click="button2_Click"/>
<ComboBox Name="devicelst" Grid.Row="1" Margin="1,0,0,0" Grid.Column="1"></ComboBox>
<Button Content="Connect" Grid.Column="3" Grid.Row="1" Height="23" HorizontalAlignment="Left" Name="button3" VerticalAlignment="Top" Click="button3_Click"/>
<TextBox Name="tb" Grid.ColumnSpan="3" Grid.Row="2" VerticalScrollBarVisibility="Visible"></TextBox>
</Grid>
</UserControl>
/*
ASCII table
Prints out byte values in all possible formats:
* as raw binary values
* as ASCII-encoded decimal, hex, octal, and binary values
For more on ASCII, see http://www.asciitable.com and http://en.wikipedia.org/wiki/ASCII
The circuit: No external hardware needed.
created 2006
by Nicholas Zambetti
modified 18 Jan 2009
by Tom Igoe
<http://www.zambetti.com>
*/
void setup()
{
Serial.begin(9600);
// prints title with ending line break
Serial.println("ASCII Table ~ Character Map");
}
// first visible ASCIIcharacter '!' is number 33:
int thisByte = 33;
// you can also write ASCII characters in single quotes.
// for example. '!' is the same as 33, so you could also use this:
//int thisByte = '!';
void loop()
{
// prints value unaltered, i.e. the raw binary version of the
// byte. The serial monitor interprets all bytes as
// ASCII, so 33, the first number, will show up as '!'
Serial.print(thisByte, BYTE);
Serial.print(", dec: ");
// prints value as string as an ASCII-encoded decimal (base 10).
// Decimal is the default format for Serial.print() and Serial.println(),
// so no modifier is needed:
Serial.print(thisByte);
// But you can declare the modifier for decimal if you want to.
//this also works if you uncomment it:
// Serial.print(thisByte, DEC);
Serial.print(", hex: ");
// prints value as string in hexadecimal (base 16):
Serial.print(thisByte, HEX);
Serial.print(", oct: ");
// prints value as string in octal (base 8);
Serial.print(thisByte, OCT);
Serial.print(", bin: ");
// prints value as string in binary (base 2)
// also prints ending line break:
Serial.println(thisByte, BIN);
// if printed last visible character '~' or 126, stop:
if(thisByte == 126) { // you could also use if (thisByte == '~') {
// This loop loops forever and does nothing
while(true) {
continue;
}
}
// go on to the next character
thisByte++;
}
Entity framework Eager Loading error
If you you get this
"The execution of this query requires the APPLY operator, which is not supported in versions of SQL Server earlier than SQL Server 2005."
We ran into this because we generated it via a 2000 database and threw an error.
Load the edmx file in notepad.
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="TableModels.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2000" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
Now all we need to do is change the 2000 to 2005!
Keep an eye on a reference.
When debugging I often find myself wishing I could just watch a single object throughout its life cycle. This is especially useful for debugging state problems (I’m thinking detached row error). Someone is modifying the state of your object and you need to figure out who. The problem lies in scoping. Every time you step in you have to change the watch window statement to track your objects state.
What you end up with is a nasty watch window.
The immediate window comes to the rescue! Simply declare a variable with a globally unique name (DataRow watchMyRow=_dr;) for example. Then go to the watch window and add a watch for watchMyRow. watchMyRow is always in scope. So now step through at your leisure and find the offending entry point and step deeper. Its that easy!
So we will start with a simple test program that calls methods with several different names and types.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MyObject val = new MyObject();
Method1(val);
Method2(val);
Method3(val);
}
static void Method1(MyObject obj)
{
obj.state = 1;
}
static void Method2(MyObject myobj)
{
myobj.state = 2;
}
static void Method3(object obj)
{
((MyObject)obj).state = 3;
}
}
public class MyObject
{
public int state = 5;
}
}
We set our break points.
And at the end of the cycle our watch window looks like this!
All to track the same value on the same object!
The real problem is that with events thrown into the mix it becomes impossible to track state on some foreign object (Detached Row I loathe you!).
Now lets do the same example with the immediate window.
Our first break point gets hit. We type MyObject WATCHMYOBJECT=obj; into the immediate window.
Next I add that variable to the watch window.
Finnally after the many function calls this is the result.
I only had to watch one object the entire time! Now if I can only build a VSADDIN to automate this process (evil grin)!
Silverlight WCF Inner-Exceptions
Found a quick and dirty way to do WCF exceptions in silverlight http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=silverlightws&DownloadId=3473 . The problem is that it doesn’t do inner-exceptions.
I changed the raw fault exception around a bit to get inner exceptions working.
public static RawFaultException BuildRawFaultException(XmlDictionaryReader reader)
{
List<string> messages = new List<string>();
List<string> stacktraces = new List<string>();
while (reader.ReadToFollowing("Message"))
{
string message = reader.ReadElementContentAsString();
string stackTrace = reader.ReadElementContentAsString("StackTrace", reader.NamespaceURI);
messages.Add(message);
stacktraces.Add(stackTrace);
}
RawFaultException e = null;
for (int i = 0; i < messages.Count; i++)
{
if (e == null) { e = new RawFaultException(messages[i]) { stackTrace = stacktraces[i] }; }
else { e = new RawFaultException(messages[i], e) { stackTrace = stacktraces[i] }; }
}
return e;
}
}
Flag enums
I was working on a project that needed to use flag enums to store state. Flag enums are great, except after 2^15 the numbers get hard to calculate. I resorted to using excel to generate the enum code for me, that is until I learned I can do math expressions inside of enums! What I forgot is that 2^0 is not equivalent to Math.Pow(2,0). This is when I discovered my old friend shift.
using System;
namespace TestEnum
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(oldway.All);
Console.WriteLine(newway.All);
}
}
[Flags]
enum oldway
{
None=0,
One=1,
Two=2,
Three=4,
Four=8,
All=15
}
[Flags]
enum newway
{
None = 0,
One = 1<<0,
Two = 1<<1,
Three = 1<<2,
Four = 1<<3,
All = (1<<4) - 1
}
}
As you can see they both work the same :)
State Bag for all objects
Extension methods are very powerful, but sometimes you need to store state on the object you are processing in an extension method. The problem is implementing state maintenance in a static method can be a real chore. Not to mention when you only want to track state on an object you can’t. I wrote this quick and dirty state maintenance set of extension methods. There is a caveat that I cannot fix at the time of writing.
If you store data in the statebag, you need someway of knowing when the object is getting destroyed so that you can manually call ClearVariables. If you do not clear the variables the values will be left in the statebag FOREVER causing massive memory leaks! I would like a way to alleviate this if anyone has any ideas.
using System;
using System.Collections.Generic;
namespace StateBagTest
{
class Program
{
static void Main(string[] args)
{
object o = new object();
o.Set("newvar", 5);
int number = (int)o.Get("newvar");
o.ClearVariables(); //very very important!
Console.ReadLine();
}
}
public static class StateBag
{
static Dictionary<int, Dictionary<string, object>> _stateBag = new Dictionary<int, Dictionary<string, object>>();
public static void Set(this object obj, string var, object value)
{
int hashcode = obj.GetHashCode();
if (!_stateBag.ContainsKey(hashcode))
{
_stateBag.Add(hashcode, new Dictionary<string, object>());
}
_stateBag[hashcode][var] = value;
}
public static object Get(this object obj, string var)
{
int hashcode = obj.GetHashCode();
if (_stateBag.ContainsKey(hashcode) && _stateBag[hashcode].ContainsKey(var))
{
return _stateBag[hashcode][var];
}
return null;
}
//Really need to call this when your object is done using the state bag, which may be hard for some objects you are extending!
public static void ClearVariables(this object obj)
{
int hashcode = obj.GetHashCode();
if (_stateBag.ContainsKey(hashcode))
{
_stateBag[hashcode] = null;
}
}
}
}
Mono.Cecil Profiler
So we have been trying to develop a profiler for a long time. All the posts seem to require an inordinate amount ofMSIL knowledge or to know C++ to interface with the CLR directly. Both of these approaches were painful. All I wanted to do was inject a Profile.Start and Profile.End into our code base for every class giving us a simple profiler. With Mono.Cecil I could do exactly that, and without Mono installed on my computer. Go get the original example from the Mono.Cecil website or just download it directly here. There is a dll called lib. I just added that to my solution in VS2008 and wrote the following simple profiler.
static void Main(string[] args)
{
string pathBin = @"C:\Users\michael\Documents\Visual Studio 2008\Projects\ConsoleApplication1\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe";
MethodInfo startMethod = typeof(ProfileClient).GetMethod("Start", new Type[] { typeof(string) });
MethodInfo endMethod =typeof(ProfileClient).GetMethod("End", new Type[] { typeof(string) });
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(pathBin);
foreach (TypeDefinition type in assembly.MainModule.Types)
{
if (type.Name != "<Module>")
{
foreach (MethodDefinition method in type.Methods)
{
CilWorker worker = method.Body.CilWorker;
string sentence = method.Name;
MethodReference start=assembly.MainModule.Import(startMethod);
MethodReference end= assembly.MainModule.Import(endMethod);
//Creates the MSIL instruction for inserting the sentence
Instruction startSentence = worker.Create(OpCodes.Ldstr, sentence);
Instruction endSentence= worker.Create(OpCodes.Ldstr, sentence);
List<Instruction> parameters = new List<Instruction>();
//Creates the CIL instruction for calling the start and end
Instruction callStart= worker.Create(OpCodes.Call, start);
Instruction callEnd= worker.Create(OpCodes.Call, end);
//-2 cause you need to go before the return opcode
Instruction lastinstruction= method.Body.Instructions[method.Body.Instructions.Count-2];
worker.InsertAfter(lastinstruction, endSentence);
worker.InsertAfter(endSentence, callEnd);
Instruction firstinstruction = method.Body.Instructions[0];
worker.InsertBefore(firstinstruction, startSentence);
worker.InsertAfter(startSentence, callStart);
}
}
}
AssemblyFactory.SaveAssembly(assembly, pathBin.Replace(".exe","")+"-new-.exe");
}
public class ProfileClient
{
public static void Start(string method,params object[] args)
{
Console.WriteLine("Start " + method+">");
}
public static void End(string method, params object[] args)
{
Console.WriteLine(">End "+ method);
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("My Code2 ");
}
}
So if you build the last program and run it, it will output
My Code2
Once you run the second console application and then run ConsoleApplication2-new-.exe you will get
Start Main>
My Code2 ?
>End Main
I’m still not sure where the ? comes from but for now this simple profiler works and will be the basis of our more complex profiler we are planning to start constructing.
Remove SVN
There is probably an easier way to do this. I accidentally tried to add a large directory to a svn repo. It failed after 3 hours leaving half my directory with .svn folders. Since Tortoise SVN didn't seem to have an option to unbind a folder I wrote this powershell script.
function removesvn
{
dir -path $pwd.Path -recurse -force | where{ $_.Name -eq ".svn"} | rm -recurse -force
}
removesvn
Convert Bitmaps to transparent png
Not much to say. This was really useful with an icon set we were using.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
string directory=@"c:\temp\";
string[] files = Directory.GetFiles(directory, "*.bmp");
foreach (string path in files)
{
Bitmap bmp = new Bitmap(path);
bmp.MakeTransparent(Color.Magenta);
FileInfo fi = new FileInfo(path);
bmp.Save(directory + fi.Name.Replace(".bmp", "") + ".png", ImageFormat.Png);
bmp.Dispose();
}
}
}
}
How Asp.Net Requests Page
As our company delved into asynchronous transfers we noticed that we would get a random application hang every now and then. We suspected and tried to hunt down a threading problem and along the way we learned some interesting things.
First: Asp.Net queues requests by session to prevent this problem. Here is our test. First we built a new Page Class with some diagnostic info.
namespace WebApplication3
{
public class TestPage : Page
{
protected override void OnLoad(EventArgs e)
{
Debug.Print("strt\t" + this.GetType().Name + "Load" + " " +this.GetHashCode());
base.OnLoad(e);
Debug.Print(" stop\t" + this.GetType().Name + "Load"+ " " +this.GetHashCode());
}
protected override void OnPreRender(EventArgs e)
{
Debug.Print(" strt\t" + this.GetType().Name + "PreR"+ " " +this.GetHashCode());
base.OnPreRender(e);
Debug.Print(" stop\t" + this.GetType().Name + "PreR"+ " " +this.GetHashCode());
}
}
}
Then I setup the Default Page to add iframes to the source.
public partial class _Default : TestPage
{
protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep(1000);
for (int i = 0; i < 20; i++)
{
HtmlGenericControl control = new HtmlGenericControl("iframe");
control.Attributes.Add("src", "WebForm1.aspx");
this.Form.Controls.Add(control);
}
}
}
Some notes here. The Thread.Sleep makes sure that the debugger is attached and responding well enough to record things correctly.
Finally WebForm1.aspx. This thing doesnt do anything but inherit from the TestPage with diagnostic information.
namespace WebApplication3
{
public partial class WebForm1 : TestPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
Ok so the first run we make sure EnableSessionState="True"
What this is supposed to do is make sure that only one request happens at a time. Here is the log.
strt default_aspxLoad 53576792
stop default_aspxLoad 53576792
strt default_aspxPreR 53576792
stop default_aspxPreR 53576792
strt webform1_aspxLoad 46058179
strt webform1_aspxLoad 63986957
stop webform1_aspxLoad 46058179
strt webform1_aspxPreR 46058179
stop webform1_aspxPreR 46058179
stop webform1_aspxLoad 63986957
strt webform1_aspxPreR 63986957
stop webform1_aspxPreR 63986957
strt webform1_aspxLoad 58678956
stop webform1_aspxLoad 58678956
strt webform1_aspxPreR 58678956
stop webform1_aspxPreR 58678956
strt webform1_aspxLoad 49300528
stop webform1_aspxLoad 49300528
strt webform1_aspxPreR 49300528
stop webform1_aspxPreR 49300528
strt webform1_aspxLoad 8423152
stop webform1_aspxLoad 8423152
strt webform1_aspxPreR 8423152
stop webform1_aspxPreR 8423152
strt webform1_aspxLoad 32996321
stop webform1_aspxLoad 32996321
strt webform1_aspxPreR 32996321
stop webform1_aspxPreR 32996321
strt webform1_aspxLoad 28848020
stop webform1_aspxLoad 28848020
strt webform1_aspxPreR 28848020
stop webform1_aspxPreR 28848020
strt webform1_aspxLoad 66504299
stop webform1_aspxLoad 66504299
strt webform1_aspxPreR 66504299
stop webform1_aspxPreR 66504299
strt webform1_aspxLoad 47123389
stop webform1_aspxLoad 47123389
strt webform1_aspxPreR 47123389
stop webform1_aspxPreR 47123389
strt webform1_aspxLoad 60889463
stop webform1_aspxLoad 60889463
strt webform1_aspxPreR 60889463
stop webform1_aspxPreR 60889463
strt webform1_aspxLoad 16515137
stop webform1_aspxLoad 16515137
strt webform1_aspxPreR 16515137
stop webform1_aspxPreR 16515137
strt webform1_aspxLoad 10378086
stop webform1_aspxLoad 10378086
strt webform1_aspxPreR 10378086
stop webform1_aspxPreR 10378086
strt webform1_aspxLoad 58791803
stop webform1_aspxLoad 58791803
strt webform1_aspxPreR 58791803
stop webform1_aspxPreR 58791803
strt webform1_aspxLoad 45784774
stop webform1_aspxLoad 45784774
strt webform1_aspxPreR 45784774
stop webform1_aspxPreR 45784774
strt webform1_aspxLoad 31806433
stop webform1_aspxLoad 31806433
strt webform1_aspxPreR 31806433
stop webform1_aspxPreR 31806433
strt webform1_aspxLoad 17398656
stop webform1_aspxLoad 17398656
strt webform1_aspxPreR 17398656
stop webform1_aspxPreR 17398656
strt webform1_aspxLoad 62912648
stop webform1_aspxLoad 62912648
strt webform1_aspxPreR 62912648
stop webform1_aspxPreR 62912648
strt webform1_aspxLoad 50833736
stop webform1_aspxLoad 50833736
strt webform1_aspxPreR 50833736
stop webform1_aspxPreR 50833736
strt webform1_aspxLoad 46154167
stop webform1_aspxLoad 46154167
strt webform1_aspxPreR 46154167
stop webform1_aspxPreR 46154167
strt webform1_aspxLoad 63078365
stop webform1_aspxLoad 63078365
strt webform1_aspxPreR 63078365
stop webform1_aspxPreR 63078365
So the first request does not seem to be blocking additional requests as seen in red. Whoops! We didn't use Session so asp.net hasn't made us a session (this is retarded I know).
So we made a change to TestPage.
public class TestPage : Page
{
protected override void OnLoad(EventArgs e)
{
Session["dumb"] = true;
Debug.Print("strt\t" + this.GetType().Name + "Load" + " " +this.GetHashCode());
base.OnLoad(e);
Debug.Print(" stop\t" + this.GetType().Name + "Load"+ " " +this.GetHashCode());
}
protected override void OnPreRender(EventArgs e)
{
Debug.Print(" strt\t" + this.GetType().Name + "PreR"+ " " +this.GetHashCode());
base.OnPreRender(e);
Debug.Print(" stop\t" + this.GetType().Name + "PreR"+ " " +this.GetHashCode());
}
}
}
See now we set ["Dumb"]=true.
Ok so here is the next trace with that fix.
strt default_aspxLoad 6100737
stop default_aspxLoad 6100737
strt default_aspxPreR 6100737
stop default_aspxPreR 6100737
strt webform1_aspxLoad 6299240
stop webform1_aspxLoad 6299240
strt webform1_aspxPreR 6299240
stop webform1_aspxPreR 6299240
strt webform1_aspxLoad 50224890
stop webform1_aspxLoad 50224890
strt webform1_aspxPreR 50224890
stop webform1_aspxPreR 50224890
strt webform1_aspxLoad 51844981
stop webform1_aspxLoad 51844981
strt webform1_aspxPreR 51844981
stop webform1_aspxPreR 51844981
strt webform1_aspxLoad 54842390
stop webform1_aspxLoad 54842390
strt webform1_aspxPreR 54842390
stop webform1_aspxPreR 54842390
strt webform1_aspxLoad 44186085
stop webform1_aspxLoad 44186085
strt webform1_aspxPreR 44186085
stop webform1_aspxPreR 44186085
strt webform1_aspxLoad 307537
stop webform1_aspxLoad 307537
strt webform1_aspxPreR 307537
stop webform1_aspxPreR 307537
strt webform1_aspxLoad 36642531
stop webform1_aspxLoad 36642531
strt webform1_aspxPreR 36642531
stop webform1_aspxPreR 36642531
strt webform1_aspxLoad 8761922
stop webform1_aspxLoad 8761922
strt webform1_aspxPreR 8761922
stop webform1_aspxPreR 8761922
strt webform1_aspxLoad 44283204
stop webform1_aspxLoad 44283204
strt webform1_aspxPreR 44283204
stop webform1_aspxPreR 44283204
strt webform1_aspxLoad 37119060
stop webform1_aspxLoad 37119060
strt webform1_aspxPreR 37119060
stop webform1_aspxPreR 37119060
strt webform1_aspxLoad 46442220
stop webform1_aspxLoad 46442220
strt webform1_aspxPreR 46442220
stop webform1_aspxPreR 46442220
strt webform1_aspxLoad 51538364
stop webform1_aspxLoad 51538364
strt webform1_aspxPreR 51538364
stop webform1_aspxPreR 51538364
strt webform1_aspxLoad 65304700
stop webform1_aspxLoad 65304700
strt webform1_aspxPreR 65304700
stop webform1_aspxPreR 65304700
strt webform1_aspxLoad 45154067
stop webform1_aspxLoad 45154067
strt webform1_aspxPreR 45154067
stop webform1_aspxPreR 45154067
strt webform1_aspxLoad 24313979
stop webform1_aspxLoad 24313979
strt webform1_aspxPreR 24313979
stop webform1_aspxPreR 24313979
strt webform1_aspxLoad 30823158
stop webform1_aspxLoad 30823158
strt webform1_aspxPreR 30823158
stop webform1_aspxPreR 30823158
strt webform1_aspxLoad 32261192
stop webform1_aspxLoad 32261192
strt webform1_aspxPreR 32261192
stop webform1_aspxPreR 32261192
strt webform1_aspxLoad 5695327
stop webform1_aspxLoad 5695327
strt webform1_aspxPreR 5695327
stop webform1_aspxPreR 5695327
strt webform1_aspxLoad 14547257
stop webform1_aspxLoad 14547257
strt webform1_aspxPreR 14547257
stop webform1_aspxPreR 14547257
strt webform1_aspxLoad 65008279
stop webform1_aspxLoad 65008279
strt webform1_aspxPreR 65008279
stop webform1_aspxPreR 65008279
EnableSessionState="ReadOnly"
Here is our log we can see that the first 2 requests to webform1 are made at the same time causing a thread exception.
strt default_aspxLoad 22177797
stop default_aspxLoad 22177797
strt default_aspxPreR 22177797
stop default_aspxPreR 22177797
strt webform1_aspxLoad 58758044
strt webform1_aspxLoad 60402358
stop webform1_aspxLoad 60402358
stop webform1_aspxLoad 58758044
strt webform1_aspxPreR 60402358
strt webform1_aspxPreR 58758044
stop webform1_aspxPreR 60402358
stop webform1_aspxPreR 58758044
strt webform1_aspxLoad 33970112
stop webform1_aspxLoad 33970112
strt webform1_aspxPreR 33970112
stop webform1_aspxPreR 33970112
strt webform1_aspxLoad 12544008
stop webform1_aspxLoad 12544008
strt webform1_aspxPreR 12544008
stop webform1_aspxPreR 12544008
strt webform1_aspxLoad 47231162
stop webform1_aspxLoad 47231162
strt webform1_aspxPreR 47231162
stop webform1_aspxPreR 47231162
strt webform1_aspxLoad 63638306
stop webform1_aspxLoad 63638306
strt webform1_aspxPreR 63638306
stop webform1_aspxPreR 63638306
strt webform1_aspxLoad 57701674
stop webform1_aspxLoad 57701674
strt webform1_aspxPreR 57701674
stop webform1_aspxPreR 57701674
strt webform1_aspxLoad 29746117
stop webform1_aspxLoad 29746117
strt webform1_aspxPreR 29746117
stop webform1_aspxPreR 29746117
strt webform1_aspxLoad 3884878
stop webform1_aspxLoad 3884878
strt webform1_aspxPreR 3884878
stop webform1_aspxPreR 3884878
strt webform1_aspxLoad 45476154
stop webform1_aspxLoad 45476154
strt webform1_aspxPreR 45476154
stop webform1_aspxPreR 45476154
strt webform1_aspxLoad 42317455
stop webform1_aspxLoad 42317455
strt webform1_aspxPreR 42317455
stop webform1_aspxPreR 42317455
strt webform1_aspxLoad 60918874
stop webform1_aspxLoad 60918874
strt webform1_aspxPreR 60918874
stop webform1_aspxPreR 60918874
strt webform1_aspxLoad 54806812
stop webform1_aspxLoad 54806812
strt webform1_aspxPreR 54806812
stop webform1_aspxPreR 54806812
strt webform1_aspxLoad 59055457
stop webform1_aspxLoad 59055457
strt webform1_aspxPreR 59055457
stop webform1_aspxPreR 59055457
strt webform1_aspxLoad 64646201
stop webform1_aspxLoad 64646201
strt webform1_aspxPreR 64646201
stop webform1_aspxPreR 64646201
strt webform1_aspxLoad 62849758
stop webform1_aspxLoad 62849758
strt webform1_aspxPreR 62849758
stop webform1_aspxPreR 62849758
strt webform1_aspxLoad 26235852
stop webform1_aspxLoad 26235852
strt webform1_aspxPreR 26235852
strt webform1_aspxLoad 2135755
stop webform1_aspxLoad 2135755
stop webform1_aspxPreR 26235852
strt webform1_aspxPreR 2135755
stop webform1_aspxPreR 2135755
strt webform1_aspxLoad 6565686
stop webform1_aspxLoad 6565686
strt webform1_aspxPreR 6565686
stop webform1_aspxPreR 6565686
strt webform1_aspxLoad 8372655
stop webform1_aspxLoad 8372655
strt webform1_aspxPreR 8372655
stop webform1_aspxPreR 8372655
So in summary asp.net only makes one request at a time as long as you initialize the session, set the EnableSessionState=True (which is the default). So if you think Asp.Net is causing threading issues think again.