Category Archives: Professional
Timer Finetuning, Each Microsecond Counts
This is a one day excursus from my present chart posts, which will continue with my next post.
Exact timing is not an issue for most of us. Who cares if something gets executed a few millisecond earlier or later. Well, this blog was started to highlight many issues of “hard-core” programming. Thus I do care!
Back in 1985, when I was hacking on my Commodore 64, there was no such as Context Switching. To wait for a few moments you could run a loop for a number of times. And when you were running the same loop on the same C64 model again, then you had pretty much the same passed amount of time. The emphasis in on the word “same”. But these days don’t work like that. You never face the “same” situation again. Since the invention of multithreading (in fact it was called multitasking on the good old Commodore Amiga) context switching came into place.
(There are Multimedia Timers in the windows core environment. I am not going to use these. High precision inside the standard .Net framework is possible. There is no need to leave robustness behind.)
I was running several small tests to get a feeling for the .Net timing. The first and most simple approach was using PriorityBoostEnabled on the process to boost the process priority. Unfortunately its use is limited. Your priority only gets boosted, when your main window has the focus. And when your process is on a high priority anyway, it obviously won’t change a lot.
Therefore the next step was to increase the process priority to ‘ProcessPriorityClass.RealTime’. This is a dangerous setting and should only be used by skilled programmers. Your application could consume too much privileged time at the expense of the entire system, which would result in a slowed down execution time for ALL processes and services – including your process that you were originally trying to speed-up.
One important aspect is the PrivilegedProcessorTime. We’ll assume 80 milliseconds, just to have a realistic number. This would imply that the windows system was giving your process 80 ms to execute code before it switched to another process. You have to share this order of magnitude amongst all your threads. 10 ms of these are consumed by your GUI update. Make sure to execute any time sensitive code in one piece before the next context switching takes place.
As we talk about timers, it does not help you at all to be precise on one side, but then lose processor execution time to another thread or process.
Let me come up with a short story. You take the train at exactly 1 pm after waiting for 8 hours. You only have 10 minutes left to get from Austria to Belgium. That sucks, right? You obviously have to start your journey without waiting 8 hours beforehand.
The hard-core scenario would be to get changed, pack your suitcase and sleep until 12:45pm … yes, in your business suit! Then suddenly wake up, jump out of your bed, don’t kiss your wife, run to the station and use the full 8 hours for your journey. You only make it, when you arrive in Brussels before the ticket collector kicks you out, because your ticket was only valid for 8 hours.
Let’s delve into practice. In the below example you can see that getting the time-stamp does take about half a tick. When you run a long loop and try to get the time-stamp many times, then it seems to slow down. The reason for this is the context switching. Somewhere in the middle the PrivilegedProcessorTime expired. Therefore running only a few loops can show better results.
The system timer is terribly slow. Its delays and reliability are bad as bad can be. It apparently accepts a double for microseconds rather than the usual long. You would expect a higher resolution then, wouldn’t you?
Especially the first run is unpredictable. The reason for this are the CLR runtime compilation operations. Your code gets compiled shortly before the first execution. You will observe this with any timer. Start your code earlier and skip the first callback. This improves the precision for the first callback you are really waiting for.
The thread timer is more precise, but is firing too early sometimes. Its precision is above one millisecond.
The timer with the most potential isn’t really a timer. It is a thread sleep method in a loop. In my opinion that is the most advanced solution to solve the timer precision problem. The loop is running on a thread. This thread is not shared like on a task scheduler. You own that thread and nobody else will use it. You obviously should not run hundreds of threads. Run 5 of them, and you are still in the green zone. The big advantage is that you can change the priority of this thread to ‘Highest’. This gives you the attention that real geeks need. Furthermore, you won’t have any multiple code executions. There is one event running at a time. If you miss one, because the previous event was running too long, then you can still decide to not execute the next run. A general system delay would then not queue up events that you don’t want to execute anymore … as it is too late anyway. You obviously can add this feature for any timer, but this one is the safest way to easily have only one event running at a time.
Check out my MicroTimer class. It waits a little bit less than required and then calls SpinWait multiple times. Once the process thread gets processor time assigned, you most likely won’t face context switching. You wait like a disciple for the one and only messiah, running around a rigid pole.
The MicroTimer class should give you a whopping precision of 1 microsecond …. unless my usual ‘unless’ sentence states something else 😉
The example output shows the delay at the point when the timer was leaving the inner loop to raise an event. And it shows the delay at the time it recorded the data inside the event. Obviously there are some microseconds in between. Measure that time and adjust your schedule accordingly.
Computer Specs:
Lenovo Yoga 2 11”
Intel i5-4202Y 1.6 GHz processor
128GB SSD hard disk
4GB memory
Windows 8.1, 64 bit
Admittedly, the code is a bit of Spaghetti style today. Tests are really what they are. It was clear that the MicroTimer would win in the end. So my effort for a proper style went into that direction.
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; namespace TimerPrecisionTest { class Program { private const long _NumDataPoints = 10; private static List<long> _DataPoints = new List<long>(); private static int _CurrentDataPoint = 0; private static long[] _Schedule; private static long _PeriodInMs = 3 * Stopwatch.Frequency * 1000; private static long _PeriodInTicks = 3 * Stopwatch.Frequency; private static double _FrequencyAsDouble = (double)Stopwatch.Frequency; private static AutoResetEvent _AutoResetEvent = new AutoResetEvent(false); static void Main(string[] args) { Process p = Process.GetCurrentProcess(); p.PriorityBoostEnabled = true; // every little helps p.PriorityClass = ProcessPriorityClass.Normal; Console.WriteLine("Process with normal priority:"); Console.WriteLine("Priviledged processor time for process " + p.ProcessName + " is " + p.PrivilegedProcessorTime.TotalMilliseconds.ToString("#,##0.0") + " ms"); p.PriorityClass = ProcessPriorityClass.RealTime; Console.WriteLine("Process with high priority:"); Console.WriteLine("Priviledged processor time for process " + p.ProcessName + " is " + p.PrivilegedProcessorTime.TotalMilliseconds.ToString("#,##0.0") + " ms"); Console.WriteLine("IsHighResolution system clock: " + Stopwatch.IsHighResolution); Console.WriteLine("Number of ticks per second: " + Stopwatch.Frequency.ToString("#,##0")); long a = Stopwatch.GetTimestamp(); for (int i = 0; i < 100000; i++) { long b = Stopwatch.GetTimestamp(); } long c = Stopwatch.GetTimestamp(); Console.WriteLine("Number of ticks to obtain a timestamp: " + ((c - a) / 100000.0).ToString("#,##0.00")); Console.WriteLine(); UseSystemTimer(); UseThreadingTimer(); // a simple loop Thread lThread = new Thread(new ThreadStart(UseLoop)); lThread.Priority = ThreadPriority.Highest; lThread.IsBackground = true; lThread.Start(); _AutoResetEvent.WaitOne(); testSpinWaitPrecision(); // a proper loop UseMicroTimerClass(); Console.ReadLine(); } // #region MicroTimer private static void UseMicroTimerClass() { Console.WriteLine("MICRO TIMER CLASS:"); Init(); long lMaxDelay = (3L * Stopwatch.Frequency) / 1000L; // 3 ms MicroTimer lMicroTimer = new MicroTimer(new Queue<long>(_Schedule), lMaxDelay); lMicroTimer.OnMicroTimer += OnMicroTimer; lMicroTimer.OnMicroTimerStop += OnMicroTimerStop; lMicroTimer.OnMicroTimerSkipped += OnMicroTimerSkipped; lMicroTimer.Start(); } // static void OnMicroTimerSkipped(int xSenderThreadId, long xWakeUpTimeInTicks, long xDelayInTicks) { Console.WriteLine("MicroTimer for WakeUpTime " + xWakeUpTimeInTicks + " did not run. Delay was: " + xDelayInTicks); } // static void OnMicroTimerStop(int xSenderThreadId) { Console.WriteLine("MicroTimer stopped."); PrintStats(); } // static void OnMicroTimer(int xSenderThreadId, long xWakeUpTimeInTicks, long xDelayInTicks) { RecordDatapoint(); Console.WriteLine("(Delay at wakeup time was " + xDelayInTicks.ToString("#,##0" + " tick)")); } // #endregion #region SpinWait precision private static void testSpinWaitPrecision() { Console.WriteLine(); Console.WriteLine("SpinWait tests (neglecting PrivilegedProcessorTime):"); Thread.CurrentThread.Priority = ThreadPriority.Highest; Thread.Sleep(0); // switch context at a good point long a = Stopwatch.GetTimestamp(); Thread.SpinWait(100000); long b = Stopwatch.GetTimestamp(); Console.WriteLine("Number of ticks for a SpinWait: " + (b - a).ToString("#,##0")); a = Stopwatch.GetTimestamp(); Thread.Sleep(0); for (int i = 0; i < 100; i++) { Thread.SpinWait(100000); } b = Stopwatch.GetTimestamp(); double lAverage = (b - a) / 100.0; Console.WriteLine("Average ticks for 100x SpinWaits: " + lAverage.ToString("#,##0") + " == " + (lAverage * 1000.0 * 1000.0/ Stopwatch.Frequency).ToString("#,##0.0000") + " microseconds"); // now we do get extremly precise long lEndTime = Stopwatch.GetTimestamp() + Stopwatch.Frequency; // wake up in one second Thread.Sleep(900); // imagine a timer raises an event roughly 100ms too early while (Stopwatch.GetTimestamp() < lEndTime) { Thread.SpinWait(10); // no context switching } a = Stopwatch.GetTimestamp(); Console.WriteLine("SpinWait caused an error of just: " + ((a - lEndTime) * 1000.0 * 1000.0 / _FrequencyAsDouble).ToString("#,##0.0000") + " microseconds"); Thread.CurrentThread.Priority = ThreadPriority.Normal; Console.WriteLine(); } // #endregion #region simple loop private static void UseLoop() { Thread.CurrentThread.Priority = ThreadPriority.Highest; Console.WriteLine("LOOP AND SLEEP:"); Init(); Thread.Sleep((int)getTimeMsToNextCall_Long()); while (_CurrentDataPoint < _NumDataPoints) { RecordDatapoint(); if (_CurrentDataPoint >= _NumDataPoints) break; Thread.Sleep((int)getTimeMsToNextCall_Long()); } PrintStats(); _AutoResetEvent.Set(); Thread.CurrentThread.Priority = ThreadPriority.Normal; } // #endregion #region SystemTimer private static System.Timers.Timer _SystemTimer = null; private static void UseSystemTimer() { Console.WriteLine("SYSTEM TIMER:"); Init(); _SystemTimer = new System.Timers.Timer(); _SystemTimer.AutoReset = false; _SystemTimer.Elapsed += SystemTimer_Elapsed; _SystemTimer.Interval = getTimeMsToNextCall_Double(); // do not init in constructor! _SystemTimer.Start(); _AutoResetEvent.WaitOne(); _SystemTimer.Stop(); _SystemTimer = null; PrintStats(); } // private static void SystemTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { RecordDatapoint(); // calibrate timer (we did not start with the right interval when we launched it) System.Timers.Timer lTimer = _SystemTimer; if (lTimer == null) return; if (_CurrentDataPoint >= _NumDataPoints) return; lTimer.Stop(); lTimer.Interval = getTimeMsToNextCall_Double(); lTimer.Start(); } // #endregion #region ThreadingTimer private static System.Threading.Timer _ThreadingTimer = null; private static void UseThreadingTimer() { Console.WriteLine("THREAD TIMER:"); Init(); TimerCallback lCallback = new TimerCallback(ThreadingTimer_Elapsed); _ThreadingTimer = new System.Threading.Timer(lCallback, null, getTimeMsToNextCall_Long(), (long)(Timeout.Infinite)); _AutoResetEvent.WaitOne(); _ThreadingTimer = null; PrintStats(); } // private static void ThreadingTimer_Elapsed(object xState) { RecordDatapoint(); // restart timer System.Threading.Timer lTimer = _ThreadingTimer; if (lTimer == null) return; if (_CurrentDataPoint >= _NumDataPoints) return; lTimer.Change(getTimeMsToNextCall_Long(), (long)Timeout.Infinite); } // #endregion #region miscellaneous private static void Init() { _DataPoints.Clear(); _CurrentDataPoint = 0; // init exact time schedule long lOffset = Stopwatch.GetTimestamp() + _PeriodInTicks; // we start in the future _Schedule = new long[_NumDataPoints]; for (int i = 0; i < _NumDataPoints; i++) { _Schedule[i] = lOffset; lOffset += _PeriodInTicks; } } // private static void PrintStats() { if (_DataPoints.Count < 1) return; Console.WriteLine("Average " + _DataPoints.Average()); long lMin = _DataPoints.Min(); long lMax = _DataPoints.Max(); Console.WriteLine("Min " + lMin); Console.WriteLine("Max " + lMax); Console.WriteLine("Range " + (lMax - lMin)); Console.WriteLine(); } // private static void RecordDatapoint() { long lDifference = Stopwatch.GetTimestamp() - _Schedule[_CurrentDataPoint]; // positive = late, negative = early _DataPoints.Add(lDifference); Console.WriteLine("Delay in ticks: " + lDifference.ToString("#,##0") + " == " + ((lDifference * 1000000.0) / _FrequencyAsDouble).ToString("#,##0") + " microseconds"); _CurrentDataPoint++; if (_CurrentDataPoint >= _NumDataPoints) _AutoResetEvent.Set(); } // private static long getTimeMsToNextCall_Long() { long lTicks = (_Schedule[_CurrentDataPoint] - Stopwatch.GetTimestamp()); return (1000 * lTicks) / Stopwatch.Frequency; } // private static double getTimeMsToNextCall_Double() { double lTicks = (double)(_Schedule[_CurrentDataPoint] - Stopwatch.GetTimestamp()); return (1000.0 * lTicks) / _FrequencyAsDouble; } // #endregion } // class } // namespace
using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; namespace TimerPrecisionTest { public class MicroTimer { private readonly Queue<long> _TickTimeTable; private readonly Thread _Thread; private readonly long _MaxDelayInTicks; // do not run if the delay was too long private long _NextWakeUpTickTime; public delegate void dOnMicroTimer(int xSenderThreadId, long xWakeUpTimeInTicks, long xDelayInTicks); public event dOnMicroTimer OnMicroTimer; public event dOnMicroTimer OnMicroTimerSkipped; public delegate void dQuickNote(int xSenderThreadId); public event dQuickNote OnMicroTimerStart; public event dQuickNote OnMicroTimerStop; public MicroTimer(Queue<long> xTickTimeTable, long xMaxDelayInTicks) { _TickTimeTable = xTickTimeTable; _Thread = new Thread(new ThreadStart(Loop)); _Thread.Priority = ThreadPriority.Highest; _Thread.Name = "TimerLoop"; _Thread.IsBackground = true; _MaxDelayInTicks = xMaxDelayInTicks; } // public int Start() { if ((_Thread.ThreadState & System.Threading.ThreadState.Unstarted) == 0) return -1; _Thread.Start(); return _Thread.ManagedThreadId; } // public void Stop() { _Thread.Interrupt(); } // private void Loop() { dQuickNote lOnStart = OnMicroTimerStart; if (lOnStart != null) lOnStart(_Thread.ManagedThreadId); try { while (true) { if (_TickTimeTable.Count < 1) break; _NextWakeUpTickTime = _TickTimeTable.Dequeue(); long lMilliseconds = _NextWakeUpTickTime - Stopwatch.GetTimestamp(); if (lMilliseconds < 0L) continue; lMilliseconds = (lMilliseconds * 1000) / Stopwatch.Frequency; lMilliseconds -= 50; // we want to wake up earlier and spend the last time using SpinWait Thread.Sleep((int)lMilliseconds); while (Stopwatch.GetTimestamp() < _NextWakeUpTickTime) { Thread.SpinWait(10); } long lWakeUpTimeInTicks = Stopwatch.GetTimestamp(); long lDelay = lWakeUpTimeInTicks - _NextWakeUpTickTime; if (lDelay < _MaxDelayInTicks) { dOnMicroTimer lHandler = OnMicroTimer; if (lHandler == null) continue; lHandler(_Thread.ManagedThreadId, lWakeUpTimeInTicks, lDelay); } else { dOnMicroTimer lHandler = OnMicroTimerSkipped; if (lHandler == null) continue; lHandler(_Thread.ManagedThreadId, lWakeUpTimeInTicks, lDelay); } } } catch (ThreadInterruptedException) { } catch (Exception) { Console.WriteLine("Exiting timer thread."); } dQuickNote lOnStop = OnMicroTimerStop; if (lOnStop != null) lOnStop(_Thread.ManagedThreadId); } // } // class } // namespace
Example output:
Process with normal priority:
Priviledged processor time for process TimerPrecisionTest.vshost is 109.4 ms
Process with high priority:
Priviledged processor time for process TimerPrecisionTest.vshost is 109.4 ms
IsHighResolution system clock: True
Number of ticks per second: 1,558,893
Number of ticks to obtain a timestamp: 0.27SYSTEM TIMER:
Delay in ticks: 65,625 == 42,097 microseconds
Delay in ticks: 1,414 == 907 microseconds
Delay in ticks: 1,663 == 1,067 microseconds
Delay in ticks: 1,437 == 922 microseconds
Delay in ticks: 25,829 == 16,569 microseconds
Delay in ticks: 1,532 == 983 microseconds
Delay in ticks: 14,478 == 9,287 microseconds
Delay in ticks: 14,587 == 9,357 microseconds
Delay in ticks: 14,615 == 9,375 microseconds
Delay in ticks: 14,650 == 9,398 microseconds
Average 15583
Min 1414
Max 65625
Range 64211THREAD TIMER:
Delay in ticks: 18,890 == 12,118 microseconds
Delay in ticks: 17,493 == 11,221 microseconds
Delay in ticks: 11,750 == 7,537 microseconds
Delay in ticks: 11,824 == 7,585 microseconds
Delay in ticks: 11,914 == 7,643 microseconds
Delay in ticks: 11,858 == 7,607 microseconds
Delay in ticks: 11,935 == 7,656 microseconds
Delay in ticks: 12,049 == 7,729 microseconds
Delay in ticks: 12,108 == 7,767 microseconds
Delay in ticks: 24,953 == 16,007 microseconds
Average 14477.4
Min 11750
Max 24953
Range 13203LOOP AND SLEEP:
Delay in ticks: 7,346 == 4,712 microseconds
Delay in ticks: 7,367 == 4,726 microseconds
Delay in ticks: 7,423 == 4,762 microseconds
Delay in ticks: 7,494 == 4,807 microseconds
Delay in ticks: 7,542 == 4,838 microseconds
Delay in ticks: 7,408 == 4,752 microseconds
Delay in ticks: 20,249 == 12,989 microseconds
Delay in ticks: 20,275 == 13,006 microseconds
Delay in ticks: 20,351 == 13,055 microseconds
Delay in ticks: 20,383 == 13,075 microseconds
Average 12583.8
Min 7346
Max 20383
Range 13037SpinWait tests (neglecting PrivilegedProcessorTime):
Number of ticks for a SpinWait: 4,833
Average ticks for 100x SpinWaits: 1,422 == 912.0831 microseconds
SpinWait caused an error of just: 0.0000 microsecondsMICRO TIMER CLASS:
Delay in ticks: 772 == 495 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 34 == 22 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 6 == 4 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 5 == 3 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 6 == 4 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 6 == 4 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 6 == 4 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 5 == 3 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 6 == 4 microseconds
(Delay at wakeup time was 1 tick)
Delay in ticks: 7 == 4 microseconds
(Delay at wakeup time was 2 tick)
MicroTimer stopped.
Average 85.3
Min 5
Max 772
Range 767
WPF Control Templates (part 1)
Styles and Templates can change the appearance of elements. So what is the difference then?
Templates are far more complex. Styles are mainly adjusting existing properties. You could say that Styles are like Face-Lifting whereas Templates are rather entire face replacements. Elements get new visual trees that can consist of other elements. This is not the case for Styles.
Before we start creating our own templates let’s have a look at the existing standard control templates. Today’s example code reads these Templates from the assembly and prints them in a TextBox.
Take a stroll and get a feeling for them.
Surely this is not for beginners. My advice is to copy, paste and reuse existing professional XAML rather than spending your precious time to figure out every little issue yourself. And today’s source code aims at exactly that approach.
<Application x:Class="WpfTemplates.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ShutdownMode="OnMainWindowClose" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application> <Window x:Class="WpfTemplates.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="Window_Loaded" Title="MainWindow" Height="350" Width="525"> <DockPanel LastChildFill="True"> <ListBox DockPanel.Dock="Left" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" MinWidth="100" /> <Grid DockPanel.Dock="Right" Name="dummyGrid" Width="0" /> <TextBox DockPanel.Dock="Left" Text="{Binding XAML}" TextWrapping="NoWrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" FontFamily="Courier New" /> </DockPanel> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Markup; using System.Xml; namespace WpfTemplates { public partial class MainWindow : Window { private class Data { public Type Type { get; set; } public string XAML { get; set; } public Data(Type xType) { this.Type = xType; XAML = "#N/A"; } public override string ToString() { return Type.Name; } } // class public MainWindow() { InitializeComponent(); } // private void Window_Loaded(object sender, RoutedEventArgs e) { List<Data> lData = new List<Data>(); Assembly lAssembly = Assembly.GetAssembly(typeof(Control)); Type lControlType = typeof(Control); List<Data> lTypes = (from t in lAssembly.GetTypes().AsParallel() where t.IsSubclassOf(lControlType) && t.IsPublic && !t.IsAbstract orderby t.Name ascending select new Data(t)).ToList(); InsertXaml(lTypes); DataContext = lTypes; } // private void InsertXaml(List<Data> xTypes) { XmlWriterSettings lXmlWriterSettings = new XmlWriterSettings(); lXmlWriterSettings.Indent = true; StringBuilder lStringBuilder = new StringBuilder(); // for output foreach (Data lData in xTypes) { try { ConstructorInfo lConstructorInfo = lData.Type.GetConstructor(System.Type.EmptyTypes); if (lConstructorInfo == null) { lData.XAML = lData.Type.Name + " control cannot be instantiated."; continue; } Control lControl = lConstructorInfo.Invoke(null) as Control; // create an instance lControl.Visibility = System.Windows.Visibility.Collapsed; bool lIsNullTemplate = (lControl.Template == null); if (lIsNullTemplate) dummyGrid.Children.Add(lControl); // add a collapsed (invisible) control to get access to the template ControlTemplate lControlTemplate = lControl.Template; // now not null anymore using (XmlWriter lXmlWriter = XmlWriter.Create(lStringBuilder, lXmlWriterSettings)) { // will write to StringBuilder XamlWriter.Save(lControlTemplate, lXmlWriter); lData.XAML = lStringBuilder.ToString(); } lStringBuilder.Clear(); if (lIsNullTemplate) dummyGrid.Children.Remove(lControl); } catch (Exception ex) { lData.XAML = lData.Type.Name + " control cannot be added to the grid.\n\nException message:\n" + ex.Message; } } } // } // class } // namespace
migration Java, C++ (day 15), professional), call C++ from Java, call Java from C++
Guys, day15! I was not giving up. I haven’t found any example on the web that explains, how to return arrays of classes/structures when crossing language barriers. I was close to giving up. But after roughly six hours I had found the solution. And as usual: In hindsight it all looks simple.
Today is the last day I cover C++ in this series. There will be more, but not as a part of this 15 day bet. I have learnt a lot during these days. I proved someone else’s opinion wrong and can firmly support people with a similar attitude now. Well, so the conclusion and good news is: YES, you can learn C++ in 15 days and be on a good level afterwards. All you need is resilience.
Surely, 15 days are not enough to become an expert. But it was amazing to see how much someone can learn in such a short time.
EDIT 27 March:
I spoke to a friend yesterday. He asked me why I was not using SWIG. It is useful to learn driving a car with gears before you upgrade to automatic transmission. However, here is the link for further studies to Swig.
What settings do we need today?
In C++ add jvm.lib to Additional Dependencies of the Linker. Then enter C:\Program Files\Java\jdk1.8.0\lib in field Additional Library Directories.
You also need additional include directories:
a) C:\Program Files\Java\jdk1.8.0\include\win32
b) C:\Program Files\Java\jdk1.8.0\include
Google for javah for further research. (You don’t need it here as I provide all required code). You can use javah to generate header files for you. It simplifies your work a lot. Furthermore you may be interested in the memory management of the JNI. You don’t need to manage objects, which you generate via the JNI in C++, yourself. Great deal!
I created a temporary directory in C:\temp . It helped me to keep my environment neat and tidy. Some people prefer to store the files directly in the build directories. But this can cause problems when you clear all project data to rebuild your code from scratch.
JNI sometimes uses weird abbreviations for IDs like in lJNIEnv->GetMethodID(lTestClass, “getFriends”, “()[LFriend;”). They look worse than they are. In the brackets () you enter the input parameter types. Next to it, on the right side, you have the output parameter type. Do not add any space or separators. For instance (II)V would describe a method with two integer parameters and a void return type.
Calling Java code from C++
import javax.swing.JOptionPane; public class TestLib { double[] DoubleArrayField = { 1.1, 2.2, 3.3, 4.4, 5.5 }; int IntField = 99; public TestLib() { System.out.println("Java: Executing constructor"); } // constructor public static void main(String[] args) { System.out.println("Java: main()"); } // public String ShowErrorMessage(String xMessage) { System.out.println("Java: Do you see the nice Java MessgeBox?"); int dialogResult = JOptionPane.showConfirmDialog(null, "Therefore let's use Javax Swing and do the stuff that would take too much time in C++.", xMessage, JOptionPane.YES_NO_CANCEL_OPTION); switch (dialogResult) { case JOptionPane.CANCEL_OPTION: return "Cancel? Any other decision would have been better than that one!"; case JOptionPane.YES_OPTION: return "Yes? PS:I lov ya!"; case JOptionPane.NO_OPTION: return "No? What a pessimist you are!"; default: } return "impossible"; } // public Friend[] getFriends() { Friend[] lFriends = new Friend[6]; lFriends[0] = new Friend(1946, "Michael Sylvester Gardenzio Stallone"); lFriends[1] = new Friend(1978, "Rocky Balboa"); lFriends[2] = new Friend(1982, "John Rambo"); lFriends[3] = new Friend(2013, "Ray Breslin"); lFriends[4] = new Friend(1993, "Gabe Walker"); lFriends[5] = new Friend(1989, "Ray Tango"); return lFriends; } // } // class
#include <iostream> #include <jni.h> #include <string> using namespace std; JNIEnv* GetJVM(JavaVM ** xJVM) { JavaVMOption lOptions; lOptions.optionString = "-Djava.class.path=C:\\Ohta\\cppin15days\\Day15\\bin"; // path to your java source code JavaVMInitArgs lInitArgs; lInitArgs.version = JNI_VERSION_1_6; lInitArgs.nOptions = 1; lInitArgs.options = &lOptions; lInitArgs.ignoreUnrecognized = 0; JNIEnv *lJNIEnv; int lResult = JNI_CreateJavaVM(xJVM, (void**)&lJNIEnv, &lInitArgs); if (lResult < 0) cout << "Cannot launch JVM" << endl; return lJNIEnv; } // void test() { JavaVM *lJavaVM; JNIEnv *lJNIEnv = GetJVM(&lJavaVM); if (lJNIEnv == nullptr) return; jclass lTestClass = lJNIEnv->FindClass("TestLib"); if (!lTestClass) { std::cerr << "C++: TestLib class not found." << std::endl; return; } // call main() jmethodID lMain = lJNIEnv->GetStaticMethodID(lTestClass, "main", "([Ljava/lang/String;)V"); if (!lMain) { std::cerr << "C++: Failed to get main()." << std::endl; return; } jobjectArray lArguments = lJNIEnv->NewObjectArray(1, lJNIEnv->FindClass("java/lang/String"), NULL); jstring lFirstArgument = lJNIEnv->NewStringUTF("dummy"); lJNIEnv->SetObjectArrayElement(lArguments, 0, lFirstArgument); lJNIEnv->CallStaticVoidMethod(lTestClass, lMain, lArguments); // instantiate our TestClass jmethodID lConstructor = lJNIEnv->GetMethodID(lTestClass, "<init>", "()V"); // get the constructor by using <init> jobject lInstance = lJNIEnv->NewObject(lTestClass, lConstructor); // DoubleArrayField jobject lJArray = lJNIEnv->GetObjectField(lInstance, lJNIEnv->GetFieldID(lTestClass, "DoubleArrayField", "[D")); jdoubleArray *lJDoubles = reinterpret_cast<jdoubleArray*>(&lJArray); jsize lJSize = lJNIEnv->GetArrayLength(*lJDoubles); jboolean lFalse(false); double *lDoubles = lJNIEnv->GetDoubleArrayElements(*lJDoubles, &lFalse); cout << "DoubleArrayField: "; for (int i = 0; i < lJSize; i++) cout << " " << lDoubles[i]; cout << endl; // IntField jint lInt = lJNIEnv->GetIntField(lInstance, lJNIEnv->GetFieldID(lTestClass, "IntField", "I")); cout << "IntField: " << (int)lInt << endl; // ShowErrorMessage() jboolean lTrue(true); if (lJNIEnv->IsInstanceOf(lInstance, lTestClass) == JNI_TRUE) { // <= this code line is just for demo purposes jmethodID lMethod = lJNIEnv->GetMethodID(lTestClass, "ShowErrorMessage", "(Ljava/lang/String;)Ljava/lang/String;"); lFirstArgument = lJNIEnv->NewStringUTF("A kiss from C++"); jobject lResult = lJNIEnv->CallObjectMethod(lInstance, lMethod, lFirstArgument); // arguments in function call ,CallCharMethodA => arguments in a jvalues array, CallCharMethodV => arguments in a va_list jstring lString = reinterpret_cast<jstring>(lResult); const char *s = lJNIEnv->GetStringUTFChars(lString, nullptr); cout << "C++: Java said => " << s << endl; //lJNIEnv->ReleaseStringUTFChars(lString, s); } // getFriends() jmethodID lMethod = lJNIEnv->GetMethodID(lTestClass, "getFriends", "()[LFriend;"); jobject lResult = lJNIEnv->CallObjectMethod(lInstance, lMethod); jobjectArray lArray = reinterpret_cast<jobjectArray>(lResult); jclass lFriendClass = lJNIEnv->FindClass("Friend"); int n = (int)lJNIEnv->GetArrayLength(lArray); for (int i = 0; i < n; i++) { jobject lFriend = lJNIEnv->GetObjectArrayElement(lArray, jsize(i)); jfieldID lFieldId0 = lJNIEnv->GetFieldID(lFriendClass, "friendSince", "I"); int lFriendSince = lJNIEnv->GetIntField(lFriend, lFieldId0); jfieldID lFieldId1 = lJNIEnv->GetFieldID(lFriendClass, "name", "Ljava/lang/String;"); jobject h = lJNIEnv->GetObjectField(lFriend, lFieldId1); jstring lName = reinterpret_cast<jstring>(h); const char *s = lJNIEnv->GetStringUTFChars(lName, nullptr); cout << s << " is my best buddy since " << lFriendSince << endl; } cin.get(); } //
example output:
Java: main()
Java: Executing constructor
DoubleArrayField: 1.1 2.2 3.3 4.4 5.5
IntField: 99
Java: Do you see the nice Java MessgeBox?
C++: Java said => No? What a pessimist you are!
Michael Sylvester Gardenzio Stallone is my best buddy since 1946
Rocky Balboa is my best buddy since 1978
John Rambo is my best buddy since 1982
Ray Breslin is my best buddy since 2013
Gabe Walker is my best buddy since 1993
Ray Tango is my best buddy since 1989
Calling C++ code from Java
// ------------------------------------------------------------------------------------------------------------- // Friend.java // ------------------------------------------------------------------------------------------------------------- public class Friend { public int friendSince; // value type public String name; // reference type public Friend(int xFriendSince, String xName) { friendSince = xFriendSince; name = xName; } } // class // ------------------------------------------------------------------------------------------------------------- // TestProgram.java file // ------------------------------------------------------------------------------------------------------------- import java.io.IOException; public class TestProgram { public native String ShowErrorMessage(String xMessage); public native Friend[] getFriends(); public static void main(String[] args) throws IOException { System.load("c:\\temp\\Day15.dll"); TestProgram lTestProgram = new TestProgram(); String s = lTestProgram.ShowErrorMessage("Java: Hello C++"); System.out.println(s); Friend[] lFriends = lTestProgram.getFriends(); for (Friend lFriend : lFriends) { System.out.println(lFriend.name + " is my best buddy since " + lFriend.friendSince); } System.in.read(); } // } // class
// ------------------------------------------------------------------------------------------------------------- // TestProgram.h (generated with javah.exe) // ------------------------------------------------------------------------------------------------------------- /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class TestProgram */ #ifndef _Included_TestProgram #define _Included_TestProgram #ifdef __cplusplus extern "C" { #endif /* * Class: TestProgram * Method: ShowErrorMessage * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_TestProgram_ShowErrorMessage (JNIEnv *, jobject, jstring); /* * Class: TestProgram * Method: getFriends * Signature: ()[LFriend; */ JNIEXPORT jobjectArray JNICALL Java_TestProgram_getFriends (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif // ------------------------------------------------------------------------------------------------------------- // Day15.cpp file // ------------------------------------------------------------------------------------------------------------- #include <stdio.h> #include <iostream> #include <string> #include <jni.h> #include "C:\temp\TestProgram.h" using namespace std; jstring Alloc(JNIEnv *xJNIEnv, const char *xText){ return xJNIEnv->NewStringUTF(xText); } // JNIEXPORT jstring JNICALL Java_TestProgram_ShowErrorMessage(JNIEnv *xJNIEnv, jobject xObject, jstring xMessage) { const char *lMessage = xJNIEnv->GetStringUTFChars(xMessage, nullptr); cout << "C++: I am too lazy for MessageBoxes." << endl; wcout << "C++: Your message was: " << lMessage << endl; return Alloc(xJNIEnv, "C++: Good night!"); } // void AddFriend(JNIEnv *xJNIEnv, jclass xFriendClass, jobjectArray xArray, int xIndex, int xFriendSince, const char *xFriendName) { jobjectArray lArguments = xJNIEnv->NewObjectArray(2, xFriendClass, NULL); jint lFriendSince(xFriendSince); jstring lName = xJNIEnv->NewStringUTF(xFriendName); jmethodID lConstructor = xJNIEnv->GetMethodID(xFriendClass, "<init>", "(ILjava/lang/String;)V"); // get the constructor by using <init> jobject lInstance = xJNIEnv->NewObject(xFriendClass, lConstructor, lFriendSince, lName); xJNIEnv->SetObjectArrayElement(xArray, jsize(xIndex), lInstance); } // JNIEXPORT jobjectArray JNICALL Java_TestProgram_getFriends(JNIEnv *xJNIEnv, jobject xObject) { //jclass lObjectClass = xJNIEnv->GetObjectClass(xObject); jclass lFriendClass = xJNIEnv->FindClass("Friend"); jobjectArray lArray = xJNIEnv->NewObjectArray(6, lFriendClass, NULL); if (!lFriendClass) { std::cerr << "C++: Friend class not found." << std::endl; return lArray; } AddFriend(xJNIEnv, lFriendClass, lArray, 0, 1946, "Michael Sylvester Gardenzio Stallone"); AddFriend(xJNIEnv, lFriendClass, lArray, 1, 1978, "Rocky Balboa"); AddFriend(xJNIEnv, lFriendClass, lArray, 2, 1982, "John Rambo"); AddFriend(xJNIEnv, lFriendClass, lArray, 3, 2013, "Ray Breslin"); AddFriend(xJNIEnv, lFriendClass, lArray, 4, 1993, "Gabe Walker"); AddFriend(xJNIEnv, lFriendClass, lArray, 5, 1989, "Ray Tango"); return lArray; } //
example output:
C++: I am too lazy for MessageBoxes.
C++: Your message was: Java: Hello C++
C++: Good night!
Michael Sylvester Gardenzio Stallone is my best buddy since 1946
Rocky Balboa is my best buddy since 1978
John Rambo is my best buddy since 1982
Ray Breslin is my best buddy since 2013
Gabe Walker is my best buddy since 1993
Ray Tango is my best buddy since 1989
migration C#, C++ (day 14), call C# from C++ (advanced), call C++ from C# (hardcore)
Hardcore today.The second example is C# even though it looks like C++. So don’t get confused. Day 15 is nigh.
C++ is about speed and not comfort. Nevertheless, sometimes you want comfort. And it can be done. The C++ programmer can in fact call C# code from C++ to access the entire .Net Framework indirectly.
Usually you would use a TCP connection to achieve this. Unfortunately it does require work. And you sometimes do not have the source code of a C# library to quickly build in a network connection. And remoting is not a solution as well; the protocol needs to be the same. So you could end up in a lot of unnecessary programming. If you can, access available C# libraries directly. And today I show you how it can be done.
In case you need to write the library, make sure to amend the com-visibility in your AssemblyInfo.cs to [assembly: ComVisible(true)]. You also have to check the “Register for COM Interop” setting.
Oh, and before I forget this important detail. Start your Visual Studio in admin mode by right clicking it and then selecting “Run as administrator”. You won’t have enough user rights to register the library for com otherwise. In case you are not using Visual Studio, or you have to roll out the library on a different PC, then use RegAsm to register the library on that system. You may have a 32 bit and a 64 bit version of RegAsm on your PC. Use the right one to avoid nasty error messages.
Calling C# code from C++
using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace TestLib { // struct is a value type [StructLayout(LayoutKind.Sequential)] public struct Friend { public int friendSince; // value type [MarshalAs(UnmanagedType.BStr)] public string name; // reference type } // struct public interface IFriends { Friend[] GetFriends { get; } string ShowErrorMessage(string xMessage); } // interface public class Friends : IFriends { public string ShowErrorMessage(string xMessage) { Console.WriteLine("Do you see the nice .Net MessgeBox?"); DialogResult lButtons = MessageBox.Show("Therefore let's use Windows Forms and do the stuff that would take too much time in C++.", xMessage, MessageBoxButtons.YesNoCancel); switch (lButtons) { case DialogResult.Cancel: return "Cancel? Any other decision would have been better than that one!"; case DialogResult.No: return "No? What a pessimist you are!"; case DialogResult.Yes: return "Yes? PS:I lov ya!"; default: return "impossible"; } } // public Friend[] GetFriends { get { Friend[] lFriends = new Friend[6]; lFriends[0].name = "Michael Sylvester Gardenzio Stallone"; lFriends[0].friendSince = 1946; lFriends[1].name = "Rocky Balboa"; lFriends[1].friendSince = 1978; lFriends[2].name = "John Rambo"; lFriends[2].friendSince = 1982; lFriends[3].name = "Ray Breslin"; lFriends[3].friendSince = 2013; lFriends[4].name = "Gabe Walker"; lFriends[4].friendSince = 1993; lFriends[5].name = "Ray Tango"; lFriends[5].friendSince = 1989; return lFriends; } } // } // class } // namespace
#include <windows.h> #include <stdio.h> #include <iostream> #pragma warning (disable: 4278) #import <mscorlib.tlb> // import the CLR #import "..\TestLib\bin\Debug\TestLib.tlb" // path to your C# library using namespace std; using namespace TestLib; void test() { HRESULT lHResult; lHResult = CoInitialize(NULL); // Initializes the COM library on the current thread and identifies the concurrency model as single-thread apartment (STA). if (FAILED(lHResult)) { cout << "Cannot initialize com." << endl; return; } IFriends *lFriends = nullptr; lHResult = CoCreateInstance(__uuidof(Friends), NULL, CLSCTX_INPROC_SERVER, __uuidof(IFriends), (void**)&lFriends); if (FAILED(lHResult)) { cout << "Instance creation failed" << endl; return; } Friend HUGEP *lBSTR; SAFEARRAY *lSafeArray = lFriends->GetFriends; lHResult = SafeArrayAccessData(lSafeArray, (void HUGEP* FAR*)&lBSTR); if (FAILED(lHResult)) { cout << "Getting a property failed" << endl; return; } for (ULONG i = 0; i < lSafeArray->rgsabound->cElements; i++) { wcout << lBSTR[i].name << " is my best buddy since " << lBSTR[i].friendSince << endl; // 64bit: lBSTR[0].name is wchar_t (Unicode) } SafeArrayUnaccessData(lSafeArray); _bstr_t lMessage("C++ is not happy enough with Rambo!"); _bstr_t lReply = lFriends->ShowErrorMessage(lMessage); wcout << "C# replied: " << lReply << endl; // .GetBSTR() CoUninitialize(); cin.get(); } //
example output:
Michael Sylvester Gardenzio Stallone is my best buddy since 1946
Rocky Balboa is my best buddy since 1978
John Rambo is my best buddy since 1982
Ray Breslin is my best buddy since 2013
Gabe Walker is my best buddy since 1993
Ray Tango is my best buddy since 1989
Do you see the nice .Net MessgeBox?
C# replied: Yes? PS:I lov ya!
Let’s do the opposite now. C# coders sometimes need to access mashine code (C++, Assembler, etc).
I decided to make it a bit more difficult today. As mentioned already: It is day 14. Therefore I thought about returning an array of a structure from C++ to C#. I was checking the internet and after more than an hour I still had not found anything that could be used. C# most likely does not support it.
After a while I got the idea to use pointers in C#. They are very unusual and only for professional people. Well, finally I found a practical example for something seemingly useless. You can really crash your PC if you do it wrong. The critical code is marked with the keyword unsafe:
unsafe { Friend* lFriend = (Friend*)lIntPtr; for (int i = 0, n = FriendsCount(); i < n; i++) { char* lChars = (char*)lFriend->name; string lWho = new string(lChars); Console.WriteLine("C#: since " + lFriend->friendSince + " my best buddy is " + lWho); lFriend++; } }
I have to admit, in hindsight it does look easy. But it took a lot of time to get there. Check the internet. There is no solution out there on how to read returned arrays … at least I did not find it. Many people have failed it seems.
First of all you have to enable unsafe code to use pointers in C#. Go to your Build settings and check the Allow unsafe code option:
The unknown size with arrays of structures can be solved by using pointers in the structure rather than the content itself. This way the size is equidistant. Surely, you cannot solve the problem when you have no source code access to the library and the returned structure size is variable. I see no practical com-example for this. Indeed all programmers of all languages would face the same problem here. C++ does not tell you the size, because there is no meta-data. This is why I implemented a function to get the array size separately. I am sure you have used buffers in the past. You always have to oversize them. This is a similar problem, not the same though.
Calling C++ code from C#
This really is C#. It can use pointers.
using System; using System.Runtime.InteropServices; namespace Day14b { class Program { [StructLayout(LayoutKind.Sequential)] public struct Friend { public int friendSince; public IntPtr name; }; // struct private const string cLibrary = @"C:\Users\Username\Desktop\Day14b\Debug\TestLib.dll"; [DllImport(cLibrary, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] static extern string ShowErrorMessage(string xMessage); [DllImport(cLibrary, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] static extern IntPtr GetFriend(); [DllImport(cLibrary)] static extern int FriendsCount(); [DllImport(cLibrary)] static extern void ReleaseMemory(); static void Main(string[] args) { string lFeedback = ShowErrorMessage("C#: Hello C++"); Console.WriteLine("C#: C++ replied -> " + lFeedback + Environment.NewLine); Console.WriteLine("C#: Number of friends: " + FriendsCount()); Console.WriteLine("C#: Fasten your seatbelts! This is hardcore C#"); IntPtr lIntPtr = GetFriend(); Console.WriteLine(); Console.WriteLine("C#: Number of friends: " + FriendsCount()); Console.WriteLine(); unsafe { Friend* lFriend = (Friend*)lIntPtr; for (int i = 0, n = FriendsCount(); i < n; i++) { char* lChars = (char*)lFriend->name; string lWho = new string(lChars); Console.WriteLine("C#: since " + lFriend->friendSince + " my best buddy is " + lWho); lFriend++; // increment the pointer, it now points to the next structure in the array } } ReleaseMemory(); Console.ReadKey(); } // } // class } // namespace
#include <stdio.h> #include <iostream> #include <string> #include <vector> #include <windows.h> using namespace std; extern "C" { struct Friend { int friendSince; const wchar_t *name; }; // struct vector<wchar_t *> _BStrings; Friend *_Friends = nullptr; const wchar_t *Alloc(const wchar_t *xText){ wchar_t * lBSTR = SysAllocString(xText); _BStrings.push_back(lBSTR); return lBSTR; } // __declspec(dllexport) const wchar_t* ShowErrorMessage(BSTR xMessage) { cout << "C++: I am too lazy for MessageBoxes." << endl; wcout << "C++: Your message was: " << xMessage << endl; return Alloc(L"C++: Good night!"); } // __declspec(dllexport) int FriendsCount() { if (_Friends == nullptr) return 0; return 6; // which is a constant value in our example } // __declspec(dllexport) Friend *GetFriend(int xIndexer) { if (_Friends != nullptr) return _Friends; _Friends = new Friend[6]; _Friends[0].name = Alloc(L"Michael Sylvester Gardenzio Stallone"); _Friends[0].friendSince = 1946; _Friends[1].name = Alloc(L"Rocky Balboa"); _Friends[1].friendSince = 1978; _Friends[2].name = Alloc(L"John Rambo"); _Friends[2].friendSince = 1982; _Friends[3].name = Alloc(L"Ray Breslin"); _Friends[3].friendSince = 2013; _Friends[4].name = Alloc(L"Gabe Walker"); _Friends[4].friendSince = 1993; _Friends[5].name = Alloc(L"Ray Tango"); _Friends[5].friendSince = 1989; return _Friends; // be aware of the possible memory leak, you have to free lFriend } // __declspec(dllexport) void ReleaseMemory() { for (auto &lBSTR : _BStrings) SysFreeString(lBSTR); if (_Friends != nullptr) delete[] _Friends; cout << endl << "C++: Memory released." << endl; } // } // extern
example output:
C++: I am too lazy for MessageBoxes.
C++: Your message was: C#: Hello C++
C#: C++ replied -> C++: Good night!C#: Number of friends: 0
C#: Fasten your seatbelts! This is hardcore C#C#: Number of friends: 6
C#: since 1946 my best buddy is Michael Sylvester Gardenzio Stallone
C#: since 1978 my best buddy is Rocky Balboa
C#: since 1982 my best buddy is John Rambo
C#: since 2013 my best buddy is Ray Breslin
C#: since 1993 my best buddy is Gabe Walker
C#: since 1989 my best buddy is Ray TangoC++: Memory released.
Protocol Buffers (part 3, advanced), Tcp Networking
Ok guys, hardcore. Unfortunately in a different way than I thought 😉
Once again it took a lot of time to prepare the next source code example. It extends the code of my last post.
I removed the ProtoType class, because inheritance is not a real strength of Protobuf-Net. It is possible, but the ease of code maintenance is a definite argument against it. The complexity increases slowly, we cannot afford code that is difficult to change.
In theory we could write a Serialize() method for each class, the result would be extreme performance. You could use the BitConverter class to convert and then merge values without causing too much overhead. And by using Assembler I guess you could even make it 10 times faster again.
I think that all modern protocols lose a lot of time due to the required reflection, which is mainly the mapping of methods and properties. And unlike Assembler the modern languages do not allow to eg. simply write an integer to a memory address and only read the first byte of it.
You can rotate bits or multiply and divide, what is much less efficient.
In C# you can use the Parallel class to boost your performance. Of course the processing time of each code piece has to be substantial, otherwise it takes longer to create tasks than to solve the problems. In slow networks it might even be worth considering packing data before it is sent.
I replaced the ProtoType class by the Header class, which tells the type of the succeeding data block and also adds a serial identifier. This identifier can be used to receive vital feedbacks. For instance the server tells the client if the data transmission was successful or not. It can also send calculation results or error messages.
The data stream has the following order: header, data, header, data, header, data … except for feedback headers; they do not need data blocks. The enum eType inside the Header class defines all possible data types: ErrorMessage, Feedback, Book or Fable.
As said before I did not use inheritance for the Header class. The code remains legible and there is no spaghetti code to achieve indirect multiple inheritance.
In my last post, the client was sending and the server was receiving data. Now the communication is bidirectional. The client uses two threads, one to send and one to receive. The sending method is still using the BlockingCollection construction and an endless loop on a separate thread.
The server can be connected to several clients simultaneously. To keep it simple I decided sending data without any context switching. This usually blocks threads during the IO operation. I have added tasks inside the event OnMessageBook to give an example on how to avoid this. Nevertheless the send method uses a lock on the client preventing simultaneous write actions on the socket. This is bad practice; you should not apply locks on objects that might be locked in other areas as well. This is out of your scope, you don’t know if the .Net framework or any other code uses a lock on the same object, which could cause undesired behavior. In the below example it would have been better to wrap the client object into another class and apply the lock on that outer shell object. But I guess this is ok in our shorter example. The code was long enough already.
public static void Send(TcpClient xClient, ProtoBufExample.Header xHeader) { if (xHeader == null) return; if (xClient == null) return; lock (xClient) { NetworkStream lNetworkStream = xClient.GetStream(); ....
The above lock problem could be avoided with this:
public class ClientData { public TcpClient Client { get; private set; } private DateTime _ConnectedSince; private string _UserName; .... public static void Send(ClientData xClient, ProtoBufExample.Header xHeader) { if (xHeader == null) return; if (xClient == null) return; lock (xClient) { NetworkStream lNetworkStream = xClient.Client.GetStream(); ....
And here is the entire source code:
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using ProtoBuf; namespace DemoApp { public class ProtoBufExample { public enum eType : byte { eError = 0, eFeedback, eBook, eFable }; [ProtoContract] public class Header { [ProtoMember(1)] public eType objectType; [ProtoMember(2)] public readonly int serialMessageId; public object data; private static int _HeaderSerialId = 0; public Header(object xData, eType xObjectType, int xSerialMessageId = 0) { data = xData; serialMessageId = (xSerialMessageId == 0) ? Interlocked.Increment(ref _HeaderSerialId) : xSerialMessageId; objectType = xObjectType; // we could use "if typeof(T) ...", but it would be slower, harder to maintain and less legible } // constructor // parameterless constructor needed for Protobuf-net public Header() { } // constructor } // class [ProtoContract] public class ErrorMessage { [ProtoMember(1)] public string Text; } // class [ProtoContract] public class Book { [ProtoMember(1)] public string author; [ProtoMember(2, DataFormat = DataFormat.Group)] public List<Fable> stories; [ProtoMember(3)] public DateTime edition; [ProtoMember(4)] public int pages; [ProtoMember(5)] public double price; [ProtoMember(6)] public bool isEbook; public override string ToString() { StringBuilder s = new StringBuilder(); s.Append("by "); s.Append(author); s.Append(", edition "); s.Append(edition.ToString("dd MMM yyyy")); s.Append(", pages "); s.Append(pages); s.Append(", price "); s.Append(price); s.Append(", isEbook "); s.Append(isEbook); s.AppendLine(); if (stories != null) foreach (Fable lFable in stories) { s.Append("title "); s.Append(lFable.title); s.Append(", rating "); s.Append(lFable.customerRatings.Average()); // Average() is an extension method of "using System.Linq;" s.AppendLine(); } return s.ToString(); } // } // class [ProtoContract] public class Fable { [ProtoMember(1)] public string title; [ProtoMember(2, DataFormat = DataFormat.Group)] public double[] customerRatings; public override string ToString() { return "title " + title + ", rating " + customerRatings.Average(); } // } // class public static Book GetData() { return new Book { author = "Aesop", price = 1.99, isEbook = false, edition = new DateTime(1975, 03, 13), pages = 203, stories = new List<Fable>(new Fable[] { new Fable{ title = "The Fox & the Grapes", customerRatings = new double[]{ 0.7, 0.7, 0.8} }, new Fable{ title = "The Goose that Laid the Golden Eggs", customerRatings = new double[]{ 0.6, 0.75, 0.5, 1.0} }, new Fable{ title = "The Cat & the Mice", customerRatings = new double[]{ 0.1, 0.0, 0.3} }, new Fable{ title = "The Mischievous Dog", customerRatings = new double[]{ 0.45, 0.5, 0.4, 0.0, 0.5} } }) }; } // } // class public class PendingFeedbacks { private readonly ConcurrentDictionary<int, ProtoBufExample.Header> _Messages = new ConcurrentDictionary<int, ProtoBufExample.Header>(); public int Count { get { return _Messages.Count; } } public void Add(ProtoBufExample.Header xHeader) { if (xHeader == null) throw new Exception("cannot add a null header"); if (!_Messages.TryAdd(xHeader.serialMessageId, xHeader)) { throw new Exception("there must be a programming error somewhere"); } } // public void Remove(ProtoBufExample.Header xHeader) { ProtoBufExample.Header lHeader; if (!_Messages.TryRemove(xHeader.serialMessageId, out lHeader)) { throw new Exception("there must be a programming error somewhere"); } switch (xHeader.objectType) { case ProtoBufExample.eType.eError: Console.WriteLine("error: " + ((ProtoBufExample.ErrorMessage)xHeader.data).Text); Console.WriteLine("the message that was sent out was: " + lHeader.objectType + " with serial id " + lHeader.serialMessageId); Console.WriteLine("please check the log files" + Environment.NewLine); break; case ProtoBufExample.eType.eFeedback: // all ok ! break; default: Console.WriteLine("warning: This message type was not expected."); break; } } // } // class public static class NetworkTest { public static void Test() { NetworkListener lServer = new NetworkListener("127.0.0.1", 65432, "Server"); NetworkClient lClient = new NetworkClient("127.0.0.1", 65432, "Client"); lServer.Connect(); lServer.OnMessageBook += new NetworkListener.dOnMessageBook(OnMessageBook); lServer.OnMessageFable += new NetworkListener.dOnMessageFable(OnMessageFable); lClient.Connect(); ProtoBufExample.Header lHeader; // send a book across the network ProtoBufExample.Book lBook = ProtoBufExample.GetData(); lHeader = new ProtoBufExample.Header(lBook, ProtoBufExample.eType.eBook); lClient.Send(lHeader); System.Threading.Thread.Sleep(1000); // remove this to see the asynchonous processing (the output will look terrible) // send a fable across the network lHeader = new ProtoBufExample.Header(lBook.stories[1], ProtoBufExample.eType.eFable); lClient.Send(lHeader); System.Threading.Thread.Sleep(1000); lClient.Disconnect(); lServer.Disconnect(); Console.ReadLine(); } // demo: synchronous processing static void OnMessageFable(TcpClient xSender, ProtoBufExample.Header xHeader, ProtoBufExample.Fable xFable) { Console.WriteLine(Environment.NewLine + "received a fable: "); Console.WriteLine(xFable.ToString()); // demo: we tell the server that something went wrong ProtoBufExample.ErrorMessage lErrorMessage = new ProtoBufExample.ErrorMessage() { Text = "The fable was rejected. It is far too short." }; ProtoBufExample.Header lErrorHeader = new ProtoBufExample.Header(lErrorMessage, ProtoBufExample.eType.eError, xHeader.serialMessageId); NetworkListener.Send(xSender, lErrorHeader); } // // demo: asynchronous processing static void OnMessageBook(TcpClient xSender, ProtoBufExample.Header xHeader, ProtoBufExample.Book xBook) { Task.Factory.StartNew(() => { Console.WriteLine(Environment.NewLine + "received a book: "); Console.WriteLine(xBook.ToString()); // send a feedback without any body to signal all was ok ProtoBufExample.Header lFeedback = new ProtoBufExample.Header(null, ProtoBufExample.eType.eFeedback, xHeader.serialMessageId); NetworkListener.Send(xSender, lFeedback); return; }); Console.WriteLine("Book event was raised"); } // } // class public class NetworkListener { private bool _ExitLoop = true; private TcpListener _Listener; public delegate void dOnMessageBook(TcpClient xSender, ProtoBufExample.Header xHeader, ProtoBufExample.Book xBook); public event dOnMessageBook OnMessageBook; public delegate void dOnMessageFable(TcpClient xSender, ProtoBufExample.Header xHeader, ProtoBufExample.Fable xFable); public event dOnMessageFable OnMessageFable; private List<TcpClient> _Clients = new List<TcpClient>(); public int Port { get; private set; } public string IpAddress { get; private set; } public string ThreadName { get; private set; } public NetworkListener(string xIpAddress, int xPort, string xThreadName) { Port = xPort; IpAddress = xIpAddress; ThreadName = xThreadName; } // public bool Connect() { if (!_ExitLoop) { Console.WriteLine("Listener running already"); return false; } _ExitLoop = false; try { _Listener = new TcpListener(IPAddress.Parse(IpAddress), Port); _Listener.Start(); Thread lThread = new Thread(new ThreadStart(LoopWaitingForClientsToConnect)); lThread.IsBackground = true; lThread.Name = ThreadName + "WaitingForClients"; lThread.Start(); return true; } catch (Exception ex) { Console.WriteLine(ex.Message); } return false; } // public void Disconnect() { _ExitLoop = true; lock (_Clients) { foreach (TcpClient lClient in _Clients) lClient.Close(); _Clients.Clear(); } } // private void LoopWaitingForClientsToConnect() { try { while (!_ExitLoop) { Console.WriteLine("waiting for a client"); TcpClient lClient = _Listener.AcceptTcpClient(); string lClientIpAddress = lClient.Client.LocalEndPoint.ToString(); Console.WriteLine("new client connecting: " + lClientIpAddress); if (_ExitLoop) break; lock (_Clients) _Clients.Add(lClient); Thread lThread = new Thread(new ParameterizedThreadStart(LoopRead)); lThread.IsBackground = true; lThread.Name = ThreadName + "CommunicatingWithClient"; lThread.Start(lClient); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { _ExitLoop = true; if (_Listener != null) _Listener.Stop(); } } // private void LoopRead(object xClient) { TcpClient lClient = xClient as TcpClient; NetworkStream lNetworkStream = lClient.GetStream(); while (!_ExitLoop) { try { ProtoBufExample.Header lHeader = ProtoBuf.Serializer.DeserializeWithLengthPrefix<ProtoBufExample.Header>(lNetworkStream, ProtoBuf.PrefixStyle.Fixed32); if (lHeader == null) break; // happens during shutdown process switch (lHeader.objectType) { case ProtoBufExample.eType.eBook: ProtoBufExample.Book lBook = ProtoBuf.Serializer.DeserializeWithLengthPrefix<ProtoBufExample.Book>(lNetworkStream, ProtoBuf.PrefixStyle.Fixed32); if (lBook == null) break; lHeader.data = lBook; // not necessary, but nicer dOnMessageBook lEventBook = OnMessageBook; if (lEventBook == null) continue; lEventBook(lClient, lHeader, lBook); break; case ProtoBufExample.eType.eFable: ProtoBufExample.Fable lFable = ProtoBuf.Serializer.DeserializeWithLengthPrefix<ProtoBufExample.Fable>(lNetworkStream, ProtoBuf.PrefixStyle.Fixed32); if (lFable == null) break; lHeader.data = lFable; // not necessary, but nicer dOnMessageFable lEventFable = OnMessageFable; if (lEventFable == null) continue; lEventFable(lClient, lHeader, lFable); break; default: Console.WriteLine("Mayday, mayday, we are in big trouble."); break; } } catch (System.IO.IOException) { if (_ExitLoop) Console.WriteLine("user requested client shutdown"); else Console.WriteLine("disconnected"); } catch (Exception ex) { Console.WriteLine(ex.Message); } } Console.WriteLine("server: listener is shutting down"); } // public static void Send(TcpClient xClient, ProtoBufExample.Header xHeader) { if (xHeader == null) return; if (xClient == null) return; lock (xClient) { try { NetworkStream lNetworkStream = xClient.GetStream(); // send header (most likely a simple feedback) ProtoBuf.Serializer.SerializeWithLengthPrefix<ProtoBufExample.Header>(lNetworkStream, xHeader, ProtoBuf.PrefixStyle.Fixed32); // send errors if (xHeader.objectType != ProtoBufExample.eType.eError) return; ProtoBuf.Serializer.SerializeWithLengthPrefix<ProtoBufExample.ErrorMessage>(lNetworkStream, (ProtoBufExample.ErrorMessage)xHeader.data, ProtoBuf.PrefixStyle.Fixed32); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } // } // class public class NetworkClient { public int Port { get; private set; } public string IpAddress { get; private set; } public string ThreadName { get; private set; } private NetworkStream _NetworkStream = null; private TcpClient _Client = null; private bool _ExitLoop = true; private BlockingCollection<ProtoBufExample.Header> _Queue = new BlockingCollection<ProtoBufExample.Header>(); private readonly PendingFeedbacks _PendingFeedbacks = new PendingFeedbacks(); public NetworkClient(string xIpAddress, int xPort, string xThreadName) { Port = xPort; IpAddress = xIpAddress; ThreadName = xThreadName; } // public void Connect() { if (!_ExitLoop) return; // running already _ExitLoop = false; _Client = new TcpClient(); _Client.Connect(IpAddress, Port); _NetworkStream = _Client.GetStream(); Thread lLoopWrite = new Thread(new ThreadStart(LoopWrite)); lLoopWrite.IsBackground = true; lLoopWrite.Name = ThreadName + "Write"; lLoopWrite.Start(); Thread lLoopRead = new Thread(new ThreadStart(LoopRead)); lLoopRead.IsBackground = true; lLoopRead.Name = ThreadName + "Read"; lLoopRead.Start(); } // public void Disconnect() { _ExitLoop = true; _Queue.Add(null); if (_Client != null) _Client.Close(); //if (_NetworkStream != null) _NetworkStream.Close(); } // public void Send(ProtoBufExample.Header xHeader) { if (xHeader == null) return; _PendingFeedbacks.Add(xHeader); _Queue.Add(xHeader); } // private void LoopWrite() { while (!_ExitLoop) { try { ProtoBufExample.Header lHeader = _Queue.Take(); if (lHeader == null) break; // send header ProtoBuf.Serializer.SerializeWithLengthPrefix<ProtoBufExample.Header>(_NetworkStream, lHeader, ProtoBuf.PrefixStyle.Fixed32); // send data switch (lHeader.objectType) { case ProtoBufExample.eType.eBook: ProtoBuf.Serializer.SerializeWithLengthPrefix<ProtoBufExample.Book>(_NetworkStream, (ProtoBufExample.Book)lHeader.data, ProtoBuf.PrefixStyle.Fixed32); break; case ProtoBufExample.eType.eFable: ProtoBuf.Serializer.SerializeWithLengthPrefix<ProtoBufExample.Fable>(_NetworkStream, (ProtoBufExample.Fable)lHeader.data, ProtoBuf.PrefixStyle.Fixed32); break; default: break; } } catch (System.IO.IOException) { if (_ExitLoop) Console.WriteLine("user requested client shutdown."); else Console.WriteLine("disconnected"); } catch (Exception ex) { Console.WriteLine(ex.Message); } } _ExitLoop = true; Console.WriteLine("client: writer is shutting down"); } // private void LoopRead() { while (!_ExitLoop) { try { ProtoBufExample.Header lHeader = ProtoBuf.Serializer.DeserializeWithLengthPrefix<ProtoBufExample.Header>(_NetworkStream, ProtoBuf.PrefixStyle.Fixed32); if (lHeader == null) break; if (lHeader.objectType == ProtoBufExample.eType.eError) { ProtoBufExample.ErrorMessage lErrorMessage = ProtoBuf.Serializer.DeserializeWithLengthPrefix<ProtoBufExample.ErrorMessage>(_NetworkStream, ProtoBuf.PrefixStyle.Fixed32); lHeader.data = lErrorMessage; } _PendingFeedbacks.Remove(lHeader); } catch (System.IO.IOException) { if (_ExitLoop) Console.WriteLine("user requested client shutdown"); else Console.WriteLine("disconnected"); } catch (Exception ex) { Console.WriteLine(ex.Message); } } Console.WriteLine("client: reader is shutting down"); } // } // class } // namespace
example output:
waiting for a client
new client connecting: 127.0.0.1:65432
waiting for a client
Book event was raisedreceived a book:
by Aesop, edition 13 Mar 1975, pages 203, price 1.99, isEbook False
title The Fox & the Grapes, rating 0.733333333333333
title The Goose that Laid the Golden Eggs, rating 0.7125
title The Cat & the Mice, rating 0.133333333333333
title The Mischievous Dog, rating 0.37received a fable:
title The Goose that Laid the Golden Eggs, rating 0.7125
error: The fable was rejected. It is far too short.
the message that was sent out was: eFable with serial id 2
please check the log filesclient: writer is shutting down
server: listener is shutting down
user requested client shutdown
client: reader is shutting down
Reflection (part 6, professional), Emit
Reflection can discover information about objects at runtime and execute against those objects. The namespace System.Reflection.Emit also allows you to build assemblies and create types at runtime. Only a few programmers will come across Emit. You have to be a kind of Rambo sewing yourself while fighting … and definitely not Alice in Wonderland.
Emit generates IL code in memory. The source code for such is quite complex and difficult. We are nearly back to Assembler style. And if you remember well, crashes and difficult debugging were the order of the day. There are tools like the EmitHelper class out there to make Emit easier. I am going to show the fundamentals today, this does not cover tools.
Today’s word list:
Domain
Application domains aid security, separating applications from each other and each other’s data. A single process can run several application domains, with the same level of isolation that would exist in separate processes. Running multiple applications within a single process increases server scalability.
Faults in one application domain cannot affect other code running in another application domain.
Module
A module is a portable executable file, such as type.dll or application.exe, consisting of one or more classes and interfaces. There may be multiple namespaces contained in a single module, and a namespace may span multiple modules.
One or more modules deployed as a unit compose an assembly.
The hierarchy is: domain => assemblies => modules => classes => functions
Ildasm.exe
A disassembler for MSIL code.
The first step is to create an executable file from:
using System; using System.Reflection; namespace HelloWorld { public class Program { static void Main(string[] args) { } // public string Test() { return string.Format("DateTime is {0:dd MMM yyyy HH:mm:ss}", DateTime.Now); } // public string Test2() { return DateTime.UtcNow.ToString(); } // public string Test3() { return Assembly.GetExecutingAssembly().ToString(); } // public void Test4() { Console.WriteLine("hello world !"); } // } // class } // namespace
On the windows taskbar, click Start, click All Programs, click Visual Studio, click Visual Studio Tools, and then click Visual Studio Command Prompt.
-or-
If you have the Windows SDK installed on your computer: On the taskbar, click Start, click All Programs, click the folder for the Windows SDK, and then click Command Prompt (or CMD Shell).
Change to the right folder and enter “ildasm HelloWorld.exe /output:HelloWorld.il”.
In your folder you should see something like this:
Open the HelloWorld.il file in a text editor and delve into the following sections:
method Test()
.method public hidebysig instance string Test() cil managed { // Code size 21 (0x15) .maxstack 8 IL_0000: ldstr "DateTime is {0:dd MMM yyyy HH:mm:ss}" IL_0005: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now() IL_000a: box [mscorlib]System.DateTime IL_000f: call string [mscorlib]System.String::Format(string, object) IL_0014: ret } // end of method Program::Test
method Test3()
.method public hidebysig instance string Test2() cil managed { // Code size 20 (0x14) .maxstack 1 .locals init ([0] valuetype [mscorlib]System.DateTime CS$0$0000) IL_0000: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_UtcNow() IL_0005: stloc.0 IL_0006: ldloca.s CS$0$0000 IL_0008: constrained. [mscorlib]System.DateTime IL_000e: callvirt instance string [mscorlib]System.Object::ToString() IL_0013: ret } // end of method Program::Test2
method Test3()
.method public hidebysig instance string Test3() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: call class [mscorlib]System.Reflection.Assembly [mscorlib]System.Reflection.Assembly::GetExecutingAssembly() IL_0005: callvirt instance string [mscorlib]System.Object::ToString() IL_000a: ret } // end of method Program::Test3
method Test4()
.method public hidebysig instance void Test4() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "hello world !" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method Program::Test4
These code sections give you a rough idea of what we are going to code in runtime.
Start a new console project. You have to edit the file “AssemblyInfo.cs”. Add the assembly attribute AllowPartiallyTrustedCallersAttribute. The program won’t run otherwise. We are on a low coding level and Microsoft obviously tries to protect code especially on that level.
AssemblyInfo.cs
using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("oink")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("blablabla")] [assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: AllowPartiallyTrustedCallersAttribute] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("0bf893aa-f3da-49ef-a2dc-a63d8ffc9ead")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
The Print() method is not big, nevertheless you find something unusual here. The keyword __arglist is not official. It has been implemented by Microsoft, but is not documented at all. It is also not well supported in C#. For instance you cannot use a foreach loop. __arglist takes any number of optional arguments like object[]. In our example it does make a big difference to use __arglist. We do not have to create an array and fill it in. This would be much more code in Emit. On the contrary the __arglist algorithm is quite short and comfortable. Follow the link for more __arglist documentation.
public static void Print(string xHeader, __arglist) { ArgIterator lIterator = new ArgIterator(__arglist); Console.WriteLine(xHeader); while (lIterator.GetRemainingCount() > 0) { TypedReference r = lIterator.GetNextArg(); object o = TypedReference.ToObject(r); Console.Write(o); } Console.WriteLine(); Console.WriteLine(); } //
EmitCode() is the actual code generation part. Let’s call the remaining source code “administration” to simplify the situation.
There are two ways to get the MethodInfo for a property. One is used for DateTime.Now, the other one is used for DateTime.UtcNow .
They can be used equally. I used both ways for demonstration purposes only.
I tried to keep the example simple. You don’t have to study the MSIL to understand the code. Some information about MSIL OpCodes can be found here.
static void EmitCode(ILGenerator xILGenerator) { MethodInfo lMethodInfo_Print = typeof(EmitDemo).GetMethod("Print"); MethodInfo lDateTime_Now = typeof(DateTime).GetProperty("Now").GetGetMethod(); MethodInfo lFormat = typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }); xILGenerator.Emit(OpCodes.Ldstr, "DateTime is {0:dd MMM yyyy HH:mm:ss}"); xILGenerator.Emit(OpCodes.Call, lDateTime_Now); xILGenerator.Emit(OpCodes.Box, typeof(DateTime)); xILGenerator.Emit(OpCodes.Call, lFormat); xILGenerator.EmitCall(OpCodes.Call, lMethodInfo_Print, new Type[] { }); xILGenerator.Emit(OpCodes.Ldstr, "This equals UTC: "); xILGenerator.Emit(OpCodes.Call, typeof(DateTime).GetMethod("get_UtcNow")); // compare this with lDateTime_Now (== same, just another demo approach) xILGenerator.EmitCall(OpCodes.Call, lMethodInfo_Print, new Type[] { typeof(DateTime) }); xILGenerator.Emit(OpCodes.Ldstr, "The assembly is: "); xILGenerator.Emit(OpCodes.Call, typeof(Assembly).GetMethod("GetExecutingAssembly")); xILGenerator.EmitCall(OpCodes.Call, lMethodInfo_Print, new Type[] { typeof(Assembly) }); xILGenerator.Emit(OpCodes.Ldstr, "Console.WriteLine() is old school."); xILGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(object) })); xILGenerator.EmitWriteLine("EmitWriteLine() is for lazy programmers."); xILGenerator.Emit(OpCodes.Ret); } //
Test1_ViaFullAssembly() shows you how to properly initialize and use an assembly. Remember the hierarchy from above: domain => assemblies => modules => classes => functions
The method is build like a chain. Each statement uses the result of the previous one.
public static void Test1_ViaFullAssembly() { AssemblyName lAssemblyName = new AssemblyName("MyAssembly"); AssemblyBuilder lAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(lAssemblyName, AssemblyBuilderAccess.Run); ModuleBuilder lModuleBuilder = lAssemblyBuilder.DefineDynamicModule("MyModule"); TypeBuilder lTypeBuilder = lModuleBuilder.DefineType("MyType"); MethodBuilder lMethodBuilder = lTypeBuilder.DefineMethod("DoSomething", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(void), Type.EmptyTypes); EmitCode(lMethodBuilder.GetILGenerator()); Type lType = lTypeBuilder.CreateType(); lType.GetMethod("DoSomething").Invoke(null, null); Console.ReadLine(); } //
example output:
DateTime is 15 Jan 2014 23:17:30This equals UTC:
15/01/2014 23:17:30The assembly is:
MyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=nullConsole.WriteLine() is old school.
EmitWriteLine() is for lazy programmers.
You can avoid building a full assembly. DynamicMethod simplifies the dynamic creation of methods. Of course you won’t have any class structure. The method is super-public, it reminds me of VBA modules.
public static void ViaDynamicMethod() { DynamicMethod lDynamicMethod = new DynamicMethod("DoSomething", typeof(void), Type.EmptyTypes, typeof(object)); EmitCode(lDynamicMethod.GetILGenerator()); lDynamicMethod.Invoke(null, null); Console.ReadLine(); } //
example output:
DateTime is 15 Jan 2014 23:17:58This equals UTC:
15/01/2014 23:17:58The assembly is:
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089Console.WriteLine() is old school.
EmitWriteLine() is for lazy programmers.
Reflection (part 5, professional), CodeDOM (& lambda expression tree)
Today’s post is about code generation at runtime using CodeDOM, which is the logical step before runtime code generation using Emit. The .Net framework has been built in an amazing way. We can easily neglect some flaws. Compared to Java there are no flaws at all. Jealousy needs to be deserved, sympathy is for free. Somehow Java is for free, and this is what you get.
Make sure, you are aware of the following differences. There are Expression and Statement. Please follow the links if you feel uncertain about the definitions.
Expression
An expression is a sequence of one or more operands and zero or more operators that can be evaluated to a single value, object, method, or namespace.
Statement
The actions that a program takes are expressed in statements. Common actions include declaring variables, assigning values, calling methods, looping through collections, and branching to one or another block of code, depending on a given condition. The order in which statements are executed in a program is called the flow of control or flow of execution.
A statement can consist of a single line of code that ends in a semicolon, or a series of single-line statements in a block. A statement block is enclosed in {} brackets and can contain nested blocks.
Succinct:
An Expression is a kind of mathematical or logical expression.
A Statement is a single code instruction or block of code to execute.
The first example code generates source code for C# and VB and saves the generated files on your desktop. Of course it has to be the classical “hello world” program.
CodeDOM stands for Code Document Object Model. It is located in the System.CodeDom namespace and provides methods to create code at runtime.
// what you need for the code example: using System; using System.CodeDom; using System.CodeDom.Compiler; using System.IO; using Microsoft.CSharp; using Microsoft.VisualBasic;
First of all you have to create a compile unit object CodeCompileUnit. It is the top container for namespaces, classes and members. Then add the components as follows:
public static void Test() { CodeCompileUnit lUnit = new CodeCompileUnit(); CodeNamespace lNamespace = new CodeNamespace("MyNamespace"); lNamespace.Imports.Add(new CodeNamespaceImport("System")); lNamespace.Imports.Add(new CodeNamespaceImport("System.IO")); // not used, just for demo lUnit.Namespaces.Add(lNamespace); CodeTypeDeclaration lClass = new CodeTypeDeclaration("MyClass"); CodeMethodInvokeExpression lExpression = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Console"), "WriteLine", new CodePrimitiveExpression("hello world !")); lNamespace.Types.Add(lClass); // write Main entry point method CodeEntryPointMethod lMain = new CodeEntryPointMethod(); lMain.Statements.Add(lExpression); lClass.Members.Add(lMain); // write another method CodeMemberMethod lMethod = new CodeMemberMethod(); lMethod.Name = "DoSomething"; lClass.Members.Add(lMethod); string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\"; CodeGeneratorOptions lOptions = new CodeGeneratorOptions(); lOptions.IndentString = " "; // or "\t"; lOptions.BlankLinesBetweenMembers = true; // generate a C# source code file CSharpCodeProvider lCSharpCodeProvider = new CSharpCodeProvider(); using (StreamWriter lStreamWriter = new StreamWriter(lDesktopPath + "HelloWorld.cs", false)) { lCSharpCodeProvider.GenerateCodeFromCompileUnit(lUnit, lStreamWriter, lOptions); } // generate a VB source code file VBCodeProvider lVBCodeProvider = new VBCodeProvider(); using (StreamWriter lStreamWriter = new StreamWriter(lDesktopPath + "HelloWorld.vb", false)) { lVBCodeProvider.GenerateCodeFromCompileUnit(lUnit, lStreamWriter, lOptions); } } //
example output file HelloWorld.cs:
//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.18408 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace MyNamespace { using System; using System.IO; public class MyClass { public static void Main() { Console.WriteLine("hello world !"); } private void DoSomething() { } } }
example output file HelloWorld.vb:
'------------------------------------------------------------------------------ ' ' This code was generated by a tool. ' Runtime Version:4.0.30319.18408 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. ' '------------------------------------------------------------------------------ Option Strict Off Option Explicit On Imports System Imports System.IO Namespace MyNamespace Public Class [MyClass] Public Shared Sub Main() Console.WriteLine("hello world !") End Sub Private Sub DoSomething() End Sub End Class End Namespace
In the above example I have added an empty method. We will make it more complex now. The method will be called with parameters from Main() and return a value to a local variable. The local variable will be stored in a field via a property.
public static void Test2() { CodeCompileUnit lUnit = new CodeCompileUnit(); CodeNamespace lNamespace = new CodeNamespace("MyNamespace"); lNamespace.Imports.Add(new CodeNamespaceImport("System")); lUnit.Namespaces.Add(lNamespace); CodeTypeDeclaration lClass = new CodeTypeDeclaration("MyClass"); lClass.IsClass = true; lClass.Attributes = MemberAttributes.Public; lNamespace.Types.Add(lClass); // ----------------------------------------------- // method DoSomething() // ----------------------------------------------- CodeTypeParameter lClassAsParameter = new CodeTypeParameter(lClass.Name); CodeTypeReference lClassReference = new CodeTypeReference(lClassAsParameter); CodeParameterDeclarationExpression lClassExpression = new CodeParameterDeclarationExpression(lClassReference, "xClass"); CodeMemberMethod lDoSomething = new CodeMemberMethod(); lDoSomething.Attributes = MemberAttributes.Public; lDoSomething.Comments.Add(new CodeCommentStatement("This is an example comment for our method DoSomething().")); lDoSomething.Name = "DoSomething"; lDoSomething.ReturnType = new CodeTypeReference(typeof(double)); lDoSomething.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "xInteger")); lDoSomething.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "xString")); lDoSomething.Parameters.Add(lClassExpression); lDoSomething.Statements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(1.0))); lClass.Members.Add(lDoSomething); // ----------------------------------------------- // private field _MyField // ----------------------------------------------- CodeMemberField lField = new CodeMemberField(typeof(long), "_MyField"); lField.Attributes = MemberAttributes.Private; lField.InitExpression = new CodePrimitiveExpression(10); lClass.Members.Add(lField); // ----------------------------------------------- // property MyProperty // ----------------------------------------------- CodeMemberProperty lProperty = new CodeMemberProperty(); lProperty.Name = "MyProperty"; lProperty.Attributes = MemberAttributes.Public; lProperty.Type = new CodeTypeReference(typeof(long)); lProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(359))); lProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), lField.Name), new CodePropertySetValueReferenceExpression())); lClass.Members.Add(lProperty); // ----------------------------------------------- // Main() // ----------------------------------------------- CodeEntryPointMethod lMain = new CodeEntryPointMethod(); lClass.Members.Add(lMain); // local double variable d CodeVariableReferenceExpression lLocalVariable_d = new CodeVariableReferenceExpression("d"); CodeVariableDeclarationStatement lLocalVariableStatement_d = new CodeVariableDeclarationStatement(typeof(double), lLocalVariable_d.VariableName); lMain.Statements.Add(lLocalVariableStatement_d); // local integer variable i CodeVariableReferenceExpression lLocalVariable_i = new CodeVariableReferenceExpression("i"); CodeVariableDeclarationStatement lLocalVariableStatement_i = new CodeVariableDeclarationStatement(typeof(int), lLocalVariable_i.VariableName); lLocalVariableStatement_i.InitExpression = new CodePrimitiveExpression(0); lMain.Statements.Add(lLocalVariableStatement_i); // local class variable MyClass CodeVariableReferenceExpression lLocalVariable_Class = new CodeVariableReferenceExpression("lClass"); CodeVariableDeclarationStatement lLocalVariableStatement_Class = new CodeVariableDeclarationStatement(lClassReference, lLocalVariable_Class.VariableName); lLocalVariableStatement_Class.InitExpression = new CodeObjectCreateExpression(lClass.Name, new CodeExpression[] { }); lMain.Statements.Add(lLocalVariableStatement_Class); // DoSomething() method call CodeMethodInvokeExpression lDoSomethingExpression = new CodeMethodInvokeExpression(lLocalVariable_Class, lDoSomething.Name, new CodeExpression[] { lLocalVariable_i, new CodePrimitiveExpression("hello"), lLocalVariable_Class}); CodeAssignStatement lAssignment = new CodeAssignStatement(lLocalVariable_d, lDoSomethingExpression); lMain.Statements.Add(lAssignment); //lMain.Statements.Add(lDoSomethingExpression); // without assignment // cast the "double" result to type "long" and write it to field _MyField via property MyProperty CodePropertyReferenceExpression lPropertyExpression = new CodePropertyReferenceExpression(lLocalVariable_Class, lProperty.Name); CodeCastExpression lCastExpression = new CodeCastExpression(typeof(long), lLocalVariable_d); lAssignment = new CodeAssignStatement(lPropertyExpression, lCastExpression); lMain.Statements.Add(lAssignment); // ----------------------------------------------- // create source code // ----------------------------------------------- string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\"; CodeGeneratorOptions lOptions = new CodeGeneratorOptions(); lOptions.IndentString = " "; // or "\t"; lOptions.BlankLinesBetweenMembers = true; // generate a C# source code file CSharpCodeProvider lCSharpCodeProvider = new CSharpCodeProvider(); using (StreamWriter lStreamWriter = new StreamWriter(lDesktopPath + "MoreComplex.cs", false)) { lCSharpCodeProvider.GenerateCodeFromCompileUnit(lUnit, lStreamWriter, lOptions); } } //
example output:
//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.18408 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace MyNamespace { using System; public class MyClass { private long _MyField = 10; public virtual long MyProperty { get { return 359; } set { this._MyField = value; } } // This is an example comment for our method DoSomething(). public virtual double DoSomething(int xInteger, string xString, MyClass xClass) { return 1D; } public static void Main() { double d; int i = 0; MyClass lClass = new MyClass(); d = lClass.DoSomething(i, "hello", lClass); lClass.MyProperty = ((long)(d)); } } }
Is your head burning already? This was quite complex. As usual there are easier ways for lazy people. I do not recommend any shortcuts. It undermines the idea of flexibility.
public static void Test3() { CodeCompileUnit lUnit = new CodeCompileUnit(); CodeNamespace lNamespace = new CodeNamespace("MyNamespace"); lNamespace.Imports.Add(new CodeNamespaceImport("System")); lUnit.Namespaces.Add(lNamespace); CodeTypeDeclaration lClass = new CodeTypeDeclaration("MyClass"); lNamespace.Types.Add(lClass); // write Main entry point method CodeEntryPointMethod lMain = new CodeEntryPointMethod(); lMain.Statements.Add(new CodeSnippetExpression("Console.WriteLine(\"hello world !\")")); lClass.Members.Add(lMain); string lDesktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\"; CodeGeneratorOptions lOptions = new CodeGeneratorOptions(); lOptions.IndentString = " "; // or "\t"; lOptions.BlankLinesBetweenMembers = true; // generate a C# source code file CSharpCodeProvider lCSharpCodeProvider = new CSharpCodeProvider(); using (StreamWriter lStreamWriter = new StreamWriter(lDesktopPath + "HelloWorldForLazyPeople.cs", false)) { lCSharpCodeProvider.GenerateCodeFromCompileUnit(lUnit, lStreamWriter, lOptions); } } //
We created source code in the runtime environment. How can we execute it?
The next example compiles a class into an assembly and then executes the Main() method, which is automatically defined as the entry point. This happens in memory. If you want to write the executable code to a disk, then set GenerateInMemory to false and define a proper OutputAssembly path in the compiler parameters (variable lParameters). Use CompileAssemblyFromFile() instead of CompileAssemblyFromSource(). Btw. CompileAssemblyFromDom() would accept CodeCompileUnits (eg. used in Test2) as input data. Therefore you don’t have to generate a source code file to compile your code structure.
private static string[] _MyCode = new string[] { "using System;" + "public class MyClass {" + "public static void Main() { Console.WriteLine(\"hello world !\"); }" + "}" }; public static void Test4() { CSharpCodeProvider lCodeProvider= new CSharpCodeProvider(); CompilerParameters lParameters = new CompilerParameters(); //lParameters.ReferencedAssemblies.Add("System.dll"); lParameters.GenerateInMemory = true; lParameters.GenerateExecutable = true; lParameters.OutputAssembly = "HelloWorld"; CompilerResults lCompilerResults = lCodeProvider.CompileAssemblyFromSource(lParameters, _MyCode); if (lCompilerResults.Errors.Count > 0) { foreach (CompilerError lError in lCompilerResults.Errors) Console.WriteLine(lError.ToString()); } else { Console.WriteLine("Compiled succesfully " + lCompilerResults.PathToAssembly); Console.WriteLine("Executing code now"); MethodInfo m = lCompilerResults.CompiledAssembly.EntryPoint; m.Invoke(null, new object[] {}); } Console.ReadLine(); } //
example output:
Compiled succesfully
Executing code now
hello world !
Lambda expressions are using expression trees. The data-structure is like a tree. We are not necessarily talking about code here. Lambda expressions are not source code in the classical sense, because compilers translate expressions into code. In fact you don’t know the result, all you need to do is describing the code in an expression tree. For instance LINQ can interact between your code and a database. It can generate SQL queries. And these have nothing to do with the C# source code.
Let’s use an expression tree to generate another “Hello World” example at runtime. For this we need to implement the System.Linq.Expressions namespace. This is not really a part of reflection. I touch on the topic here to have it mentioned before we continue with Emit, which is far more on spot.
public static void Test5() { Expression A = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) }), Expression.Constant("hello world !")); Expression B = Expression.Call(null, typeof(Console).GetMethod("ReadLine", new Type[] { }), null); Expression[] Expressions = new Expression[] { A, B }; BlockExpression lBlockExpression = Expression.Block(Expressions); Action lAction = Expression.Lambda<Action>(lBlockExpression).Compile(); lAction(); } //
Let’s build a simple calculator now. Imagine someone enters a mathematical formula in a TextBox and wants to calculate it. I have seen so many solutions on the internet and I can tell you for sure they are all far too long! In the following code you just need to add some “nice features” like pre-parsing to allow more user flexibility. The entire Math library is yours! May the 4th be with you 😉
public static void Test6() { string lTextBoxText = "Math.Floor(5.0 * (3.0 + 7.0) / 9.0 * Math.Exp(3.0))"; string[] lCode = new string[] { "using System;" + "public class MyClass {" + "public static double Calc() { return (double)" + lTextBoxText + "; }" + "}" }; CSharpCodeProvider lCodeProvider = new CSharpCodeProvider(); CompilerParameters lParameters = new CompilerParameters(); lParameters.GenerateInMemory = true; lParameters.GenerateExecutable = false; lParameters.OutputAssembly = "CalcAssembly"; CompilerResults lCompilerResults = lCodeProvider.CompileAssemblyFromSource(lParameters, lCode); MethodInfo m = lCompilerResults.CompiledAssembly.GetType("MyClass").GetMethod("Calc"); double d = (double)m.Invoke(null, null); Console.WriteLine("calculation result is " + d); Console.WriteLine("cross check result is " + (Math.Floor(5.0 * (3.0 + 7.0) / 9.0 * Math.Exp(3.0)))); Console.ReadLine(); } //
example output:
calculation result is 111
cross check result is 111
Do you remember the post about extension methods? Unfortunately we cannot write any extension method for static classes like the .Net framework Math class. So I am going to use the string class instead. The calculator looks a little bit nicer then.
public static class MathExtension { public static double Formula(this string xFormula) { string[] lCode = new string[] { "using System;" + "public class MyClass {" + "public static double Calc() { return (double)" + xFormula + "; }" + "}" }; CSharpCodeProvider lCodeProvider = new CSharpCodeProvider(); CompilerParameters lParameters = new CompilerParameters(); lParameters.GenerateInMemory = true; lParameters.GenerateExecutable = false; lParameters.OutputAssembly = "CalcAssembly"; CompilerResults lCompilerResults = lCodeProvider.CompileAssemblyFromSource(lParameters, lCode); MethodInfo m = lCompilerResults.CompiledAssembly.GetType("MyClass").GetMethod("Calc"); return (double)m.Invoke(null, null); } // } // // how to call the extension method public static void Test7() { string lTextBoxText = "Math.Floor(5.0 * (3.0 + 7.0) / 9.0 * Math.Exp(3.0))"; double d = lTextBoxText.Formula(); Console.WriteLine(d); Console.ReadLine(); } //
Epilogue
There are some stock market trading applications, which allow users to add their own algorithmic trading strategies. Wouldn’t it be great to enter your code in C# and use the .Net framework instead of reinventing the wheel and come up with your own scripting language that will never reach any widely recognized state of the art?
Reflection (part 3, advanced, professional), plugin
We are progressing further.
Reflection tells us all we need for plugins. Once we know the assembly we can examine it like a poor frog on the dissection table. If the dissector cannot find something, then it is not the fault of the frog. Sorry for the macabre metaphor, I could not resist my ugly thoughts.
First of all we need an assembly. In the Solution Explorer right-click your Project. The tab-menu shows (Application, Build, Build Events, Debug …). Select Application. The first entry is “Assembly name:”. You can enter your prefered assembly name here. Let’s use “TestAssembly” for our example. Now the compiler will create an assembly with the name TestAssembly.
A short test program gives you an idea where your assembly is stored and what its name is.
Assembly lAssembly = Assembly.GetCallingAssembly(); Console.WriteLine(lAssembly.Location); // prints the full path of your assembly Console.WriteLine(lAssembly.FullName);
You can load assemblies into applications. We are concentrating on plugins today.
In theory you can get all required information to eg. call methods or properties via reflection itself. There is no need for extra definition files. But you can also make your life easier and predefine signatures, which are used by both the application and the plugin. Of course you are implementing constraints that reduce the flexibility, but on the other hand you reduce the error-proneness and save a lot of debugging time.
So we are going to use an interface to predefine signatures. It is important that you do not copy the interface source code into both projects. You should compile the interface into a DLL-library and access these definitions from outside in the two assemblies. Otherwise the compilers generate different identifiers (see GUID) and two equal interfaces cannot be recognized as the same type.
To clearly show the independence I want you to open Visual Studio three times. Please do not create three projects in one single solution.
Visual Studio 1
Create a library project and paste the following code. We don’t need any “using” or “namespace”. Five lines of code are enough in our example.
public interface IPlugin { string Name { get; } int Born { get; } double DoSomething(double d, string s); } // interface
Compile the project and move the library DLL-file to your preferred location.
Visual Studio 2
Add a reference to our interface library. (Right click on References in your Solution Explorer. Add Reference, then browse for the library file.)
We are creating the plugin now. The method Test() prints a path that you will need in the source code for the calling application (just to make your life easier).
public class MyPlugin1 : IPlugin { public string Name { get { return "Diana Frances"; } } public int Born { get { return 1961; } } public double DoSomething(double d, string s) { return 1.0; } } // class public class MyPlugin2 : IPlugin { public string Name { get { return "Jeanne d'Arc"; } } public int Born { get { return 1412; } } public double DoSomething(double d, string s) { return 1.0; } } // class public static class TestClass { public static void Test() { Assembly lPluginAssembly = Assembly.GetCallingAssembly(); // Assembly lPluginAssembly = Assembly.Load("TestAssembly"); // alternative Console.WriteLine(lPluginAssembly.Location); Console.WriteLine(lPluginAssembly.FullName); Console.ReadLine(); } // } // class
Visual Studio 3
The projects in Visual Studio 1 and Visual Studio 2 are compiled. You can close these two programs.
Once again add a reference to our interface library. In below example code you need to replace a path by the one, which was printed out in Visual Studio 2. (in line Assembly lPluginAssembly = Assembly.LoadFile(@”xxxxxx\TestAssembly.exe”);)
static void Main(string[] args) { Assembly lPluginAssembly = Assembly.LoadFile(@"xxxxxx\TestAssembly.exe"); //Assembly lPluginAssembly = Assembly.Load("TestAssembly"); => if loaded already var lPlugins = from t in lPluginAssembly.GetTypes() where typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface select t; foreach (Type t in lPlugins) { IPlugin lPlugin = Activator.CreateInstance(t) as IPlugin; Console.WriteLine(lPlugin.Name + " was born in " + lPlugin.Born); double d = lPlugin.DoSomething(2.0, "hello"); } Console.ReadLine(); } // main
output example:
Diana Frances was born in 1961
Jeanne d’Arc was born in 1412
The program loaded the assembly, created instances for each class and called the properties and the method. This basically is all you need for Plugins.
Of course there are also dirty ways to invoke plugin methods. One is:
Assembly lPluginAssembly = Assembly.LoadFile(@"xxxxxx\TestAssembly.exe"); Type t = lPluginAssembly.GetType("MyNamespaceName.MyPlugin1"); MethodInfo m = t.GetMethod("DoSomething"); double d = (double)m.Invoke(Activator.CreateInstance(t), new object[] { 2.0, "hello" });
Async and await (advanced, .Net 4.5, C# 5)
The importance is in the details. It all looks easy, but follow each step carefully today.
Windows pauses threads that are waiting for I/O operations to complete (eg. internet or file access). The same threads cannot be used for other jobs in the meantime and new threads need to be created. You could use tasks to solve this specific problem. The program would start an asynchronous task to deal with an I/O operation. After a while the same task would trigger a follow-up procedure via continuation task. It requires some work to cover all code paths, but it can be done.
C# 5 has implemented new keywords to make your life easier. You can use async to mark methods for asynchronous operations, which start synchronously and then split up as soon as the program arrives at any await keyword.
The below Print() method prints the time, sequence and ThreadId. This information is useful to understand the program cycle.
private static void Print(int xStep) { Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " step " + xStep + " , thread " + Thread.CurrentThread.ManagedThreadId); } // static async void AsyncCalls1() { Print(1); int i = await Task.Run<int>(() => { Print(2); Thread.Sleep(5000); Print(3); return 0; }); Print(4); // same thread as in step 3 Console.ReadLine(); // return void } //
example output:
19:09:36 step 1 , thread 9
19:09:36 step 2 , thread 10
19:09:41 step 3 , thread 10
19:09:41 step 4 , thread 10
The above code is a warm up for us. The method AsyncCalls1() returns void. I emphasize this seemingly insignificant fact here. If you do not return void then the compiler will complain. It wants you to add async in the calling method as well. But if you do so, then it would also ask you to add async in the calling method, that called the calling method. It would be an endless game until you arrive at Main(). And there you would not know what to do, because you cannot use async in Main(). Novices can get quite frustrated with such minuscule glitch.
What is the program doing? It starts new task, which uses another thread from the thread pool. The original thread is then neglected, there is no follow-up. Now check this out: When the created task ends, the program continues with (Task.ContinueWith()) the same thread, which it was using in the task. It seems there is no context switching.
static async void AsyncCalls2() { Print(1); Task<int> task = AsyncCalls3(); Print(4); int x = await task; Print(7); // same thread as in step 6 Console.ReadLine(); // return void } // static async Task<int> AsyncCalls3() { Print(2); int i = await Task.Run<int>(() => { Print(3); Thread.Sleep(5000); Print(5); return 0; }); Print(6); return i; // same thread as in step 5, returning an INTEGER !!! } //
example output:
19:10:16 step 1 , thread 9
19:10:16 step 2 , thread 9
19:10:16 step 3 , thread 10
19:10:16 step 4 , thread 9
19:10:21 step 5 , thread 10
19:10:21 step 6 , thread 10
19:10:21 step 7 , thread 10
Method AsyncCalls3() has a return value, which is a Task. The task that is started inside this method returns an integer. But doesn’t Task.Run() have to return Task<int> according to its definition? It is the await that changes the behavior. It returns the integer value (0). await has been implemented to shorten code, and this is what it does. The code is more legible.
Method AsyncCalls2() calls AsyncCalls3() and receives an integer and not a Task<int>. This is caused by the async keyword.
AsyncCalls2() itself returns void. This is the same issue as with AsyncCalls1(). However AsyncCalls3() can return a value to AsyncCalls2(), because AsyncCalls2() itself uses the async keyword in the method definition.
Check the program sequence. I marked the steps clearly to make comprehension easy. And then analyse the thread context switching. Between step 2 and 3 is a context switch operation, but not between 5, 6 and 7. This is the same behavior as in the first example code.
public static async void AsyncCalls4() { Print(1); string s = await AsyncCalls5(); Print(4); Console.ReadLine(); // return void } // // using System.Net.Http; public static async Task<string> AsyncCalls5() { using (HttpClient lClient = new HttpClient()) { Print(2); string lResult = await lClient.GetStringAsync("http://www.microsoft.com"); Print(3); return lResult; } } //
example output:
19:11:47 step 1 , thread 10
19:11:47 step 2 , thread 10
19:11:48 step 3 , thread 14
19:11:48 step 4 , thread 14
When searching for async and await on the web you will find the emphasis on I/O. Most example programs concentrate on this and don’t explain what is roughly going on inside the async-I/O method itself. Basically .Net async-I/O methods deal with tasks and use a similar construction to Task.ContinueWith(). This is why I concentrated on different examples that can be used in any context (even though not very meaningful examples). The internet download example is more or less a classical one. You can use await on many I/O methods. Keep in mind that AsyncCalls4() returns void and that you are not supposed to call AsyncCalls5() from the Main() method, because you would have to add async to it.