<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>C# on dwmkerr.com</title><link>https://dwmkerr.com/categories/c%23/</link><description>Recent content in C# on dwmkerr.com</description><generator>Hugo -- gohugo.io</generator><language>en-uk</language><managingEditor>Dave Kerr</managingEditor><copyright>Copright &amp;copy; Dave Kerr</copyright><lastBuildDate>Sat, 05 Sep 2020 00:00:00 +0000</lastBuildDate><atom:link href="https://dwmkerr.com/categories/c%23/index.xml" rel="self" type="application/rss+xml"/><item><title>Unit Testing the Windows Registry</title><link>https://dwmkerr.com/unit-testing-the-windows-registry/</link><pubDate>Sat, 05 Sep 2020 00:00:00 +0000</pubDate><guid>https://dwmkerr.com/unit-testing-the-windows-registry/</guid><description>&lt;p&gt;I&amp;rsquo;ve been updating some of my .NET projects recently (read more about this in &lt;a href="https://dwmkerr.com/modernising-dotnet-projects/"&gt;Modernising .NET projects for .NET Core and beyond!&lt;/a&gt;). In one of these projects I have to work with the &lt;a href="https://en.wikipedia.org/wiki/Windows_Registry"&gt;Windows Registry&lt;/a&gt; - which can be quite painful, particularly if you want to make your code unit test friendly.&lt;/p&gt;
&lt;p&gt;In this article I&amp;rsquo;m going to introduce a simple approach to make testing the registry a little easier. If you are just interested in the code and not so much the story behind it, you can skip straight to the project at &lt;a href="https://github.com/dwmkerr/dotnet-windows-registry"&gt;github.com/dwmkerr/dotnet-windows-registry&lt;/a&gt;.&lt;/p&gt;
&lt;!-- vim-markdown-toc GFM --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#why-bother-testing"&gt;Why Bother Testing?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#why-bother-testing-the-registry"&gt;Why Bother Testing the Registry?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#talk-is-cheap-show-me-the-code"&gt;Talk is cheap, show me the code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-registry-is-not-easily-testable"&gt;The Registry is not easily testable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-testable-registry"&gt;The Testable Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#go-forth-and-test"&gt;Go forth and test&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- vim-markdown-toc --&gt;
&lt;h1 id="why-bother-testing"&gt;Why Bother Testing?&lt;/h1&gt;
&lt;p&gt;There is a wealth of material available on the subject of testing. The value different of different types of tests has been discussed at length and is a constant source of debate. If you are interested in reading about testing in more detail, I recommend &lt;a href="https://martinfowler.com/testing/"&gt;Martin Fowler&amp;rsquo;s Software Testing Guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to weigh in on the debate of the value of different tests. Instead, here are the specific issues I faced when working on my &lt;a href="https://github.com/dwmkerr/sharpshell"&gt;SharpShell&lt;/a&gt; project (which is where my registry testing project originated):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;This is an open source project with a number of users, who would be inconvenienced if things broke from one release to another&lt;/li&gt;
&lt;li&gt;There are a number of scenarios in the project which involve extensive modification of the registry&lt;/li&gt;
&lt;li&gt;Even very small mistakes in the way the registry is accessed can break the code&lt;/li&gt;
&lt;li&gt;Manually testing these scenarios is &lt;em&gt;very&lt;/em&gt; time consuming&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;and I have very limited time to work on this project&lt;/li&gt;
&lt;li&gt;I want to encourage others to contribute, but have confidence their changes will not cause unexpected failures&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In &lt;em&gt;this&lt;/em&gt; project, being able to test the changes my code is going to make to the registry has been valuable. Whether it is for your own projects will depend on your own circumstances.&lt;/p&gt;
&lt;h1 id="why-bother-testing-the-registry"&gt;Why Bother Testing the Registry?&lt;/h1&gt;
&lt;p&gt;The Registry is essentially a database. A problematic database. It has a complex schema, which has evolved over time. The schema for certain features (such as Windows Shell Extensions) has changed considerably over the years. It is often messy - many programs will write to it and programs can overwrite values.&lt;/p&gt;
&lt;p&gt;One thing I have discovered over my years maintaining the SharpShell project is that registry access is one of the most &lt;em&gt;brittle&lt;/em&gt; elements of the code. It is risky, it can have unexpected consequences.&lt;/p&gt;
&lt;p&gt;There are a few things which should cause anyone working with the registry to seriously consider testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What do you do if the keys you are accessing have been modified by other programs?&lt;/li&gt;
&lt;li&gt;What if your own programs have written incorrect data?&lt;/li&gt;
&lt;li&gt;Is your code going to run on different versions of Windows, which might use the registry in different ways?&lt;/li&gt;
&lt;li&gt;Registry access is &lt;em&gt;security sensitive&lt;/em&gt; - does your code run with the appropriate permissions to access what it needs to access?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The registry is a database, but it is not an ACID database, meaning you can quite easily end up writing data in an inconsistent format (for example, if your program crashes before it has written all of the data it needs to). It has very limited access control - there is no way to limit other privileged programs overwriting or corrupting your data.&lt;/p&gt;
&lt;p&gt;Hopefully covers some of the reasons it is worth testing the registry. Now lets see some code.&lt;/p&gt;
&lt;h1 id="talk-is-cheap-show-me-the-code"&gt;Talk is cheap, show me the code&lt;/h1&gt;
&lt;p&gt;Here&amp;rsquo;s an example of what I want to be able to do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Test]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Register_Server_Associations_Uses_Appropriate_Class_Id_For_Class_Of_Extension()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Pretty important test. Given we have a file extension in the registry, assert that we&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// register an extension with the appropriate ProgID.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Prime the registry with a progid for *.exe files.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _registry.AddStructure(RegistryView.Registry64, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Join(Environment.NewLine,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34;HKEY_CLASSES_ROOT&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; .exe&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; (Default) = exefile&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; Content Type = application/x-msdownload&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; exefile&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; (Default) = Application&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Register a context menu with an *.exe association.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; clsid = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Guid(&lt;span style="color:#e6db74"&gt;&amp;#34;00000000-1111-2222-3333-444444444444&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serverType = ServerType.ShellContextMenu;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serverName = &lt;span style="color:#e6db74"&gt;&amp;#34;TestContextMenu&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; associations = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;[] { &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; COMServerAssociationAttribute(AssociationType.ClassOfExtension, &lt;span style="color:#e6db74"&gt;&amp;#34;.exe&amp;#34;&lt;/span&gt;) };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; registrationType = RegistrationType.OS64Bit;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerRegistrationManager.RegisterServerAssociations(clsid, serverType, serverName, associations, registrationType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Assert we have the new extention.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; print = _registry.Print(RegistryView.Registry64);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.That(print, Is.EqualTo(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Join(Environment.NewLine,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34;HKEY_CLASSES_ROOT&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; .exe&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; (Default) = exefile&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; Content Type = application/x-msdownload&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; exefile&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; (Default) = Application&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; ShellEx&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; ContextMenuHandlers&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; TestContextMenu&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; (Default) = {00000000-1111-2222-3333-444444444444}&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This test looks a little complex, but the details don&amp;rsquo;t matter. What matters is the &lt;em&gt;flow&lt;/em&gt;, which is just:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Given&lt;/strong&gt; a particular existing structure in the registry&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;strong&gt;when&lt;/strong&gt; I call a certain API&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;strong&gt;then&lt;/strong&gt; I expect a certain set of changes to have been made&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Should be easy right? Unfortunately, it&amp;rsquo;s not as easy as this.&lt;/p&gt;
&lt;h1 id="the-registry-is-not-easily-testable"&gt;The Registry is not easily testable&lt;/h1&gt;
&lt;p&gt;The &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.win32.registrykey"&gt;.NET Framework Registry classes&lt;/a&gt; are not written with testing in mind. This is not surprising - they are just wrappers around the &lt;a href="https://docs.microsoft.com/en-us/windows/win32/sysinfo/registry-functions"&gt;Win32 Registry APIs&lt;/a&gt;. These are APIs which have been around for a while, they have a very well-defined goal, which is to provide access to the registry. They were not written with unit testing in mind.&lt;/p&gt;
&lt;p&gt;There are in general two approaches which can be taken to testing &lt;a href="https://en.wikipedia.org/wiki/Side_effect_(computer_science)"&gt;&lt;em&gt;side effects&lt;/em&gt;&lt;/a&gt;. Side effects are changes to state &lt;em&gt;outside&lt;/em&gt; of your function or code&amp;rsquo;s state - such as the file system, databases and so on. These approaches are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test the System: We allow our tests to change the external system, making sure to prepare it in advance, read the changes, then clean up afterwards&lt;/li&gt;
&lt;li&gt;Mocks the System: We make sure our code doesn&amp;rsquo;t touch the external system when it is testing, we test a mock only and assert that the mocked code makes the expected changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first approach is arguably better - you are &lt;em&gt;really&lt;/em&gt; asserting that the expected changes have been made. But it is also complex - you have to clean up after yourself, you run the risk of your tests actually changing (or even breaking your system) and you make it harder to have other developers easily run the tests. Some systems can mitigate this - for example, with some databases you could test in the context of a transaction which you never commit. But the registry offers no such capabilities.&lt;/p&gt;
&lt;p&gt;The second approach is more common and in general a little easier. It doesn&amp;rsquo;t cause side effects, but still allows us to at least ensure we are going to attempt to make the expected changes.&lt;/p&gt;
&lt;p&gt;To mock a service under test in .NET, we generally need to be calling functions on an &lt;em&gt;interface&lt;/em&gt;. There are some ways around this (fakes, modified assemblies, etc) but they are problematic. However, the .NET Registry classes are not exposed as interfaces. This is not a failure of the framework, arguably adding interfaces without a specific need is an anti-pattern. But it does make mocking the registry hard.&lt;/p&gt;
&lt;p&gt;The easiest way around this problem (at least in my opinion) is to wrap the registry access in an interface, then provide two implementations. One which uses the standard registry access methods, and one which mocks the changes to the registry in an isolated and testable fashion. In my SharpShell code this was the approach I took, and I have just extracted this code into its own library to help others who might want to use the same approach.&lt;/p&gt;
&lt;h1 id="the-testable-registry"&gt;The Testable Registry&lt;/h1&gt;
&lt;p&gt;The solution I&amp;rsquo;ve used is fairly simple. You can see the code at:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/dotnet-windows-registry"&gt;github.com/dwmkerr/dotnet-windows-registry&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Instead of making calls to &lt;code&gt;Regsitry&lt;/code&gt; or &lt;code&gt;RegistryKey&lt;/code&gt;, you make calls to &lt;code&gt;IRegsitry&lt;/code&gt; or &lt;code&gt;IRegsitryKey&lt;/code&gt;. Then use the appropriate implementation. There are examples in the project documentation, but here&amp;rsquo;s how it looks in a nutshell.&lt;/p&gt;
&lt;p&gt;First, make sure the code you have which access the registry does it via the &lt;code&gt;IRegistry&lt;/code&gt; interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Greeter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Greeter(IRegistry _registry)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _registry = registry;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Greet(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; name, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; greeting)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var key = registry.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var subkey = key.OpenSubKey(&lt;span style="color:#e6db74"&gt;&amp;#34;Greetings&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subkey.SetValue(name, &lt;span style="color:#e6db74"&gt;$&amp;#34;{greeting}, {name}!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; IRegsitry _registry;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now in your program, create your class and provide it with a &lt;code&gt;WindowsRegistry&lt;/code&gt; class:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; greeter = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Greeter(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; WindowsRegistry());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;greeter.Greet(&lt;span style="color:#e6db74"&gt;&amp;#34;Billy&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Howdy&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And you can test your code like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; registry = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InMemoryRegistry();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; greeter = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Greeter(registry);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; print = _registry.Print(RegistryView.Registry64);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Assert.That(print, Is.EqualTo(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Join(Environment.NewLine,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34;HKEY_CURRENT_USER&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; Greetings&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;@&amp;#34; Billy = Howdy, Billy!&amp;#34;&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s the basics.&lt;/p&gt;
&lt;h1 id="go-forth-and-test"&gt;Go forth and test&lt;/h1&gt;
&lt;p&gt;There is a degree of inconvenience in having to use the interface rather than using the out-of-the-box implementation. This is a trade-off you will have to make to allow your code to be testable, and whether it is a worthwhile trade will depend on your project.&lt;/p&gt;
&lt;p&gt;The pattern of not relying on concrete implementations and instead providing interfaces to classes is known as &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection"&gt;Dependency Injection&lt;/a&gt;. There are technologies which attempt to assist with this pattern, known as Inversion of Control Containers - whether they make life easier to simply move complexity around (see &lt;a href="https://github.com/dwmkerr/hacker-laws#the-law-of-conservation-of-complexity-teslers-law"&gt;The Law of Conservation of Complexity&lt;/a&gt;). But if you are &lt;em&gt;already using&lt;/em&gt; an IoC container then adopting this library and pattern will be trivial.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it - the code has been internal to the SharpShell project for years and I have only just extracted it into its own library. I&amp;rsquo;ll be using it in my &lt;a href="https://github.com/dwmkerr/dotnet-com-admin"&gt;ComAdmin&lt;/a&gt; project (which is also being extracted from SharpShell). Given that it is new it might change a bit, and I&amp;rsquo;d love any feedback:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/dotnet-windows-registry"&gt;https://github.com/dwmkerr/dotnet-windows-registry&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Modernising .NET projects for .NET Core and beyond!</title><link>https://dwmkerr.com/modernising-dotnet-projects/</link><pubDate>Wed, 01 Jul 2020 00:00:00 +0000</pubDate><guid>https://dwmkerr.com/modernising-dotnet-projects/</guid><description>&lt;p&gt;The world of .NET is going through a transformation. The .NET Framework is reaching end of life, &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/"&gt;.NET Core&lt;/a&gt; is an increasingly feature rich and robust platform to develop solutions which target Linux, MacOS, embedded devices, containers and more. There&amp;rsquo;s also the .NET Standard.&lt;/p&gt;
&lt;p&gt;But what does this mean for .NET &lt;em&gt;Framework&lt;/em&gt; projects? In this article I&amp;rsquo;ll describe how to modernise your .NET Framework projects for .NET Core, the .NET Standard and .NET 5, which is planned to be released this year. I&amp;rsquo;ll also explain the high level differences between the platforms and what the consequences of upgrading are for consumers, developers and maintainers.&lt;/p&gt;
&lt;!-- vim-markdown-toc GFM --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-net-framework-net-core-and-the-future"&gt;The .NET Framework, .NET Core and the Future&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-challenge-modernisation-and-compatibility"&gt;The Challenge: Modernisation and Compatibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-modernisation-process---introducing-our-two-villains"&gt;The Modernisation Process - Introducing our two Villains&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#step-1---understand-the-domain"&gt;Step 1 - Understand the Domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-2---understand-the-goal---multi-platform-builds"&gt;Step 2 - Understand the Goal - Multi-Platform Builds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-3---migrate-projects-leaf-wise"&gt;Step 3 - Migrate Projects &amp;ldquo;Leaf-wise&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-4---refactor-rinse-repeat"&gt;Step 4 - Refactor, Rinse, Repeat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-5---update-your-builds"&gt;Step 5 - Update Your Builds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-6---simplify"&gt;Step 6 - Simplify!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-7---test-test-test"&gt;Step 7 - Test, Test, Test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-8---document-compatibility"&gt;Step 8 - Document Compatibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-key-learnings"&gt;The Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- vim-markdown-toc --&gt;
&lt;h1 id="the-net-framework-net-core-and-the-future"&gt;The .NET Framework, .NET Core and the Future&lt;/h1&gt;
&lt;p&gt;There&amp;rsquo;s a lot which has been written on this topic, but it can still be a little overwhelming to understand just how all of these things fit together.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a simple visual I&amp;rsquo;ve created to try and put things into context:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/dotnet-timeline.png" alt="Diagram: .NET Timeline"&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m only going to cover the bare essentials - but there are links to further reading on each topic if you want to go deeper. This article is mainly going to be focused on the practicality and consequence of migration and re-targeting.&lt;/p&gt;
&lt;p&gt;First, the &lt;strong&gt;.NET Framework&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The .NET Framework was created in 2002 as set of unified tools and standards to allow developers on the Microsoft Platform to more quickly build solutions, provide interoperability between languages and more. &lt;a href="https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet-framework"&gt;Read more about the .NET Framework&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The .NET Framework rapidly gained popularity, partly due to the convenience of developing in C# rather than Basic or C/C++. C# provided a more developer friendly language than C or C++ for many use cases, and was heavily inspired by Java. &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/"&gt;Read more about C#&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;With the increase in popularity, the .NET Framework started to have more frequent releases and became a standard part of the Windows operating system, installed out of the box rather than on-demand if needed.&lt;/li&gt;
&lt;li&gt;However - the .NET Framework only functioned on Microsoft Windows, which greatly limited its potential uses cases, even as more and more engineers used it for Web, Client Applications and mobile.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enter &lt;strong&gt;.NET Core&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft signaled a &lt;em&gt;radical&lt;/em&gt; switch in their strategy with the appointment of &lt;a href="https://en.wikipedia.org/wiki/Satya_Nadella"&gt;Satya Nadella&lt;/a&gt;, becoming increasingly focused on open source, and more importantly, deciding that the Microsoft development toolchain should not &lt;em&gt;force&lt;/em&gt; users to use Windows as their execution environment&lt;/li&gt;
&lt;li&gt;.NET Core was developed as a lightweight version of the .NET Framework, which could run on multiple platforms - including Linux and MacOS. &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/"&gt;Read more about .NET Core&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In a short period of time .NET Core became more and more feature rich, providing a lot of capabilities for web developers and front-end application developers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The challenges of &lt;strong&gt;divergence&lt;/strong&gt; and the &lt;strong&gt;.NET Standard&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As .NET Core became more feature rich, the API became closer to the .NET Framework - but they are still fundamentally different runtimes. A binary compiled for the .NET Core does not run on the .NET Framework and vice-versa.&lt;/li&gt;
&lt;li&gt;To deal with this issue, Microsoft developed the &lt;strong&gt;.NET Standard&lt;/strong&gt; - a specification of a set of APIs. If a runtime offered these APIs, then solutions built on &lt;em&gt;any runtime which meets the standard&lt;/em&gt; could run on any compliant platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What does this mean? Basically, the table below shows the consequences of this. If you build on .NET Core 2.0 (for example), you can also run on the .NET Framework 4.6.1. Mono 5.4, Unity 2018.1 and more, because all of these runtimes implement the &lt;em&gt;.NET Standard 2.0&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Of course, some features are always going to be very platform specific, so the standard started out small but has grown over time.&lt;/p&gt;
&lt;p&gt;Moving to &lt;strong&gt;convergence&lt;/strong&gt; and &lt;strong&gt;.NET&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Given that the later versions of the .NET Framework and .NET Core actually follow the same standard, the platforms are actually starting to become more and more similar.&lt;/li&gt;
&lt;li&gt;They are becoming &lt;em&gt;so&lt;/em&gt; similar that it no longer makes sense to maintain them separately. The next major version of &lt;em&gt;both&lt;/em&gt; platforms is &lt;strong&gt;.NET 5&lt;/strong&gt;. This is a new runtime which is the next version of .NET Core &lt;em&gt;and&lt;/em&gt; the .NET Framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means that the .NET Framework and .NET Core are going to converge into a single platform, which will be wonderful for developers and simplify a complex landscape.&lt;/p&gt;
&lt;p&gt;But what does this mean if you have .NET Framework projects? How do we modernise, and do we have to make trade-offs around compatibility?&lt;/p&gt;
&lt;h1 id="the-challenge-modernisation-and-compatibility"&gt;The Challenge: Modernisation and Compatibility&lt;/h1&gt;
&lt;p&gt;I have a number of projects which target the .NET Framework. On &lt;em&gt;all&lt;/em&gt; of these projects I have had multiple requests to migrate to the .NET Core, but I have had to hold off on this work until I could really understand in detail a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What would this mean for &lt;em&gt;consumers&lt;/em&gt; of the libraries? Would they have to change the platform they use? Could this break things for them?&lt;/li&gt;
&lt;li&gt;What would this mean for &lt;em&gt;developers&lt;/em&gt; on the platform? Would they need to change their development environment? Would this cause problems?&lt;/li&gt;
&lt;li&gt;What would this mean for &lt;em&gt;maintainers&lt;/em&gt; of the libraries? Would this greatly increase build and deployment complexity?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally I have found the time to be able to start to address these issues in detail - hopefully the learnings will be useful to anyone who is maintaining a .NET codebase and thinking about the future.&lt;/p&gt;
&lt;h1 id="the-modernisation-process---introducing-our-two-villains"&gt;The Modernisation Process - Introducing our two Villains&lt;/h1&gt;
&lt;p&gt;There are two key projects I wanted to modernise. They are both reasonably well used, complex, and have some potentially serious complexities for multi-platform builds.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/sharpgl"&gt;&lt;strong&gt;SharpGL&lt;/strong&gt;&lt;/a&gt; is a library that allows developers to use &lt;a href="https://www.opengl.org/"&gt;OpenGL&lt;/a&gt; in .NET applications. The big challenge? OpenGL is cross platform, but SharpGL &lt;em&gt;specifically&lt;/em&gt; provides an interface to the &lt;em&gt;Windows&lt;/em&gt; version of OpenGL. Can this possibly be made more future-proof? Could it ever target other platforms?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/sharpshell"&gt;&lt;strong&gt;SharpShell&lt;/strong&gt;&lt;/a&gt; is a library that allows developers to build &amp;lsquo;Shell Extensions&amp;rsquo; for Windows. Shell extensions are customisations to the Windows user interface, so would not be portable across platforms, but I still want to ensure that the project is future proof.&lt;/p&gt;
&lt;p&gt;What would be the experience with these two projects? I have other .NET projects, but they are far less popular and much more simple, my instinct is that if I can work through the process with &lt;em&gt;these&lt;/em&gt; projects, the others should be more straightforward.&lt;/p&gt;
&lt;p&gt;These are the steps I&amp;rsquo;ve followed to modernise. I&amp;rsquo;ll finish the article with a summary of the key learnings.&lt;/p&gt;
&lt;h2 id="step-1---understand-the-domain"&gt;Step 1 - Understand the Domain&lt;/h2&gt;
&lt;p&gt;I cannot stress this enough. In all meaningful technology work, &lt;em&gt;understand the domain&lt;/em&gt; you are dealing with. A quick Google on how to migrate, or following the formal migration guide was not enough for me. I knew I had to actually understand, at a reasonably detailed level, the differences in the runtime, the trade-offs, the process, the complexity.&lt;/p&gt;
&lt;p&gt;This article is the result of that work - sometimes writing about a topic is the best way to force yourself to learn it.&lt;/p&gt;
&lt;p&gt;Making changes rapidly and waiting to see what the consequences are can often work for small projects, internal tools and so on, but for a library which is relied upon by others is not good for the community. The last thing I wanted to do was make changes which had unintended consequences for users. So making sure that I learnt about this space, how things work under the hood, and what the expected changes in the future are was critical.&lt;/p&gt;
&lt;p&gt;Hopefully for others the process of understanding the domain will be a little easier with this article to cover the high level topics. During my actual process of writing and migrating, I went a lot deeper than this article goes.&lt;/p&gt;
&lt;p&gt;The key document to follow to actually &lt;em&gt;execute&lt;/em&gt; the migration is the excellent official &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;.NET Framework to .NET Core Porting Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="step-2---understand-the-goal---multi-platform-builds"&gt;Step 2 - Understand the Goal - Multi-Platform Builds&lt;/h2&gt;
&lt;p&gt;Given the understanding of the domain, it made it much easier to understand what the required steps would be. Essentially, all that would be needed would be to target a version of the .NET Framework which adheres to a recent version of the .NET Standard. Once this was done, in theory the project could be built for the .NET Framework &lt;em&gt;and&lt;/em&gt; for .NET Core, and also be ready for the upcoming .NET 5 release.&lt;/p&gt;
&lt;p&gt;Multi-platform builds are supported in Visual Studio 2019. These builds allow us to have a single codebase, but build libraries for multiple platforms (i.e. the .NET Framework and .NET Core). The resulting binaries can be packed as a single package, and when consumers install the package, the appropriate library is installed.&lt;/p&gt;
&lt;p&gt;This introduces the first of the significant consequences - modernising your project means you must migrate it to Visual Studio 2019.&lt;/p&gt;
&lt;p&gt;In the past, this might have been more of an issue, licenses for Visual Studio were expensive, and many organisations were locked onto specific versions for compatibility issues (or because they were slow to upgrade). This seems to be the case less often nowadays, but is still an important consideration.&lt;/p&gt;
&lt;p&gt;My projects were using Visual Studio 2017. This is how the project properties looked:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/sharpgl-target-framework-2017.png" alt="Screenshot: SharpGL Target Framework Properties for Visual Studio 2017"&gt;&lt;/p&gt;
&lt;p&gt;Unsurprisingly the .NET Standard isn&amp;rsquo;t mentioned. Time to upgrade to 2019. While I installed it I could reminisce about the excitement of buying Visual C++ .NET Learning Edition:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/visual-cpp-dotnet-learning-edition.jpg" alt="Photo: Visual C++ .NET 2003 Learning Edition"&gt;&lt;/p&gt;
&lt;p&gt;And try and remember what is was like to be a 15 years old. I wonder if that box set is still kicking around somewhere, I want to see it again. So much has changed. But long install processes for Visual Studio haven&amp;rsquo;t, at least they kept that:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/install-visual-studio-2019.png" alt="Screenshot: Visual Studio 2019 Installer"&gt;&lt;/p&gt;
&lt;p&gt;When installing, remember to enable the .NET Core features.&lt;/p&gt;
&lt;h2 id="step-3---migrate-projects-leaf-wise"&gt;Step 3 - Migrate Projects &amp;ldquo;Leaf-wise&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;As per the &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;Porting Guide&lt;/a&gt;, we need to migrate each of the projects which make up the solution, starting with the &amp;rsquo;leaves&amp;rsquo; (projects which don&amp;rsquo;t depend on other projects) and then moving up the tree to the &amp;lsquo;root&amp;rsquo; projects (projects which are depended on by others).&lt;/p&gt;
&lt;p&gt;Visually, for a solution like SharpGL, that would mean the projects will need to be converted in the following order:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/sharpgl-project-structure.png" alt="Diagram: SharpGL Project Dependency Graph"&gt;&lt;/p&gt;
&lt;p&gt;I was expecting each project to have quite different experiences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SharpGL.Serialization&lt;/code&gt; is just a set of classes which load data from files. In theory, this library should become completely portable.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpGL.WPF&lt;/code&gt; and &lt;code&gt;SharpGL.WinForms&lt;/code&gt; are &lt;em&gt;specifically&lt;/em&gt; for Windows front-end technologies. I expected these to be able to be ported, but don&amp;rsquo;t expect them to work on other platforms (in the future there might be &lt;code&gt;SharpGL.OSx&lt;/code&gt;, or &lt;code&gt;SharpGL.Gnome&lt;/code&gt;, who knows)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpGL.SceneGraph&lt;/code&gt; is a set of classes which represent 3D scenes - things like lights, cameras, materials and so on. I expect &lt;em&gt;some&lt;/em&gt; of this to &amp;lsquo;just work&amp;rsquo;, but things like image loading to perhaps need some tweaking.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpGL&lt;/code&gt; is just a wrapper around the Windows &lt;code&gt;opengl32.dll&lt;/code&gt; library. I can&amp;rsquo;t imagine this &lt;em&gt;working&lt;/em&gt; anywhere but Windows, but how would the project structure porting go and would it build?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The details on &lt;em&gt;how&lt;/em&gt; to migrate a project are in the &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;Porting Guide&lt;/a&gt;, but the general approach will be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Attempt to convert to the latest project format with the &lt;code&gt;try-convert&lt;/code&gt; tool&lt;/li&gt;
&lt;li&gt;Re-target the project to the .NET Framework 4.7.2 (the first version which supports the .NET standard)&lt;/li&gt;
&lt;li&gt;Repeat for projects which this project depends on, walking the tree of projects to the root&lt;/li&gt;
&lt;li&gt;Run the Portability Analysis tool to see if there are APIs which are not available on certain platforms&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is how you project might look after migration, having run the &lt;code&gt;try-convert&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/migrate-project.png" alt="Screenshot: Ported Visual Studio Project"&gt;&lt;/p&gt;
&lt;p&gt;Now we just need to edit the project files and change the line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;4.7.2&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;TargetFrameworks&amp;gt;&lt;/span&gt;netcoreapp2.0;netcoreapp3.0;netcoreapp3.1;net40;net45;net472&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFrameworks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The targets you will choose will depend on the APIs you want to use. There is an Portability Analysis extension available which can build a portability report, here&amp;rsquo;s what one looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/portability-report-summary.png" alt="Screenshot: Portability Report Summary"&gt;&lt;/p&gt;
&lt;p&gt;This will also show the &lt;em&gt;specific&lt;/em&gt; APIs which are not compatible with specific targets:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/portability-report.png" alt="Screenshot: Portability Report Details"&gt;&lt;/p&gt;
&lt;p&gt;Now it&amp;rsquo;s time to move to the next step.&lt;/p&gt;
&lt;h2 id="step-4---refactor-rinse-repeat"&gt;Step 4 - Refactor, Rinse, Repeat&lt;/h2&gt;
&lt;p&gt;This is the tricky part. You&amp;rsquo;ll now need to work out whether you want to &lt;em&gt;remove&lt;/em&gt; API calls which are not portable, try and use alternatives, or conditionally compile the code for different platforms.&lt;/p&gt;
&lt;p&gt;If you are using non-portable APIs you may need to use conditional blocks to execute different code depending on the framework used. The &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-target-frameworks"&gt;Target frameworks in SDK-style projects&lt;/a&gt; guide shows how to do this.&lt;/p&gt;
&lt;p&gt;You may also have to manually edit the project file to ensure that certain dependencies are &lt;em&gt;only&lt;/em&gt; used for certain targets. You solution file and dependencies may end up looking something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/conditional-dependencies.png" alt="Screenshot: Conditional Dependencies"&gt;&lt;/p&gt;
&lt;p&gt;Once you have reloaded the project you&amp;rsquo;ll see your dependencies can now be specified on a per-framework basis, and a build generates assemblies for each of the targets:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/generated-assemblies.png" alt="Screenshot: Generated Assemblies"&gt;&lt;/p&gt;
&lt;p&gt;This process might be simple, or complex, depending on the nuances of your project. For me it was fairly iterative - starting by targeting only &lt;code&gt;net40&lt;/code&gt; (the original target framework which I&amp;rsquo;d used), then adding more and more targets.&lt;/p&gt;
&lt;p&gt;Some targets will simply not be possible - for example .NET Core only supports WinForms and WPF from .NET Core 3.0 onwards; you won&amp;rsquo;t be able to build a WinForms or WPF assembly which targets a lower version, the framework doesn&amp;rsquo;t support it.&lt;/p&gt;
&lt;h2 id="step-5---update-your-builds"&gt;Step 5 - Update Your Builds&lt;/h2&gt;
&lt;p&gt;At this stage, having fixed compatibility issues, you should have code which builds in Visual Studio.&lt;/p&gt;
&lt;p&gt;Now I would recommend porting all of your build code to use the &lt;code&gt;dotnet&lt;/code&gt; build system. This is going to maximise the portability and future-proof your project, you&amp;rsquo;ll be able to run the builds on multiple platforms and are using the preferred standard tool (&lt;code&gt;msbuild&lt;/code&gt; will essentially become legacy).&lt;/p&gt;
&lt;p&gt;The way I like to structure things personally is have a set of scripts which you can run to build, test and package the code locally. You can then call these scripts from you CI tool of choice to automate things, but still keep the logic in your own code, rather than hidden away in a build system.&lt;/p&gt;
&lt;p&gt;For example, in my SharpGL project I have the following scripts:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Script&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ensure your machine can run builds by installing necessary components such as &lt;code&gt;nunit&lt;/code&gt;. Should only need to be run once.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build all solutions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run all tests, including those in samples.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coverage.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a coverage report. Reports are written to &lt;code&gt;./artifacts/coverage&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pack.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create all of the SharpGL NuGet packages, which are copied to &lt;code&gt;./artifacts/packages&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I updated my scripts to use the &lt;code&gt;dotnet&lt;/code&gt; tool. For example, the &amp;lsquo;build&amp;rsquo; script looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ps" data-lang="ps"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Run&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;build,&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hiding&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;documentation&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;warnings&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pinvoke&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;code.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;$buildCommand&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;=&amp;#34;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;msbuild&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$PSScriptRoot\SharpGL.sln&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-noWarn:CS1591&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-noWarn:CS1573&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-t:Rebuild&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-p:Configuration=Release&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Write-Host&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;Running:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;&amp;#34;$buildCommand&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Invoke-Expression&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$buildCommand&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the &amp;lsquo;pack&amp;rsquo; script looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ps" data-lang="ps"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL/SharpGL.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.SceneGraph/SharpGL.SceneGraph.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.Serialization/SharpGL.Serialization.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.WinForms/SharpGL.WinForms.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.WPF/SharpGL.WPF.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The actual scripts are a little more complex. But the key thing here is that I can run &lt;em&gt;any&lt;/em&gt; part of the CI/CD process locally (to test, debug and so on) or on a CI/CD platform.&lt;/p&gt;
&lt;p&gt;You will most likely have to &lt;em&gt;conditionally&lt;/em&gt; reference certain components. The dependency for &lt;code&gt;net40&lt;/code&gt; might be different to that for &lt;code&gt;netcoreapp3.0&lt;/code&gt;. You&amp;rsquo;ll see that in many of my project files there is now code like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Design&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net40&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Design&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Design&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Windows.Forms&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net40&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Windows.Forms&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Windows.Forms&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.0&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.1&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.0&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.1&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In my case quite a bit of trial and error was needed to find the appropriate references for each platform.&lt;/p&gt;
&lt;h2 id="step-6---simplify"&gt;Step 6 - Simplify!&lt;/h2&gt;
&lt;p&gt;One benefit I have found during this process is that you can &lt;em&gt;simplify&lt;/em&gt; your projects. You no longer need any kind of &amp;lsquo;automated NuGet restore&amp;rsquo; functionality. This means you can remove code like this from your project files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Project=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(SolutionDir)\.nuget\NuGet.targets&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Exists(&amp;#39;$(SolutionDir)\.nuget\NuGet.targets&amp;#39;)&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Target&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Name=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;EnsureNuGetPackageBuildImports&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BeforeTargets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;PrepareForBuild&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ErrorText&amp;gt;&lt;/span&gt;This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.&lt;span style="color:#f92672"&gt;&amp;lt;/ErrorText&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Error&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;!Exists(&amp;#39;..\packages\NUnit.3.11.0\build\NUnit.props&amp;#39;)&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Text=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$([System.String]::Format(&amp;#39;$(ErrorText)&amp;#39;, &amp;#39;..\packages\NUnit.3.11.0\build\NUnit.props&amp;#39;))&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Error&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;!Exists(&amp;#39;..\packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props&amp;#39;)&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Text=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$([System.String]::Format(&amp;#39;$(ErrorText)&amp;#39;, &amp;#39;..\packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props&amp;#39;))&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can also remove your &lt;code&gt;project.json&lt;/code&gt; as all of the data is now in the &lt;code&gt;csproj&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Another nice update is that you no longer need to maintain an &lt;code&gt;AssemblyInfo.cs&lt;/code&gt; file; you can keep all of your assembly metadata in the &lt;code&gt;csproj&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Finally, you can almost certainly remove any &lt;code&gt;nuspec&lt;/code&gt; files - all NuGet packaging data can also be embedded in the &lt;code&gt;csproj&lt;/code&gt; file. For example, here&amp;rsquo;s what my SharpShell project metadata looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Project&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Sdk=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.NET.Sdk.WindowsDesktop&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;TargetFrameworks&amp;gt;&lt;/span&gt;netcoreapp2.0;netcoreapp3.0;netcoreapp3.1;net40;net45;net472&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFrameworks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Library&lt;span style="color:#f92672"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- The following properies are used to manage how the project is packaged. --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageId&amp;gt;&lt;/span&gt;SharpShell&lt;span style="color:#f92672"&gt;&amp;lt;/PackageId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Copyright&amp;gt;&lt;/span&gt;Copyright (c) Dave Kerr 2020&lt;span style="color:#f92672"&gt;&amp;lt;/Copyright&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageProjectUrl&amp;gt;&lt;/span&gt;https://github.com/dwmkerr/sharpshell&lt;span style="color:#f92672"&gt;&amp;lt;/PackageProjectUrl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;RepositoryUrl&amp;gt;&lt;/span&gt;https://github.com/dwmkerr/sharpshell&lt;span style="color:#f92672"&gt;&amp;lt;/RepositoryUrl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Version&amp;gt;&lt;/span&gt;3.1.1.0&lt;span style="color:#f92672"&gt;&amp;lt;/Version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Authors&amp;gt;&lt;/span&gt;Dave Kerr&lt;span style="color:#f92672"&gt;&amp;lt;/Authors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Company&amp;gt;&lt;/span&gt;Dave Kerr&lt;span style="color:#f92672"&gt;&amp;lt;/Company&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageTags&amp;gt;&lt;/span&gt;Shell;SharpShell;COM;Context Menu;Icon Handler&lt;span style="color:#f92672"&gt;&amp;lt;/PackageTags&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Description&amp;gt;&lt;/span&gt;SharpShell is a framework that lets you build Windows Shell Extensions using .NET Core or the .NET Framework.&lt;span style="color:#f92672"&gt;&amp;lt;/Description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- ...snip... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This helps to keep a lot of the project dependency and property data in one place and is probably more convenient for many users.&lt;/p&gt;
&lt;p&gt;You can see the &lt;a href="https://github.com/dwmkerr/sharpgl/pull/177"&gt;Pull Request&lt;/a&gt; for SharpGL to see how the project files were updated in this case. You can also see the &lt;a href="https://github.com/dwmkerr/sharpshell/pull/331"&gt;SharpShell Pull Request&lt;/a&gt;. The SharpShell version is still work in progress at the time of writing.&lt;/p&gt;
&lt;h2 id="step-7---test-test-test"&gt;Step 7 - Test, Test, Test&lt;/h2&gt;
&lt;p&gt;Now for the fun part. You are going to &lt;em&gt;really&lt;/em&gt; have to test the new packages on each platform. Sadly, this kind of migration is not something which will have issues exposed via unit tests, you&amp;rsquo;ll need to create test projects which import your packages, ideally for each platform, and make sure they work. There could be runtime errors, particularly if you have made mistakes with the references.&lt;/p&gt;
&lt;p&gt;Many issues will be caught at compile time - some will not.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a screenshot of me having fun trying out the .NET Framework 4 package for WinForms, and the .NET Core 3.1 package for WPF:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/test-packages.png" alt="Screenshot: Testing SharpGL"&gt;&lt;/p&gt;
&lt;p&gt;How you test your packages will be very dependent on what you are building. If it is highly platform specific then you will likely have to do lots of testing. If it is fairly self-contained code then you might be able to get away with some basic smoke testing.&lt;/p&gt;
&lt;h2 id="step-8---document-compatibility"&gt;Step 8 - Document Compatibility&lt;/h2&gt;
&lt;p&gt;If you are supporting multiple platforms and frameworks, it&amp;rsquo;s going to be a lot of help to consumers of your code if you can be very clear about &lt;em&gt;what is supported&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This may be more complex than you think. Your library may run fine as part of a .NET Core Console Application on Windows - but does it work on MacOS? What about Linux?&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a screenshot I would never have imagined when I started the SharpGL project - a terminal application running on MacOS which is using the &lt;code&gt;SharpGL.Serialization&lt;/code&gt; library to load geometry from a file:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/sharpgl-on-mac.png" alt="Screenshot: Loading Geometry in SharpGL on MacOS"&gt;&lt;/p&gt;
&lt;p&gt;Now of course for something like SharpGL to run on a Mac or Linux, a lot more work would be needed. SharpGL is at its core nothing more than a wrapper around &lt;code&gt;opengl32.dll&lt;/code&gt; on Windows, on other platforms there are no DLLs, but OpenGL &lt;em&gt;is&lt;/em&gt; still available. So support is possible, but not ready yet. So at this stage, docmenting what you know works &lt;em&gt;as well as what doesn&amp;rsquo;t&lt;/em&gt; will be really helpful.&lt;/p&gt;
&lt;p&gt;You might also want to preserve your &amp;lsquo;pre-migration&amp;rsquo; code in a separate branch, in case you have users who for some reason have issues migrating and need to use an older version. For SharpGL, I updated the project page to indicate compatibility, what has been tested and so on:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/readme-compatability.png" alt="Screenshot: SharpGL README showing compatibility information"&gt;&lt;/p&gt;
&lt;h1 id="the-key-learnings"&gt;The Key Learnings&lt;/h1&gt;
&lt;p&gt;Here are the key learnings which stood out for me as I worked on migration of these projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consumer Experience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you are careful, you don&amp;rsquo;t have to break anything for consumers - with multi-targeting you can &lt;em&gt;still&lt;/em&gt; target older frameworks.&lt;/li&gt;
&lt;li&gt;You can potentially greatly increase the compatibility of your projects by offering support for .NET Core.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to upgrade to Visual Studio 2019&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;however, you can use Visual Studio for Mac or even the command-line to build across many platforms.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Maintainer Experience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You will have a much larger set of potential consumers, but you will likely find bugs which are framework or platform specific.&lt;/li&gt;
&lt;li&gt;You will likely need to work on migrating your project files and use the latest &lt;code&gt;dotnet&lt;/code&gt; tooling.&lt;/li&gt;
&lt;li&gt;You should be careful to document known compatibility issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in all, the process was less painful than I expected. Now that this work is done I can focus on more exciting things, such as potentially getting projects like SharpGL working on Linux or MacOS, which is much more exciting.&lt;/p&gt;
&lt;p&gt;As always, questions, comments, suggestions, rants, anything are welcome!&lt;/p&gt;
&lt;p&gt;The pull request which migrates the SharpGL project and SharpShell projects are below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dwmkerr/sharpgl/pull/177/"&gt;github.com/dwmkerr/sharpgl/pull/177/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dwmkerr/sharpshell/pull/331"&gt;github.com/dwmkerr/sharpshell/pull/331&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Useful References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/"&gt;Microsoft Docs: .NET Core Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;Microsoft Docs: Overview of porting from .NET Framework to .NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-target-frameworks"&gt;Microsoft Docs: Target frameworks in SDK-style projects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>CodeProject</category></item><item><title>Context Menu for Trello</title><link>https://dwmkerr.com/context-menu-for-trello/</link><pubDate>Thu, 27 Jun 2013 05:40:58 +0000</pubDate><guid>https://dwmkerr.com/context-menu-for-trello/</guid><description>&lt;p&gt;I&amp;rsquo;m on holiday at the moment, back in sunny England. Holiday may not be the right term really, I&amp;rsquo;m mostly working through charity stuff (for my charity &lt;a title="Namaste - Children's Homes Nepal" href="http://www.childrenshomesnepal.org/" target="_blank"&gt;Namaste - Children&amp;rsquo;s Homes Nepal&lt;/a&gt;) and company administration. I&amp;rsquo;m also starting working on a big new project, which is pretty exciting.&lt;/p&gt;
&lt;p&gt;Anyway, I got a nice message from a fellow coder &lt;a title="Goerge Hahn on Twitter" href="https://twitter.com/George_Hahn" target="_blank"&gt;George Hahn&lt;/a&gt; who has put together a pretty cool project that lets you send files directly to &lt;a title="Trello" href="https://trello.com/" target="_blank"&gt;Trello&lt;/a&gt; as an attachment to a card, or even as a new card. Here&amp;rsquo;s a screenshot of it in action:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/wp-content/uploads/2013/06/TrelloContextMenuExample.png"&gt;&lt;img src="images/TrelloContextMenuExample.png" alt="TrelloContextMenuExample" width="503" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a nice project, you can check it out on GitHub:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;iframe style="width: 170px; height: 30px;" src="http://ghbtns.com/github-btn.html?user=GeorgeHahn&amp;amp;repo=TrelloContextMenu&amp;amp;type=watch&amp;amp;count=true&amp;amp;size=large" height="30" width="170" frameborder="0" scrolling="0"&gt;&lt;/iframe&gt;&lt;/td&gt;
&lt;td&gt;&lt;iframe style="width: 170px; height: 30px;" src="http://ghbtns.com/github-btn.html?user=GeorgeHahn&amp;amp;repo=TrelloContextMenu&amp;amp;type=fork&amp;amp;count=true&amp;amp;size=large" height="30" width="170" frameborder="0" scrolling="0"&gt;&lt;/iframe&gt;&lt;/td&gt;
&lt;td&gt;&lt;iframe style="width: 240px; height: 30px;" src="http://ghbtns.com/github-btn.html?user=GeorgeHahn&amp;amp;type=follow&amp;amp;count=true&amp;amp;size=large" height="30" width="240" frameborder="0" scrolling="0"&gt;&lt;/iframe&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
What's also cool about this project is that it's the first project that someone's told me about that uses &lt;a title="SharpShell" href="https://sharpshell.codeplex.com/" target="_blank"&gt;SharpShell&lt;/a&gt;. Many people have got in touch with me about SharpShell (in fact, &lt;a title="SharpShell Context Menus on the CodeProject" href="http://www.codeproject.com/Articles/512956/NET-Shell-Extensions-Shell-Context-Menus" target="_blank"&gt;the SharpShell Context Menus article on the CodeProject&lt;/a&gt; is very popular), but so far this is the first real-world project where the writer got in touch after the project is completed.
&lt;p&gt;Thanks George, I look forward to seeing what else you&amp;rsquo;re working on!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Build Buttons for Facebook, Twitter, LinkedIn, GitHub and More!</title><link>https://dwmkerr.com/build-buttons-for-facebook-twitter-linkedin-github-and-more/</link><pubDate>Tue, 18 Jun 2013 02:34:59 +0000</pubDate><guid>https://dwmkerr.com/build-buttons-for-facebook-twitter-linkedin-github-and-more/</guid><description>&lt;p&gt;Recently I&amp;rsquo;ve been working on a small project called &lt;a title="Build Buttons" href="http://www.buildbuttons.com" target="_blank"&gt;Build Buttons&lt;/a&gt;. &lt;a title="Build Buttons" href="http://www.buildbuttons.com" target="_blank"&gt;Build Buttons&lt;/a&gt; is a website that let&amp;rsquo;s you quickly create buttons for sharing and promoting content. You can use Build Buttons to create Facebook &amp;lsquo;Like&amp;rsquo; or &amp;lsquo;Follow&amp;rsquo; buttons, LinkedIn &amp;lsquo;Share&amp;rsquo; buttons, Google +1 buttons, GitHub Star, Fork and Follow buttons and more. Here&amp;rsquo;s how it works.&lt;/p&gt;
&lt;p&gt;First, go to &lt;a title="Build Buttons" href="http://www.buildbuttons.com" target="_blank"&gt;&lt;a href="https://www.buildbuttons.com"&gt;www.buildbuttons.com&lt;/a&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/wp-content/uploads/2013/06/buildbuttons.jpg"&gt;&lt;img src="images/buildbuttons.jpg" alt="Build Buttons" width="800" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now choose the kind of buttons you want, in this example we&amp;rsquo;ll select &amp;lsquo;Social Media&amp;rsquo; from the top menu:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/wp-content/uploads/2013/06/socialmedia.jpg"&gt;&lt;img src="images/socialmedia.jpg" alt="Social Media Buttons" width="800" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On each category page, there&amp;rsquo;s a list of the different types of buttons that can be built. Social Media includes the &amp;lsquo;Share&amp;rsquo; button set. Click &amp;lsquo;Build It!&amp;rsquo;:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/wp-content/uploads/2013/06/sharebuttons.jpg"&gt;&lt;img src="images/sharebuttons.jpg" alt="Social Media Button Settings" width="733" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now just fill in the details to customise your buttons, enter a URL and select the sort of buttons you want to include. When you&amp;rsquo;re ready to see how your buttons look, choose &amp;lsquo;Build it!&amp;rsquo;:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/wp-content/uploads/2013/06/results.jpg"&gt;&lt;img src="images/results.jpg" alt="Build Buttons Results" width="597" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You get a working preview of how your buttons look, and a text box that includes the HTML you need to drop into your webpage or blog, easy!&lt;/p&gt;
&lt;p&gt;Build Buttons has quite a few different types of buttons you can create. You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style="line-height: 14px;"&gt;Create a set of social media buttons&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Create Facebook Like and Follow buttons&lt;/li&gt;
&lt;li&gt;Create Google +1 buttons&lt;/li&gt;
&lt;li&gt;Create LinkedIn Share buttons&lt;/li&gt;
&lt;li&gt;Create GitHub Star, Follow and Fork buttons&lt;/li&gt;
&lt;/ul&gt;</description><category>CodeProject</category></item><item><title>Getting Paths for Files in NUnit Tests</title><link>https://dwmkerr.com/getting-paths-for-files-in-nunit-tests/</link><pubDate>Thu, 02 May 2013 05:22:45 +0000</pubDate><guid>https://dwmkerr.com/getting-paths-for-files-in-nunit-tests/</guid><description>&lt;p&gt;When using NUnit, sometimes you will want to access files in the test project. These might be xml files with data, assembly references or whatever. Now typically, NUnit will actually copy the files it thinks it needs into a temporary location. This causes the problem that you can then do things like use a relative path to get files in the project. You can use manifest resource streams but sometimes this just isn&amp;rsquo;t suitable.&lt;/p&gt;
&lt;p&gt;To get the path of the root of your test project, you can use the snippet below. Make sure you call it in a unit test fixture that&amp;rsquo;s actually in your test project, not from a class referenced in another project!&lt;/p&gt;
&lt;p&gt;This class, &amp;lsquo;TestHelper&amp;rsquo; can be included in a Unit Test project to let you quickly get the path to the test project.&lt;/p&gt;
&lt;p&gt;[code lang=&amp;ldquo;csharp&amp;rdquo;]public static class TestHelper
{
public static string GetTestsPath()
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase).Replace(@&amp;quot;file:&amp;amp;quot;, string.Empty);
}
}[/code]&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Introducing FireKeys</title><link>https://dwmkerr.com/introducing-firekeys/</link><pubDate>Mon, 11 Mar 2013 11:11:29 +0000</pubDate><guid>https://dwmkerr.com/introducing-firekeys/</guid><description>&lt;p&gt;I don&amp;rsquo;t know when I learnt that Windows + E opened up Windows Explorer. It must have been a while ago. But it&amp;rsquo;s imprinted in my muscle memory, the number of times I hit that combo every day is probably quite high. But how many other hotkeys do I use? Asides from a few other functional ones, like Win + D, I don&amp;rsquo;t use hotkeys so much. And I got to thinking, I&amp;rsquo;d love to open Google Chrome with a hotkey just like I do with explorer.&lt;/p&gt;
&lt;p&gt;So I wrote FireKeys - a lightweight application that lets you assign hotkeys to actions. These actions could be opening program, a folder or a URL, but the underlying model is designed to be extensible.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/wp-content/uploads/2013/03/FireKeysMain.jpg"&gt;&lt;img src="images/FireKeysMain.jpg" alt="FireKeysMain" width="600" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can get the tool from the &lt;a title="FireKeys" href="http://www.dwmkerr.com/firekeys/"&gt;FireKeys&lt;/a&gt; page. There&amp;rsquo;s an article on how it was developed on the CodeProject, &lt;a href="http://www.codeproject.com/Articles/559500/FireKeys-Open-Programs-Folders-or-URLs-with-Hot-Ke"&gt;FireKeys - Open Programs, Folders and URLs with Hot Keys&lt;/a&gt;.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Spider Solitaire and Augmented Reality</title><link>https://dwmkerr.com/spider-solitaire-and-augmented-reality/</link><pubDate>Mon, 25 Feb 2013 16:16:20 +0000</pubDate><guid>https://dwmkerr.com/spider-solitaire-and-augmented-reality/</guid><description>&lt;p&gt;A while ago, I made an implementation of Solitaire and Spider Solitaire using WPF and my Apex MVVM library. I wrote about it on the CodeProject, in an article called &lt;a title="Solitaire and Spider Solitaire for WPF" href="http://www.codeproject.com/Articles/252152/Solitaire-and-Spider-Solitaire-for-WPF"&gt;Solitaire and Spider Solitaire for WPF&lt;/a&gt; (imaginative title indeed).&lt;/p&gt;
&lt;p&gt;Anyway, just recently I got a very interesting message from rupam rupam, who has made an augmented reality version of the project! In his application, you use your webcam to play the game physically by picking up cards with gestures. Other gestures, like thumbs up and thumbs down are bound to commands in the game - here&amp;rsquo;s a screenshot:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=wCOjuPdBooI"&gt;&lt;img src="images/SpiderAugmented.jpg" alt="SpiderAugmented" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The project is called GesCard and as far as I know there isn&amp;rsquo;t a page showing the code - but there are more links on the YouTube video for the page. Check out the YouTube video with the link here &lt;a href="https://www.youtube.com/watch?v=wCOjuPdBooI"&gt;&lt;a href="https://www.youtube.com/watch?v=wCOjuPdBooI"&gt;https://www.youtube.com/watch?v=wCOjuPdBooI&lt;/a&gt;&lt;/a&gt;. Thanks to rupam for getting in touch and sharing this very cool code!&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;</description><category>CodeProject</category></item><item><title>Creating Info Tip Handlers with .NET</title><link>https://dwmkerr.com/creating-info-tip-handlers-with-net/</link><pubDate>Mon, 14 Jan 2013 03:47:44 +0000</pubDate><guid>https://dwmkerr.com/creating-info-tip-handlers-with-net/</guid><description>&lt;p&gt;I have just added an article to the CodeProject that discusses how to create Info Tip shell extensions in .NET. These extensions are used by the shell to customise the tooltips shown over shell items.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/2013/01/creating-info-tip-handlers-with-net/shellinfotiphandler/" rel="attachment wp-att-210"&gt;&lt;img src="images/ShellInfoTipHandler.png" alt="ShellInfoTipHandler" width="385" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The article shows how you can use &lt;a title="SharpShell on CodePlex" href="http://sharpshell.codeplex.com"&gt;SharpShell &lt;/a&gt;to very quickly create these extensions, you can find it at: &lt;a title="Shell Info Tip Handlers" href="http://www.codeproject.com/Articles/527058/NET-Shell-Extensions-Shell-Info-Tip-Handlers"&gt;&lt;a href="http://www.codeproject.com/Articles/527058/NET-Shell-Extensions-Shell-Info-Tip-Handlers"&gt;http://www.codeproject.com/Articles/527058/NET-Shell-Extensions-Shell-Info-Tip-Handlers&lt;/a&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So just how easy does SharpShell make creating Shell Info Tip Handlers? The answer is pretty easy indeed. The code below shows the &lt;strong&gt;full &lt;/strong&gt;implementation of a Shell Info Tip Handler that changes the tooltips for folders to show the name of the folder and the number of items it contains:&lt;/p&gt;
&lt;p&gt;[csharp]/// &amp;lt;summary&amp;gt;
/// The FolderInfoTip handler is an example SharpInfoTipHandler that provides an info tip
/// for folders that shows the number of items in the folder.
/// &amp;lt;/summary&amp;gt;
[ComVisible(true)]
[COMServerAssociation(AssociationType.Directory)]
public class FolderInfoTipHandler : SharpInfoTipHandler
{
/// &amp;lt;summary&amp;gt;
/// Gets info for the selected item (SelectedItemPath).
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;infoType&amp;quot;&amp;gt;Type of info to return.&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;singleLine&amp;quot;&amp;gt;if set to &amp;lt;c&amp;gt;true&amp;lt;/c&amp;gt;, put the info in a single line.&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;
/// Specified info for the selected file.
/// &amp;lt;/returns&amp;gt;
protected override string GetInfo(RequestedInfoType infoType, bool singleLine)
{
// Switch on the tip of info we need to provide.
switch (infoType)
{
case RequestedInfoType.InfoTip:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; // Format the formatted info tip.
return string.Format(singleLine
? &amp;amp;quot;{0} - {1} Items&amp;amp;quot;
: &amp;amp;quot;{0}&amp;amp;quot; + Environment.NewLine + &amp;amp;quot;Contains {1} Items&amp;amp;quot;,
Path.GetFileName(SelectedItemPath), Directory.GetFiles(SelectedItemPath).Length);
case RequestedInfoType.Name:
// Return the name of the folder.
return string.Format(&amp;amp;quot;Folder '{0}'&amp;amp;quot;, Path.GetFileName(SelectedItemPath));
default:
// We won't be asked for anything else, like shortcut paths, for folders, so we
// can return an empty string in the default case.
return string.Empty;
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;} [/csharp]&lt;/p&gt;
&lt;p&gt;As you can see, all of the COM interfaces are hidden away and handled for you, there is no ugly pinvoke code and no use of strange structures imported from Win32. SharpShell handles all of the plumbing for you.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>SharpShell</title><link>https://dwmkerr.com/sharpshell/</link><pubDate>Tue, 08 Jan 2013 16:28:05 +0000</pubDate><guid>https://dwmkerr.com/sharpshell/</guid><description>&lt;p&gt;SharpShell is a project that I have recently uploaded to CodePlex. This class library, and set of tools and samples, is designed to be a framework to enable rapid development of Shell Extensions using the .NET Framework. In time it may grow to contain some functionality for using Shell entities within managed applications (for example, allowing an Explorer context menu to be built dynamically for a given path).&lt;/p&gt;
&lt;p&gt;Anyway, the code is all at &lt;a title="SharpShell on CodePlex" href="http://sharpshell.codeplex.com" target="_blank"&gt;sharpshell.codeplex.com&lt;/a&gt;. You can also see a nice article on the CodeProject that show&amp;rsquo;s how to create a Shell Context Menu Extension using C#, the article is at: &lt;a title=".NET Shell Extensions - Shell Context Menus" href="http://www.codeproject.com/Articles/512956/NET-Shell-Extensions-Shell-Context-Menus" target="_blank"&gt;.NET Shell Extensions - Shell Context Menus&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/2013/01/sharpshell/screenshot1_exampleiconhandler/" rel="attachment wp-att-200"&gt;&lt;img src="images/Screenshot1_ExampleIconHandler.png" alt="Screenshot1_ExampleIconHandler" width="515" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Above: An example of a Managed Shell Extension. This sample colours the icons for dlls differently, depending on whether they are native dlls or assemblies.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So far, in the repo on CodePlex there are also samples for Shell Icon Handlers (which customise icons in Explorer) and Shell Info Tip Handlers (which customise tooltips). Both of these extension types are fully supported in the current dev version and will be released in the next few days. There&amp;rsquo;s also a partially functioning Shell Property Sheet implementation which will be delivered in the subsequent version. The Shell Property Sheet introduces some particularly strange code - 32 and 64 bit C++ dlls are embedded as manifest resource streams and extracted as needed to provide access to C++ function pointers - ouch.&lt;/p&gt;
&lt;p&gt;More to follow - check out the project and the article.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>The GAC Manager</title><link>https://dwmkerr.com/the-gac-manager/</link><pubDate>Sun, 29 Jul 2012 08:44:00 +0000</pubDate><guid>https://dwmkerr.com/the-gac-manager/</guid><description>&lt;p&gt;I have started a new project on CodePlex called 'GAC Manager'. This is a project that is in two parts, the first is a simple tool to allow users to manipulate their local global assembly cache, the second is an API that provides the core functionality.&lt;/p&gt;
&lt;p&gt;Here's a screenshot of the tool in its current state:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/1_TheGacManagerTool.png" /&gt;&lt;/p&gt;
&lt;p&gt;An article on the project is available on the CodeProject at:&amp;nbsp;&lt;a href="http://www.codeproject.com/Articles/430568/A-GAC-Manager-Utility-and-API"&gt;http://www.codeproject.com/Articles/430568/A-GAC-Manager-Utility-and-API&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The project itself is at:&amp;nbsp;&lt;a href="https://github.com/dwmkerr/gacmanager"&gt;https://github.com/dwmkerr/gacmanager&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As always, comments, feature requests and so on are welcome!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Apex Part 2 - Adding Commands to an MVVM Application</title><link>https://dwmkerr.com/apex-part-2-adding-commands-to-an-mvvm-application/</link><pubDate>Tue, 15 May 2012 07:43:00 +0000</pubDate><guid>https://dwmkerr.com/apex-part-2-adding-commands-to-an-mvvm-application/</guid><description>&lt;p&gt;In Part 2 of my video tutorial series on using Apex I show you how you can add commands to an MVVM application. Commands let you rapidly add functionality to a ViewModel, whilst still maintaining separation from UI.&lt;/p&gt;
&lt;p&gt;&lt;iframe src="http://www.youtube.com/embed/wt7nncMNRG8" frameborder="0" width="420" height="315"&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;A CodeProject article to accompany this video will be published shortly.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Apex Part 1 - Getting Started with the Apex SDK</title><link>https://dwmkerr.com/apex-part-1-getting-started-with-the-apex-sdk/</link><pubDate>Mon, 23 Apr 2012 05:35:00 +0000</pubDate><guid>https://dwmkerr.com/apex-part-1-getting-started-with-the-apex-sdk/</guid><description>&lt;p&gt;Create an MVVM application in minutes with the new Apex SDK!&lt;/p&gt;
&lt;p&gt;The video below shows this in action, see what you think.&lt;/p&gt;
&lt;p&gt;&lt;iframe src="http://www.youtube.com/embed/m4cx9w5fiwk" frameborder="0" width="420" height="315"&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p&gt;There is also an accompanying article that describes what's going on. As always, would love to hear feedback!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.codeproject.com/Articles/371217/Apex-Part-1-Create-Your-First-MVVM-Application"&gt;http://www.codeproject.com/Articles/371217/Apex-Part-1-Create-Your-First-MVVM-Application&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Come on MS - Improve MFC</title><link>https://dwmkerr.com/come-on-ms-improve-mfc/</link><pubDate>Thu, 19 Apr 2012 09:02:00 +0000</pubDate><guid>https://dwmkerr.com/come-on-ms-improve-mfc/</guid><description>&lt;p&gt;Loads of developers still use MFC. OK - if you're writing a new project, MFC would not be a great choice. But what if you're maintaining a 1.5 million line MFC app?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;MFC support in Visual Studio has barely improved since VC++ 6.0 - in fact its got worse. Their cursory attempt to show an effort by adding support for the Ribbon Control with the MFC feature pack was not enough. Why can we still not properly use tab controls in the dialog editor?&lt;/p&gt;
&lt;p&gt;Those who use MFC are probably supporting big enterprise applications - for a long time now we've been neglected. Please vote for more MFC support in Visual Studio Uservoice below:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2782934-improve-mfc"&gt;http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2782934-improve-mfc&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Will they listen? Chances are not - unless lots of people vote. But I'd really like to see some effort on this, it's a technology still used by many.&lt;/p&gt;
&lt;p&gt;It would be interesting to see a survey of enterprise applications - and what they're written in. It'd be interesting to then compare this to how well MS support that platform. MS will put lots of efforts into what they &lt;em&gt;think &lt;/em&gt;that developers &lt;em&gt;should &lt;/em&gt;be using - but how well are they supporting their real customers who are creating real products?&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Could not load file or assembly 'System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' or one of its dependencies.</title><link>https://dwmkerr.com/could-not-load-file-or-assembly-system-windows-version2-0-5-0-cultureneutral-publickeytoken7cec85d7bea7798e-or-one-of-its-dependencies/</link><pubDate>Mon, 16 Apr 2012 07:55:00 +0000</pubDate><guid>https://dwmkerr.com/could-not-load-file-or-assembly-system-windows-version2-0-5-0-cultureneutral-publickeytoken7cec85d7bea7798e-or-one-of-its-dependencies/</guid><description>&lt;p&gt;Are you getting the error below when working with Silverlight projects?&lt;/p&gt;
&lt;pre&gt;Could not load file or assembly 'System.Windows, Version=2.0.5.0, &lt;br /&gt;Culture=neutral, PublicKeyToken=7cec85d7bea7798e' or&lt;br /&gt; one of its dependencies.&lt;/pre&gt;
&lt;p&gt;It's a bit of an odd one. The solution that works for me is to re-register System.Core and System.Windows in the GAC. Use the commands below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;32 Bit System&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil" /i "C:\Program Files\Microsoft Silverlight\4.1.10111.0\System.Core.dll"&lt;br /&gt;"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil" /i "C:\Program Files\Microsoft Silverlight\4.1.10111.0\System.Windows.dll"&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;64 Bit System&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil" /i "C:\Program Files\Microsoft Silverlight\4.1.10111.0\System.Core.dll"&lt;br /&gt;"C:\Program Files&amp;nbsp;(x86)\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil" /i "C:\Program Files\Microsoft Silverlight\4.1.10111.0\System.Windows.dll"&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;So far I am yet to understand why this happens - if anyone can shed any light please comment!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Embedding a Console in a C# application</title><link>https://dwmkerr.com/embedding-a-console-in-a-c-application/</link><pubDate>Tue, 28 Feb 2012 05:29:00 +0000</pubDate><guid>https://dwmkerr.com/embedding-a-console-in-a-c-application/</guid><description>&lt;p&gt;I have uploaded a new article on the CodeProject - embedding a Console in a C# Application. Here's a screenshot of the control in use:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/Screenshot_ConsoleControlSample.png" /&gt;&lt;/p&gt;
&lt;p&gt;As always, comments and suggestions are more than welcome - you can find the article here:&amp;nbsp;&lt;a href="http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application?msg=4169170#xx4168613xx"&gt;http://www.codeproject.com/Articles/335909/Embedding-a-Console-in-a-C-Application&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>SharpGL 2.0</title><link>https://dwmkerr.com/sharpgl-2-0/</link><pubDate>Wed, 22 Feb 2012 03:42:00 +0000</pubDate><guid>https://dwmkerr.com/sharpgl-2-0/</guid><description>&lt;p&gt;SharpGL 2.0 has been released - hit the GitHub site to get it:&amp;nbsp;&lt;a href="https://github.com/dwmkerr/sharpgl"&gt;https://github.com/dwmkerr/sharpgl&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some new features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full support for all OpenGL functions up to OpenGL 4.2&lt;/li&gt;
&lt;li&gt;Full support for all commonly used OpenGL extensions&lt;/li&gt;
&lt;li&gt;Support for WinForms applications&lt;/li&gt;
&lt;li&gt;Support for WPF applications (without resorting to WinForms hosts)&lt;/li&gt;
&lt;li&gt;A powerful scene graph including polygons, shaders, NURBs and more&lt;/li&gt;
&lt;li&gt;Many sample applications as starting points for your own projects.&lt;/li&gt;
&lt;li&gt;Visual Studio Extension with SharpGL project templates for WPF and WinForms.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;And a few screenshots:&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;The Radial Blur Sample&lt;/div&gt;
&lt;p&gt;&lt;img src="images/RadialBlurSample.png" /&gt;&lt;/p&gt;
&lt;p&gt;The New Project Types&lt;/p&gt;
&lt;p&gt;&lt;img src="images/NewWpfApplication.png" /&gt;&lt;/p&gt;
&lt;p&gt;WPF Support&lt;/p&gt;
&lt;p&gt;&lt;img src="images/TeapotSample.png" /&gt;&lt;/p&gt;
&lt;p&gt;Text Rendering&lt;/p&gt;
&lt;p&gt;&lt;img src="images/TextRenderingSample.png" /&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Debugger:: An unhandled non-continuable exception was thrown during process load</title><link>https://dwmkerr.com/debugger-an-unhandled-non-continuable-exception-was-thrown-during-process-load/</link><pubDate>Wed, 08 Feb 2012 06:54:00 +0000</pubDate><guid>https://dwmkerr.com/debugger-an-unhandled-non-continuable-exception-was-thrown-during-process-load/</guid><description>&lt;p&gt;The following exception can be a very tricky one to deal with:&lt;/p&gt;
&lt;pre&gt;Debugger:: An unhandled non-continuable exception was thrown during process load&lt;/pre&gt;
&lt;p&gt;here's some tips if you get it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Are you linking to winmm.lib? If so avoid it - it can cause these problems.&lt;/li&gt;
&lt;li&gt;Are you delay-loading the module? If not, try it - this can often resolve this issue if other modules like winmm.lib are interfering with the module that causes this exception.&lt;/li&gt;
&lt;li&gt;Are you using C++/CLI for the excepting module? If so, try using #pragma pack around exported class definitions.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;If you haven't specified packing - do so. This is good practice anyway. I've used libraries that change the packing (which is very bad behaviour) before and this has caused all sorts of problems, so try and do the following:&lt;/div&gt;
&lt;div&gt;
&lt;pre class="brush: c-sharp;"&gt;// Push packing options, specify the packing.
#pragma pack(push, 1)
&lt;p&gt;// Exported class
class MY_API MyClass
{
public:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ...etc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;};&lt;/pre&gt;&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;// Restore packing options.
#pragma pack(pop)&lt;/pre&gt;
&lt;/div&gt;</description><category>CodeProject</category></item><item><title>Switch</title><link>https://dwmkerr.com/switch/</link><pubDate>Sat, 04 Feb 2012 03:53:00 +0000</pubDate><guid>https://dwmkerr.com/switch/</guid><description>&lt;p&gt;&lt;img src="images/Title.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I have written the second article in my series on Extending Visual Studio. In this article I describe how to create a Visual Studio Addin that allows you to switch between cpp/h files, WinForms code and designer, XAML and codebehind and so on. You can find the article on the CodeProject here:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.codeproject.com/Articles/324611/Extending-Visual-Studio-Part-2-Creating-Addins"&gt;http://www.codeproject.com/Articles/324611/Extending-Visual-Studio-Part-2-Creating-Addins&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;There is also a direct download page on this blog, you can get Switch from dwmkerr.com by going here:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dwmkerr.com/page/Switch.aspx"&gt;http://www.dwmkerr.com/page/Switch.aspx&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Funky WPF - Enumerations and the Combo Box</title><link>https://dwmkerr.com/funky-wpf-enumerations-and-the-combo-box/</link><pubDate>Wed, 18 Jan 2012 03:11:00 +0000</pubDate><guid>https://dwmkerr.com/funky-wpf-enumerations-and-the-combo-box/</guid><description>&lt;p class="MsoNormal"&gt;Binding a combo box to an enumeration in WPF is more work than it should be, creating an object data provider etc etc:&lt;/p&gt;
&lt;pre class="brush: xml;"&gt;&amp;lt;Window.Resources&amp;gt;
&amp;lt;ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="CharacterEnumValues"&amp;gt;
&amp;lt;ObjectDataProvider.MethodParameters&amp;gt;
&amp;lt;x:Type TypeName="Character" /&amp;gt;
&amp;lt;/ObjectDataProvider.MethodParameters&amp;gt;
&amp;lt;/ObjectDataProvider&amp;gt;
&amp;lt;/Window.Resources&amp;gt;&lt;/pre&gt;
&lt;p class="MsoNormal"&gt;Followed by&lt;/p&gt;
&lt;pre class="brush: xml;"&gt;&amp;lt;ComboBox SelectedItem="{Binding Character}"&lt;br /&gt; ItemsSource="{Binding &lt;br /&gt;Source={StaticResource CharacterValues}} "/&amp;gt;&lt;/pre&gt;
&lt;p class="brush: xml;"&gt;What a pain! I have just added 'EnumerationComboBox' to my Apex library - so now you can do this:&lt;/p&gt;
&lt;pre class="brush: xml;"&gt;&amp;lt;!-- The combo box, bound to an enumeration. --&amp;gt;
&amp;lt;apexControls:EnumerationComboBox &lt;br /&gt;SelectedEnumeration="{Binding Character}" /&amp;gt;&lt;/pre&gt;
&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;No need for an ObjectDataProvider, an items source or anything &amp;ndash; and if you decorate enum&amp;rsquo;s with the &amp;lsquo;[Description]&amp;rsquo; attribute, it&amp;rsquo;ll use the description in the combo.&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span lang="EN-US"&gt;There&amp;rsquo;s an article/download here for anyone who's interested:&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;a href="http://www.codeproject.com/KB/WPF/enumcombobox.aspx"&gt;http://www.codeproject.com/KB/WPF/enumcombobox.aspx&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Apex 1.2 Released</title><link>https://dwmkerr.com/apex-1-2-released/</link><pubDate>Tue, 01 Nov 2011 07:31:00 +0000</pubDate><guid>https://dwmkerr.com/apex-1-2-released/</guid><description>&lt;p&gt;Apex 1.2 has been released, with some new features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &amp;lsquo;Compatibility&amp;rsquo; namespace, which contains classes to address compatibility issues between WPF, Silverlight and WP7.&lt;/li&gt;
&lt;li&gt;The &amp;lsquo;Asynchronous Command&amp;rsquo; object, which provides a powerful way to use Asynchronous Commands in MVVM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The release is at: &lt;a href="https://github.com/dwmkerr/apex"&gt;https://github.com/dwmkerr/apex&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>MVVM Commanding</title><link>https://dwmkerr.com/mvvm-commanding/</link><pubDate>Sat, 29 Oct 2011 08:31:00 +0000</pubDate><guid>https://dwmkerr.com/mvvm-commanding/</guid><description>&lt;p&gt;I have written an article that describes commanding in WPF, Silverlight and WP7 in detail. It is on the CodeProject at:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.codeproject.com/KB/WPF/consistentmvvmcommands.aspx"&gt;http://www.codeproject.com/KB/WPF/consistentmvvmcommands.aspx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It uses the latest version of Apex (version 1.2) which will be released formally shortly. Enjoy!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>MVVM: Asynchronous Commands</title><link>https://dwmkerr.com/mvvm-asynchronous-commands/</link><pubDate>Mon, 24 Oct 2011 03:51:00 +0000</pubDate><guid>https://dwmkerr.com/mvvm-asynchronous-commands/</guid><description>&lt;p&gt;The latest cut of the Apex Code (&lt;a href="http://apex.codeplex.com/SourceControl/changeset/changes/6701"&gt;http://apex.codeplex.com/SourceControl/changeset/changes/6701&lt;/a&gt;) contains a very cool new feature - Asynchronous Command Objects.&lt;/p&gt;
&lt;p&gt;An Asynchronous Command is a ViewModelCommand - the standard object used in Apex for commanding. However, what is different about this function is that it runs Asynchronously.&lt;/p&gt;
&lt;p&gt;One of the problems with running a view model command asynchronously is that generally the view model properties cannot be accessed - as they're created on a different dispatcher. This problem is resolved by using the 'ReportProgress' function. Here's an example:&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;public class SomeViewModel : ViewModel
{
public SomeViewModel()
{
// Create the command.
asyncCommand = new AsynchronousCommand(DoAsyncCommand, true);
}
&lt;p&gt;private void DoAsyncCommand()
{
for(int i = 0; i &amp;lt; 100; i++)
{
// Perform some long operation.
string message = DoSomeLongOperation();&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; // Add the message to the View Model - safely!
asyncCommand.ReportProgress(
() =&amp;amp;gt;
{
messages.Add(message);
}
);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;private ObservableCollection&amp;lt;string&amp;gt; messages =
new ObservableCollection&amp;lt;string&amp;gt;();&lt;/p&gt;
&lt;p&gt;public ObservableCollection&amp;lt;string&amp;gt; Messages
{
get { return messages; }
}&lt;/p&gt;
&lt;p&gt;private AsynchronousCommand asyncCommand;&lt;/p&gt;
&lt;p&gt;public AsynchronousCommand AsyncCommand
{
get { return asyncCommand; }
}
}&lt;/pre&gt;&lt;/p&gt;
&lt;p class="brush: c-sharp;"&gt;In this basic mock-up we have a command called 'AsyncCommand' (which we could bind a button to for example) which invokes DoAsyncCommand. However, it invokes it Asynchronously. We can also update the ViewModel properties by using ReportProgress - meaning AsyncCommands can seamlessly provide live feedback while they're working - and we're keeping well locked in with the MVVM commanding model!&lt;/p&gt;
&lt;p class="brush: c-sharp;"&gt;Expect a full article soon on the CodeProject, until then the source is at:&lt;/p&gt;
&lt;p class="brush: c-sharp;"&gt;&lt;a href="http://apex.codeplex.com/SourceControl/changeset/changes/6701"&gt;http://apex.codeplex.com/SourceControl/changeset/changes/6701&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>CodeProject Competition</title><link>https://dwmkerr.com/codeproject-competition/</link><pubDate>Thu, 06 Oct 2011 07:30:00 +0000</pubDate><guid>https://dwmkerr.com/codeproject-competition/</guid><description>&lt;p&gt;My Solitaire and Spider Solitaire in WPF article is in two CodeProject competitions this month. The article is at:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.codeproject.com/Articles/252152/Solitaire-and-Spider-Solitaire-for-WPF"&gt;https://www.codeproject.com/Articles/252152/Solitaire-and-Spider-Solitaire-for-WPF&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you think the article is worthy of a vote, then please go to the voting page for either of the two competitions!&lt;/p&gt;
&lt;p&gt;Best C# Article: &lt;a href="http://www.codeproject.com/script/Surveys/VoteForm.aspx?srvid=1209"&gt;&lt;a href="http://www.codeproject.com/script/Surveys/VoteForm.aspx?srvid=1209"&gt;http://www.codeproject.com/script/Surveys/VoteForm.aspx?srvid=1209&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Best Overall Article: &lt;a href="http://www.codeproject.com/script/Surveys/VoteForm.aspx?srvid=1212"&gt;&lt;a href="http://www.codeproject.com/script/Surveys/VoteForm.aspx?srvid=1212"&gt;http://www.codeproject.com/script/Surveys/VoteForm.aspx?srvid=1212&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Drawing a DIB Section in WPF</title><link>https://dwmkerr.com/drawing-a-dib-section-in-wpf/</link><pubDate>Fri, 30 Sep 2011 05:09:00 +0000</pubDate><guid>https://dwmkerr.com/drawing-a-dib-section-in-wpf/</guid><description>&lt;p&gt;One of the most exciting new features in the forthcoming SharpGL 2.0 (which was actually planned for 2.1 but has been moved to 2.0) is the facility to do OpenGL drawing in a WPF control. This isn't done via a WinFormsHost (which has unpleasant side-effects due to Airspace, see&amp;nbsp;&lt;a href="http://msdn.microsoft.com/en-us/library/aa970688(v=VS.100).aspx"&gt;http://msdn.microsoft.com/en-us/library/aa970688(v=VS.100).aspx&lt;/a&gt;) but actually via an Image in a WPF UserControl.&lt;/p&gt;
&lt;p&gt;What does this mean? Well it means that when you use the SharpGL.WPF libraries OpenGLControl you get what is essentially a genuine WPF control - you can overlay other controls on top of it, with transparency and bitmap effects and do everything you'd normally be able to do with a WPF control.&lt;/p&gt;
&lt;p&gt;How this works is an interesting bit of code so here are the details.&lt;/p&gt;
&lt;p&gt;When using a WPF OpenGL control we render either using a DIBSectionRenderContextProvider, or a FBORenderContextProvider. Here's the difference:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DIBSectionRenderContextProvider&lt;/strong&gt;&amp;nbsp;- Renders directly to a DIB Section. Supported with any version of OpenGL but never hardware accelerated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FBORenderContextProvider&lt;/strong&gt;&amp;nbsp;- Renders to a Framebuffer object, via the GL_EXT_framebuffer_object extension. This is fully hardware accelerated but only supported in OpenGL 1.3 and upwards. The resultant framebuffer is copied into a DIB section also.&lt;/p&gt;
&lt;p&gt;With either render context provider we end up with a DIB section that contains the frame - here's how we can render it:&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;/// &amp;lt;summary&amp;gt;
/// Converts a &amp;lt;see cref="System.Drawing.Bitmap"/&amp;gt; into a WPF &amp;lt;see cref="BitmapSource"/&amp;gt;.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;remarks&amp;gt;Uses GDI to do the conversion. Hence the call to the marshalled DeleteObject.
/// &amp;lt;/remarks&amp;gt;
/// &amp;lt;param name="source"&amp;gt;The source bitmap.&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;A BitmapSource&amp;lt;/returns&amp;gt;
public static BitmapSource HBitmapToBitmapSource(IntPtr hBitmap)
{
BitmapSource bitSrc = null;
&lt;pre&gt;&lt;code&gt;try
{
bitSrc = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
catch (Win32Exception)
{
bitSrc = null;
}
finally
{
Win32.DeleteObject(hBitmap);
}
return bitSrc;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;This function allows us to turn a handle to a DIB section into a BitmapSource. The OpenGLControl is essentially just an image, and with each frame we simply set the BitmapSource to the newly rendered DIBSection.&lt;/p&gt;
&lt;p&gt;The version of the code this post relates to is:&amp;nbsp;&lt;a href="http://sharpgl.codeplex.com/SourceControl/changeset/view/4805"&gt;http://sharpgl.codeplex.com/SourceControl/changeset/view/4805&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The WPF example renders the Utah Teapot (&lt;a href="http://en.wikipedia.org/wiki/Utah_teapot"&gt;http://en.wikipedia.org/wiki/Utah_teapot&lt;/a&gt;) directly in a WPF application. We're still pre-beta but grab the code if you want to try OpenGL in WPF.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Importing OpenGL Extensions Functions with wglGetProcAddress</title><link>https://dwmkerr.com/importing-opengl-extensions-functions-with-wglgetprocaddress/</link><pubDate>Sat, 24 Sep 2011 06:57:00 +0000</pubDate><guid>https://dwmkerr.com/importing-opengl-extensions-functions-with-wglgetprocaddress/</guid><description>&lt;p&gt;There are only a small set of the core OpenGL functions that can be imported via p/invoke - the majority of OpenGL functions are actually extension functions which are supported only on specific video cards. OpenGL offers a function called&amp;nbsp;wglGetProcAddress which can return the address of a named function - but how do we deal with this in the managed world?&lt;/p&gt;
&lt;p&gt;Here's a brief description of how it's handled in SharpGL. As of this morning, SharpGL's latest version contains &lt;strong&gt;all &lt;/strong&gt;core functions up to OpenGL 4.2 and &lt;strong&gt;all &lt;/strong&gt;standard extensions up to OpenGL 4.2. This takes the support for OpenGL to the latest version - August 2011.&lt;/p&gt;
&lt;p&gt;First we must import the wglGetProcAddress function:&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;[DllImport("opengl32.dll")]
public static extern IntPtr wglGetProcAddress(string name);&lt;/pre&gt;
&lt;p&gt;This is the correect p/invoke method of importing this function, however it returns an IntPtr, which we cannot call as a function. We could change the return type to a delegate but this function can return essentially any type of delegate - so where do we go from here?&lt;/p&gt;
&lt;p&gt;Well the next step is to define the delegates we want to use - they must have exactly the same name as the OpenGL functions and use the correct parameters for marshalling. Here are a couple of delegates for OpenGL 1.4:&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;private delegate void glBlendFuncSeparate (uint sfactorRGB, uint dfactorRGB, uint sfactorAlpha, uint dfactorAlpha);
&lt;p&gt;private delegate void glMultiDrawArrays (uint mode, int[] first, int[] count, int primcount);&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;Now we must create a function which will turn an IntPtr into a delegate and invoke it:&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;/// &amp;lt;summary&amp;gt;
/// The set of extension functions.
/// &amp;lt;/summary&amp;gt;
private Dictionary&amp;lt;string, Delegate&amp;gt; extensionFunctions = new Dictionary&amp;lt;string, Delegate&amp;gt;();
&lt;p&gt;/// &amp;lt;summary&amp;gt;
/// Invokes an extension function.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;typeparam name=&amp;ldquo;T&amp;rdquo;&amp;gt;The extension delegate type.&amp;lt;/typeparam&amp;gt;
/// &amp;lt;param name=&amp;ldquo;args&amp;rdquo;&amp;gt;The arguments to the pass to the function.&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;The return value of the extension function.&amp;lt;/returns&amp;gt;
private object InvokeExtensionFunction&amp;lt;T&amp;gt;(params object[] args)
{
// Get the type of the extension function.
Type delegateType = typeof(T);&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Get the name of the extension function.
string name = delegateType.Name;
// Does the dictionary contain our extension function?
Delegate del = null;
if (extensionFunctions.ContainsKey(name) == false)
{
// We haven't loaded it yet. Load it now.
IntPtr proc = Win32.wglGetProcAddress(name);
if (proc == IntPtr.Zero)
throw new Exception(&amp;quot;Extension function &amp;quot; + name + &amp;quot; not supported&amp;quot;);
// Get the delegate for the function pointer.
del = Marshal.GetDelegateForFunctionPointer(proc, delegateType);
if (del == null)
throw new Exception(&amp;quot;Extension function &amp;quot; + name + &amp;quot; not supported&amp;quot;);
// Add to the dictionary.
extensionFunctions.Add(name, del);
}
// Get the delegate.
del = extensionFunctions[name];
// Try and invoke it.
object result = null;
try
{
result = del.DynamicInvoke(args);
}
catch
{
throw new Exception(&amp;quot;Cannot invoke extension function &amp;quot; + name);
}
return result;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;We now have a generalised way to invoke an extension function. The loaded functions are stored in a dictionary keyed by name so that the heavy lifting is only done the first time we try to invoke the function. &amp;nbsp;We can finally add the functions to the class as below:&lt;/p&gt;
&lt;pre class="brush: c-sharp;"&gt;public void BlendFuncSeparate(uint sfactorRGB, uint dfactorRGB, uint sfactorAlpha, uint dfactorAlpha)
{
InvokeExtensionFunction&amp;lt;glBlendFuncSeparate&amp;gt;(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha);
}
&lt;p&gt;public void MultiDrawArrays(uint mode, int[] first, int[] count, int primcount)
{
InvokeExtensionFunction&amp;lt;glMultiDrawArrays&amp;gt;(mode, first, count, primcount);
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;This is pretty cool - we can invoke any extension function as long as we have defined a delegate for it. What's more, by making the InvokeExtensionFunction function public we can allow other developers to provide their own delegates and invoke other extension functions.&lt;/p&gt;
&lt;p&gt;This is the technique used in SharpGL 2.0 to import extension functions - the Core/OpenGLExtensions.cs file contains thousands of lines of functions defined like this, however knowing how to invoke any kind of delegate is a useful skill in the managed world, so this trick could be used in other places.&lt;/p&gt;
&lt;p&gt;The version of SharpGL this post relates to is at:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sharpgl.codeplex.com/SourceControl/changeset/view/4474"&gt;http://sharpgl.codeplex.com/SourceControl/changeset/view/4474&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>How ISupportInitialize Can Help</title><link>https://dwmkerr.com/how-isupportinitialize-can-help/</link><pubDate>Sun, 18 Sep 2011 14:49:00 +0000</pubDate><guid>https://dwmkerr.com/how-isupportinitialize-can-help/</guid><description>&lt;p&gt;I have recently come to discover the &lt;a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.isupportinitialize.aspx"&gt;ISupportInitialize&lt;/a&gt; interface and found that it is extremely useful when developing more complicated WinForms controls.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the link to the interface on MSDN: &lt;a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.isupportinitialize.aspx"&gt;ISupportInitialize&lt;/a&gt; - here I&amp;rsquo;ll describe how it can be useful.&lt;/p&gt;
&lt;h3 id="the-problem"&gt;The Problem&lt;/h3&gt;
&lt;p&gt;I have a fairly complicated WinForms usercontrol called 'OpenGLControl', which allows OpenGL commands to be used to render 3D scenes in a C# WinForms application. The control has properties which are interdependent to each other. If these properties are set in the designer, code like this is generated:
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// openGLControl1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | System.Windows.Forms.AnchorStyles.Left)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | System.Windows.Forms.AnchorStyles.Right)));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.BitDepth = &lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.DrawRenderTime = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.FrameRate = &lt;span style="color:#ae81ff"&gt;29.41176F&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.Location = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; System.Drawing.Point(&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.Name = &lt;span style="color:#e6db74"&gt;&amp;#34;openGLControl1&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.RenderContextType = SharpGL.RenderContextType.NativeWindow;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.Size = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; System.Drawing.Size(&lt;span style="color:#ae81ff"&gt;768&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;379&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.TabIndex = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1.OpenGLDraw += &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; System.Windows.Forms.PaintEventHandler(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1_OpenGLDraw);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now this leads to a problem - BitDepth, OpenGLDraw, FrameRate etc must all be declared BEFORE the Size property is set - but how can we control this? Or how can we deal with this situation in general?&lt;/p&gt;
&lt;p&gt;This is where the ISupportInitialize interface comes in. If a control is added to the design surface with this interface, we&amp;rsquo;ll get the following code wrapped around the designer code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; InitializeComponent()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.ComponentModel.ComponentResourceManager resources = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; System.ComponentModel.ComponentResourceManager(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(FormExample1));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.label1 = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; System.Windows.Forms.Label();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.linkLabel1 = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; System.Windows.Forms.LinkLabel();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1 = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SharpGL.OpenGLControl();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ((System.ComponentModel.ISupportInitialize)(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1)).BeginInit();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.SuspendLayout();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ...ordianry designer code...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ((System.ComponentModel.ISupportInitialize)(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.openGLControl1)).EndInit();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.ResumeLayout(&lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.PerformLayout();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now just implement the ISupportInitialize interface in your control - in the &amp;lsquo;EndInit&amp;rsquo; function do any processing that depends on the interdependent properties. This is the earliest point that we can do processing like this. In certain circumstances, knowing about this interface can save you a lot of trouble.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>P/Invoke Performance</title><link>https://dwmkerr.com/pinvoke-performance/</link><pubDate>Mon, 12 Sep 2011 13:09:00 +0000</pubDate><guid>https://dwmkerr.com/pinvoke-performance/</guid><description>&lt;p&gt;SharpGL 2.0 has no P/Invoke - all native functions are called by a C++/CLI class library (OpenGLWrapper if you're getting the code from CodePlex) which calls functions directly. This means there's no more importing of PIXELFORMAT structures and so on.&lt;/p&gt;
&lt;p&gt;The thinking behind this was that a C++/CLI wrapper is faster than P/Invoke for a talkative API like OpenGL - but is this actually the case? In my new article on the CodeProject I investigate the performance differences between these two methods.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.codeproject.com/KB/dotnet/pinvokeperformance.aspx"&gt;http://www.codeproject.com/KB/dotnet/pinvokeperformance.aspx&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Trials and Tribulations with SharpGL 2.0</title><link>https://dwmkerr.com/trials-and-tribulations-with-sharpgl-2-0/</link><pubDate>Mon, 12 Sep 2011 06:03:00 +0000</pubDate><guid>https://dwmkerr.com/trials-and-tribulations-with-sharpgl-2-0/</guid><description>&lt;p&gt;SharpGL has not been updated for a while, the original CoreProject article is at: &lt;a href="http://www.codeproject.com/KB/openGL/sharpgl.aspx"&gt;http://www.codeproject.com/KB/openGL/sharpgl.aspx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Recently I have begun work on SharpGL 2.0, with plans to address some of the issues people have had with SharpGL 1.83. In preparation there is a public accessible repository on CodePlex: &lt;a href="http://sharpgl.codeplex.com/"&gt;http://sharpgl.codeplex.com/&lt;/a&gt;&amp;nbsp;check it soon, it will shortly be online.&lt;/p&gt;
&lt;p&gt;Trying to squeeze acceptible performance from SharpGL has so far been an interesting task, I have found out many interesting things on the way, I'll be posting small snippets as I work on SharpGL 2.0 describing how I'm improving the performance and structure of the library.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Solitaire and Spider Solitaire on the CodeProject</title><link>https://dwmkerr.com/solitaire-and-spider-solitaire-on-the-codeproject/</link><pubDate>Mon, 12 Sep 2011 06:01:00 +0000</pubDate><guid>https://dwmkerr.com/solitaire-and-spider-solitaire-on-the-codeproject/</guid><description>&lt;p&gt;I have uploaded a new article on the CodeProject, a step by step tutorial showing how to create Solitaire and Spider Solitaire for WPF with the help of Apex.&lt;/p&gt;
&lt;p&gt;The article is available at: &lt;a href="http://www.codeproject.com/KB/WPF/solitaire.aspx"&gt;http://www.codeproject.com/KB/WPF/solitaire.aspx&lt;/a&gt;&lt;/p&gt;</description><category>CodeProject</category></item></channel></rss>