Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Saturday, 16 March 2019

C# - Picking nice graph axes functionally

So I am revising XAML and I wanted to write a graph control. The first problem I encountered is how to pick nice numbers for your graph axis, the algorithm is not obvious and I had to google before I found a popular answer on Stack Overflow.

The original given code was quite classy when it needn't be given that we are not holding state. I think a functional implementation is more appropriate and so I have rewritten the C# code a class with static members only. I have abolished the class member variables and instead I am returning all the facts (short for artefacts) found in the function via an object array. I have been influenced by Python of late with its capability to return tuples but I have avoided the Tuple<> generic class because I didn't want to have to specify all the types ( I appreciate there is a small performance hot with the boxing).

using System;

namespace GraphIntervalTest
{
    class Program
    {
        static void Main(string[] args)
        {

            object[] facts = NiceScaleStatic.CalculateNiceScale(1.2813, 1.331, 8);  

            System.Console.Out.WriteLine("Tick Spacing:\t" + facts[0]);
            System.Console.Out.WriteLine("Nice Minimum:\t" + facts[1]);
            System.Console.Out.WriteLine("Nice Maximum:\t" + facts[2]);
            System.Console.Out.WriteLine("Tick Count:\t" + facts[3]);
            System.Console.Out.WriteLine("Min:\t" + facts[4]);
            System.Console.Out.WriteLine("Max:\t" + facts[5]);
            System.Console.Out.WriteLine("MaxTicks:\t" + facts[6]);
        }
    }

    public class NiceScaleStatic
    {
        public static object[] CalculateNiceScale(double min, double max, long maxTicks = 10)
        {
            double tickSpacing;
            double range;
            double niceMin;
            double niceMax;
            long tickCount;

            range = niceNum(max - min, false);
            tickSpacing = niceNum(range / (maxTicks - 1), true);
            niceMin = Math.Floor(min / tickSpacing) * tickSpacing;
            niceMax = Math.Ceiling(max / tickSpacing) * tickSpacing;

            tickCount = (long)((niceMax - niceMin) / tickSpacing);

            return new object[] { tickSpacing, niceMin, niceMax, tickCount, min, max, maxTicks };
        }

        static double niceNum(double range, bool round)
        {
            double exponent; /** exponent of range */
            double fraction; /** fractional part of range */
            double niceFraction;  /** nice, rounded fraction */

            exponent = Math.Floor(Math.Log10(range));
            fraction = range / Math.Pow(10, exponent);

            if (round)
            {
                if (fraction < 1.5)
                    niceFraction = 1;
                else if (fraction < 3)
                    niceFraction = 2;
                else if (fraction < 7)
                    niceFraction = 5;
                else
                    niceFraction = 10;
            }
            else
            {
                if (fraction <= 1)
                    niceFraction = 1;
                else if (fraction <= 2)
                    niceFraction = 2;
                else if (fraction <= 5)
                    niceFraction = 5;
                else
                    niceFraction = 10;
            }

            return niceFraction * Math.Pow(10, exponent);
        }
    }
}

Monday, 7 May 2018

C# - Async - Task<> can prevent proliferation of async methods

I am just preparing a post on retired Microsoft C# exams, and I wanted to probe the Microsoft Learning web pages, so I wrote a little program to GET the HTML from a series of potential exam numbers. I did not want to hit Microsoft's server with multiple simultaneous requests because it is bad manners so I choose to run the series synchronously but the library has async methods only and I did not want a proliferation of async keywords as recommended by the compiler error message.

The answer is to use the Task class but you need to use a generic Task, that is Task<string> otherwise the Result property is not available. This isn't immediately obvious and so IMHO worth a blog post. The console code follows...

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace MicrosoftLearning
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 480; i <= 499; i++)
            {
                string html = readExam(i);
                using (var fil = System.IO.File.CreateText(@"n:\microsoftexams" + i.ToString() + ".html"))
                {
                    fil.Write(html);
                }
            }
        }

        public static string readExam(int lExamNum)
        {
            string html = "";
            using (HttpClient client = new HttpClient())
            {
                try
                {
                    string sUrl = "https://www.microsoft.com/en-gb/learning/exam-70-" + lExamNum.ToString() + ".aspx";
                    Task<string> tskGet = client.GetStringAsync(sUrl);
                    tskGet.Wait();
                    html = tskGet.Result;
                }
                catch (Exception ex)
                {

                    if (ex.InnerException.Message == "Response status code does not indicate success: 404 (Not Found).")
                    {
                        //bury the error
                    }
                    else
                    {
                        throw new Exception("unanticipated error:", ex);
                    }
                }
            }
            return html;
        }
    }
}

Saturday, 5 May 2018

C# - COM Interop - Launching a MIDL process from C#

So I'd been given advice about modifying a type library's source idl and then recompiling the type library using midl.exe. This seemed straight forward enough except there were a number of issues to solve, most of which revolved around getting the right include and path directories. I decided to pack this information into a C# program and its source is given below.

The program is a console program and it is designed to be called in a post-build macro. So we pass in a .NET assembly's dll as argument 0 and for argument 1 we pass in the path of the source idl which itself has been copy and pasted from Oleview.exe. Further arguments are specific to the specific use case which amends a coclass with the appobject attribute meaning VBA com clients do not need to New the class.

Anyway, if you're here for the midl.exe launcher code then all you'll need is the code in the RunMidl() method. The rest of the code handles argument validation and rewriting the idl in order to add appobject to specific coclass's.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

namespace PostBuildTypeLibraryHelper
{
    class Program
    {
        static void RunMidl(string sMidlExeFilename, string sSourceIdlFilename, 
                string sTargetTblFilename , StreamWriter sw)
        {
            #region call midl.exe to re-compile the type library
            {

                Process process = new Process();
                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardOutput = true;
                startInfo.RedirectStandardError = true;
                startInfo.FileName = sMidlExeFilename;

                // midl docs https://msdn.microsoft.com/en-us/library/windows/desktop/aa366715(v=vs.85).aspx
                // midl.exe conmmand line reference https://msdn.microsoft.com/en-us/library/windows/desktop/aa367084(v=vs.85).aspx
                startInfo.Arguments = "\"" + sSourceIdlFilename + "\" /tlb \"" + sTargetTblFilename + "\"";

                startInfo.EnvironmentVariables["PATH"] = startInfo.EnvironmentVariables["PATH"]
                    + @";C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\" /* home of cl.exe */;


                startInfo.EnvironmentVariables["INCLUDE"] = startInfo.EnvironmentVariables["INCLUDE"]
                    + @";C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\um\" /* home of oaidl.idl */
                    + @";C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\shared\" /* home of wtypes.idl */
                    + @";C:\Windows\Microsoft.NET\Framework\v4.0.30319" /* home of mscorlib.tlb */;
                process.StartInfo = startInfo;
                process.Start();
                process.WaitForExit();

                sw.WriteLine("midl exit code:" + process.ExitCode + '\n');
                string sStdErr = process.StandardError.ReadToEnd();
                sw.WriteLine("midl standard err:\n" + sStdErr);
                string sStdOut = process.StandardOutput.ReadToEnd();
                sw.WriteLine("midl standard out:\n" + sStdOut);

                var vSplitPath = startInfo.EnvironmentVariables["PATH"].Split(';');
                sw.WriteLine("PATH:\n" + String.Join(";\n", vSplitPath) + '\n');

                var vSplitInclude = startInfo.EnvironmentVariables["INCLUDE"].Split(';');
                sw.WriteLine("INCLUDE:\n" + String.Join(";\n", vSplitInclude) + '\n');

            }
            #endregion

        }


        static void Main(string[] args)
        {
            using (StreamWriter sw = System.IO.File.CreateText(@"n:\log.txt"))
            {
                try
                {
                    string sTargetDllFilename;
                    string sTargetTblFilename;
                    string sSourceIdlFilename;

                    string sMidlExeFilename;

                    bool bChangesMadeToIdl = false;
                    List sAppObject = new List();

                    List sSourceIdl = new List();

                    Dictionary dicCoClass = new Dictionary();


                    #region ParseArguments
                    {
                        if (args.Length < 2) { throw new Exception("#Expecting arg0:{target dll filename}, arg1: {source idl filename}!"); }

                        sTargetDllFilename = args[0];
                        if (!System.IO.File.Exists(sTargetDllFilename)) { throw new Exception("#target dll filename " + sTargetDllFilename + " does not exist!"); }

                        {
                            System.IO.FileInfo dll = new FileInfo(sTargetDllFilename);
                            sTargetTblFilename = dll.Directory + "\\" + Path.GetFileNameWithoutExtension(sTargetDllFilename) + ".tlb";
                        }
                        if (!System.IO.File.Exists(sTargetDllFilename)) { throw new Exception("#target tlb filename (derived from target dll filename) " + sTargetTblFilename + " does not exist!"); }

                        sSourceIdlFilename = args[1];
                        if (!System.IO.File.Exists(sSourceIdlFilename)) { throw new Exception("#source idl filename " + sSourceIdlFilename + " does not exist!"); }

                        sMidlExeFilename = @"C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x86\midl.exe";
                        if (!System.IO.File.Exists(sMidlExeFilename)) { throw new Exception("#midl.exe " + sMidlExeFilename + " does not exist!"); }

                        for (int i = 2; i < args.Length; i++)
                        {
                            sAppObject.Add(args[i]);
                        }

                        sw.WriteLine("sTargetDllFilename:\t" + sTargetDllFilename);
                        sw.WriteLine("sTargetTblFilename:\t" + sTargetTblFilename);
                        sw.WriteLine("sSourceIdlFilename:\t" + sSourceIdlFilename);
                        sw.WriteLine("sMidlExeFilename:\t" + sMidlExeFilename);
                        sw.WriteLine("append appObject attribute to the following:");
                        for (int i = 0; i < sAppObject.Count; i++)
                        {
                            sw.WriteLine(sAppObject[i]);
                        }
                        sw.WriteLine();
                    }
                    #endregion

                    #region Read Idl source
                    {
                        string line;
                        int counter = 0;
                        using (StreamReader file = new StreamReader(sSourceIdlFilename))
                        {
                            while ((line = file.ReadLine()) != null)
                            {
                                sSourceIdl.Add(line);
                                if (line.StartsWith("    coclass "))
                                {
                                    string[] parts = line.Trim().Split(' ');
                                    dicCoClass.Add(parts[1], counter);
                                }
                                counter++;
                            }
                            file.Close();
                        }
                        sw.WriteLine("count of idl source lines :\t" + sSourceIdl.Count + '\n');
                    }
                    #endregion

                    #region look for classes to rewrite
                    {
                        for (int i = 0; i < sAppObject.Count; i++)
                        {
                            string sAppObjectLoop = sAppObject[i];
                            if (dicCoClass.ContainsKey(sAppObjectLoop))
                            {
                                int lCoClassLine = dicCoClass[sAppObjectLoop];
                                // so if we have already been here read a few lines back
                                const string sEXTRALINE = @"      ,appobject";

                                if (!sSourceIdl[lCoClassLine - 2].StartsWith(sEXTRALINE))
                                {
                                    sSourceIdl.Insert(lCoClassLine - 1, sEXTRALINE);
                                    bChangesMadeToIdl = true;
                                }
                            }
                        }
                    }
                    #endregion

                    #region write back string array to file
                    {
                        if (bChangesMadeToIdl)
                        {
                            using (StreamWriter swIdl = System.IO.File.CreateText(sSourceIdlFilename))
                            {
                                for (int i = 0; i < sSourceIdl.Count; i++)
                                {
                                    swIdl.WriteLine(sSourceIdl[i]);
                                }
                                swIdl.Close();
                            }
                        }
                    }
                    #endregion

                    RunMidl(sMidlExeFilename, sSourceIdlFilename, sTargetTblFilename, sw);
                }
                catch (Exception ex)
                {
                    sw.WriteLine("Exception:" + ex.Message);
                }
                finally
                {
                    sw.Close();
                }
            }
        }
    }
}

Tuesday, 1 May 2018

C# - COM Interop - Before dynamic to avoid PIAs one needed to call IDispatch

So I had researched an answer to a SO question where OP wanted to avoid a method call throwing an error and also wanted to not have to use On Error Resume Next. I recognise this situation. To avoid OERN I recommend burying the error throwing code in a .NET DLL. I got a solution using the dynamic keyword working because that avoids any dependency on an Primary Interop Assembly. But given my ongoing interest in IDispatch I also developed an IDispatch solution.

So might as well share the code


using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace SupportsGroupItemsByIDispatchLib
{

    public interface ISupportsGroupItemsByIDispatch
    {
        bool SupportsGroupItemsByIDispatch(object shp);
    }

    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(ISupportsGroupItemsByIDispatch))]
    public class CSupportsGroupItemsByIDispatch : ISupportsGroupItemsByIDispatch
    {
        [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")]
        private interface IDispatch
        {
            int GetTypeInfoCount();
            [return: MarshalAs(UnmanagedType.Interface)]
            ITypeInfo GetTypeInfo([In, MarshalAs(UnmanagedType.U4)] int iTInfo, [In, MarshalAs(UnmanagedType.U4)] int lcid);
            void GetIDsOfNames([In] ref Guid riid, [In, MarshalAs(UnmanagedType.LPArray)] string[] rgszNames, [In, MarshalAs(UnmanagedType.U4)] int cNames, [In, MarshalAs(UnmanagedType.U4)] int lcid, [Out, MarshalAs(UnmanagedType.LPArray)] int[] rgDispId);
            int Invoke
            (
                int dispIdMember,
                ref Guid riid,
                uint lcid,
                ushort wFlags,
                ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams,
                out object pVarResult,
                ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo,
                IntPtr[] pArgErr
            );
        }

        public const int lDISPID_GROUPITEMS = 108;
        public const int DISPATCH_PROPERTYGET = 2;
        bool ISupportsGroupItemsByIDispatch.SupportsGroupItemsByIDispatch(object shp)
        {
            bool bSupportsGroupItems = false;

            try
            {
                Guid nullGuid = new Guid("00000000000000000000000000000000");

                IDispatch disp = (IDispatch)shp;
                object grpItems;
                System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo = new System.Runtime.InteropServices.ComTypes.EXCEPINFO();


                //
                System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams = new System.Runtime.InteropServices.ComTypes.DISPPARAMS();
                pDispParams.cArgs = 0;

                //
                IntPtr[] pArgErr = new IntPtr[1];

                int dispInvokeRet;
                dispInvokeRet = disp.Invoke(lDISPID_GROUPITEMS, ref nullGuid, 0, DISPATCH_PROPERTYGET, pDispParams, out grpItems, ref pExcepInfo, pArgErr);

                bSupportsGroupItems = true;

            }
            catch (Exception)
            {
                // bury the error
                //throw new Exception("Error :" + ex.Message);

            }
            return bSupportsGroupItems;
        }
    }
}



Wednesday, 21 February 2018

C# -Elliptic Curve Digital Signatures

So the prior post showed an example of using RSA asymmetric hashing and signing to implement a digital signature but the latest trend is to use Elliptic Curve Digital Signature Algorithm (ECDSA) because they use shorter keys. So I have given another chunk of source code to demonstrate this.

Also in this code base I am using SHA256 for the hash and I am hashing twice just like BitCoin.

So for the Form1.cs code use this


// Based on code by Adnan Samuel, Code Project,
// https://www.codeproject.com/script/Membership/View.aspx?mid=1307566

using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        // instance of class ServerSide (Bob)
        public ServerSide mySender; // = new ServerSide();

        // instance of class ClientSide (Alice)
        public ClientSide myReceiver = new ClientSide();

        public Form1()
        {
            InitializeComponent();
        }

        private void btnExit_Click(object sender, EventArgs e)
        {
            myReceiver = null;
            mySender = null;
            Application.Exit();
        }

        private void btn2_GenerateSignatureBlock_Click(object sender, EventArgs e)
        {

            int keySize = Int32.Parse(maskTxtKeySize.Text);
            mySender = new ServerSide(keySize);

            //* Hash and Sign only
            Byte[] nonencryptedSignature;
            nonencryptedSignature = mySender.HashAndSign(Encoding.UTF8.GetBytes(
                                                              txtPlainText.Text));

            txtNonEncryptedSignatureBlock.Text = Convert.ToBase64String(
                                                           nonencryptedSignature);

            //C# 6 grpPublicKey.Text = $"Public Key ({mySender.KeySize} bit)";
            grpPublicKey.Text = "Public Key (" + mySender.KeySize + " bit)";
            txtPublicKey.Text = mySender.ToString2();

            btn3b_VerifySignature.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            btn3b_VerifySignature.Enabled = false;

            // build the link
            LinkLabel.Link link = new LinkLabel.Link();
            link.LinkData =
            "https://www.codeproject.com/Articles/8868/Implementing-Digital-Signing-in-NET";
            linkLabel1.Links.Add(link);
        }

        private void btn3b_VerifySignature_Click(object sender, EventArgs e)
        {
            Byte[] signature;
            signature = Convert.FromBase64String(
                                              txtNonEncryptedSignatureBlock.Text);

            Byte[] plainTextAsBytes;
            plainTextAsBytes = Encoding.UTF8.GetBytes(txtPlainText.Text);

            if (myReceiver.VerifyHash(txtPublicKey.Text, plainTextAsBytes,
                                                                       signature))
            {
                txtPlainTextReceiver.Text = txtPlainText.Text;
                MessageBox.Show("Signature Valid", "Verification Results",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                txtPlainTextReceiver.Text = "";
                MessageBox.Show("Invalid Signature", "Verification Results",
                                MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        private void linkLabel1_LinkClicked(object sender,
                                                  LinkLabelLinkClickedEventArgs e)
        {
            // Send the URL to the operating system.
            Process.Start(e.Link.LinkData as string);
        }

        private void maskTxtKeySize_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                // enter key has been pressed
                btn2_GenerateSignatureBlock_Click(sender, e);
            }
        }
    }

    public class ServerSide //Bob
    {
        //========================================================================
        //  Bob  is the Server who will hash and sign license documents
        //========================================================================
        private ECDsaCng ecDsaCngserverSide = null;

        private ServerSide()
        {
            ecDsaCngserverSide = new ECDsaCng();
        }

        public ServerSide(int keySize)
        {
            try
            {
                ecDsaCngserverSide = new ECDsaCng(keySize);
            }
            catch (Exception ex)
            {
                
                if (ex.HResult == -2146233296)
                {
                    MessageBox.Show("Key size should be 256, 384 or 521", 
                           "Key initialisation error", 
                           MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
                else
                {
                    //C# 6.0 string errMsg = $"Unhandled error {ex.HResult} : {ex.Message} ";
                    string errMsg = "Unhandled error " + "ex.HResult" + " : " + ex.Message + " ";
                    MessageBox.Show(errMsg, "Key initialisation error", 
                           MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
            }

        }

        public string PublicParametersAsXml
        {
            get
            {
                return ecDsaCngserverSide.ToXmlString(false);
            }
        }

        public int KeySize
        {
            get
            {
                return ecDsaCngserverSide.KeySize;
            }
        }

        public byte[] HashAndSign(byte[] input)
        {


            // a byte array to store hash value
            byte[] hashedData = null;

            byte[] results = null;

            // create new instance of SHA256 hash algorithm to compute hash
            HashAlgorithm hashAlgo = new SHA256Managed();

            // compute hash with algorithm 
            hashedData = hashAlgo.ComputeHash(input);

            // hasgh again, like BitCoin
            hashedData = hashAlgo.ComputeHash(hashedData);

            // sign Data using private key 
            results = ecDsaCngserverSide.SignHash(hashedData);

            return results;
        }

        public string ToString2()
        {
            return ecDsaCngserverSide.ToXmlString(ECKeyXmlFormat.Rfc4050);
        }
    }

    public class ClientSide
    {
        //'========================================================================
        //' Alice is Client who will import Public Key and verify signed hash
        //'========================================================================

        public Boolean VerifyHash(String ecDsaCngParamsXml, byte[] signedData,
                                                                  byte[] signature)
        {
            // create new instance 
            ECDsaCng client = new ECDsaCng();

            bool bOk = true;
            try
            {
                client.FromXmlString(ecDsaCngParamsXml, ECKeyXmlFormat.Rfc4050);
            }
            catch (Exception ex)
            {
                bOk = false;
                if (ex.HResult == -2146893785)
                {
                    MessageBox.Show("Key has been tampered with", 
                               "Key initialisation error", 
                               MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
                else
                {
                    //C# 6.0 string errMsg = $"Unhandled error {ex.HResult} : {ex.Message} ";
                    string errMsg = "Unhandled error " + "ex.HResult" + " : " + ex.Message + " ";

                    MessageBox.Show(errMsg, "Key initialisation error", 
                                 MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
            }

            // a byte array to store hash value
            byte[] hashedData = null;

            HashAlgorithm hashAlgo = new SHA256Managed();

            if (bOk && hashAlgo != null)
            {
                // compute hash with algorithm specified 
                hashedData = hashAlgo.ComputeHash(signedData);

                // hash it twice
                hashedData = hashAlgo.ComputeHash(hashedData);

                return client.VerifyHash(hashedData, signature);
            }
            else
            {
                return false;
            }
        }
    }
}

and ther Form1.Designer.cs code is here

namespace WindowsFormsApp1
{
    partial class Form1
    {
        /// 
        /// Required designer variable.
        /// 
        private System.ComponentModel.IContainer components = null;


        /// 
        /// Clean up any resources being used.
        /// 
        /// true if managed resources should be disposed; 
        /// otherwise, false.
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// 
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// 
        private void InitializeComponent()
        {
            this.grpServer = new System.Windows.Forms.GroupBox();
            this.grpMetrics = new System.Windows.Forms.GroupBox();
            this.maskTxtKeySize = new System.Windows.Forms.MaskedTextBox();
            this.lblKeySize = new System.Windows.Forms.Label();
            this.txtNonEncryptedSignatureBlock = new System.Windows.Forms.TextBox();
            this.btn2_GenerateSignatureBlock = new System.Windows.Forms.Button();
            this.txtPlainText = new System.Windows.Forms.TextBox();
            this.lblEnterPlaintextForSigning = new System.Windows.Forms.Label();
            this.lblTitle = new System.Windows.Forms.Label();
            this.grpClientSide = new System.Windows.Forms.GroupBox();
            this.txtPlainTextReceiver = new System.Windows.Forms.TextBox();
            this.btn3b_VerifySignature = new System.Windows.Forms.Button();
            this.btnExit = new System.Windows.Forms.Button();
            this.grpPublicKey = new System.Windows.Forms.GroupBox();
            this.txtPublicKey = new System.Windows.Forms.TextBox();
            this.lblIOriginalAuthor = new System.Windows.Forms.Label();
            this.linkLabel1 = new System.Windows.Forms.LinkLabel();
            this.grpServer.SuspendLayout();
            this.grpMetrics.SuspendLayout();
            this.grpClientSide.SuspendLayout();
            this.grpPublicKey.SuspendLayout();
            this.SuspendLayout();
            // 
            // grpServer
            // 
            this.grpServer.BackColor = System.Drawing.SystemColors.Control;
            this.grpServer.Controls.Add(this.grpMetrics);
            this.grpServer.Controls.Add(this.txtNonEncryptedSignatureBlock);
            this.grpServer.Controls.Add(this.btn2_GenerateSignatureBlock);
            this.grpServer.Controls.Add(this.txtPlainText);
            this.grpServer.Controls.Add(this.lblEnterPlaintextForSigning);
            this.grpServer.ForeColor = System.Drawing.SystemColors.WindowText;
            this.grpServer.Location = new System.Drawing.Point(12, 39);
            this.grpServer.Name = "grpServer";
            this.grpServer.Size = new System.Drawing.Size(826, 184);
            this.grpServer.TabIndex = 9;
            this.grpServer.TabStop = false;
            this.grpServer.Text = "ServerSide (Bob)";
            // 
            // grpMetrics
            // 
            this.grpMetrics.Controls.Add(this.maskTxtKeySize);
            this.grpMetrics.Controls.Add(this.lblKeySize);
            this.grpMetrics.Location = new System.Drawing.Point(11, 43);
            this.grpMetrics.Name = "grpMetrics";
            this.grpMetrics.Size = new System.Drawing.Size(159, 80);
            this.grpMetrics.TabIndex = 19;
            this.grpMetrics.TabStop = false;
            this.grpMetrics.Text = "Metrics";
            // 
            // maskTxtKeySize
            // 
            this.maskTxtKeySize.BackColor = System.Drawing.SystemColors.Control;
            this.maskTxtKeySize.Location = new System.Drawing.Point(84, 13);
            this.maskTxtKeySize.Mask = "00000";
            this.maskTxtKeySize.Name = "maskTxtKeySize";
            this.maskTxtKeySize.Size = new System.Drawing.Size(61, 20);
            this.maskTxtKeySize.TabIndex = 18;
            this.maskTxtKeySize.Text = "384";
            this.maskTxtKeySize.ValidatingType = typeof(int);
            this.maskTxtKeySize.KeyDown += new System.Windows.Forms.KeyEventHandler(this.maskTxtKeySize_KeyDown);
            // 
            // lblKeySize
            // 
            this.lblKeySize.BackColor = System.Drawing.SystemColors.Control;
            this.lblKeySize.ForeColor = System.Drawing.SystemColors.WindowText;
            this.lblKeySize.Location = new System.Drawing.Point(6, 17);
            this.lblKeySize.Name = "lblKeySize";
            this.lblKeySize.Size = new System.Drawing.Size(62, 16);
            this.lblKeySize.TabIndex = 17;
            this.lblKeySize.Text = "Key Size :";
            // 
            // txtNonEncryptedSignatureBlock
            // 
            this.txtNonEncryptedSignatureBlock.BackColor = System.Drawing.SystemColors.Control;
            this.txtNonEncryptedSignatureBlock.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtNonEncryptedSignatureBlock.Location = new System.Drawing.Point(192, 130);
            this.txtNonEncryptedSignatureBlock.Multiline = true;
            this.txtNonEncryptedSignatureBlock.Name = "txtNonEncryptedSignatureBlock";
            this.txtNonEncryptedSignatureBlock.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtNonEncryptedSignatureBlock.Size = new System.Drawing.Size(628, 48);
            this.txtNonEncryptedSignatureBlock.TabIndex = 15;
            // 
            // btn2_GenerateSignatureBlock
            // 
            this.btn2_GenerateSignatureBlock.BackColor = System.Drawing.SystemColors.Control;
            this.btn2_GenerateSignatureBlock.ForeColor = System.Drawing.SystemColors.WindowText;
            this.btn2_GenerateSignatureBlock.Location = new System.Drawing.Point(11, 130);
            this.btn2_GenerateSignatureBlock.Name = "btn2_GenerateSignatureBlock";
            this.btn2_GenerateSignatureBlock.Size = new System.Drawing.Size(160, 48);
            this.btn2_GenerateSignatureBlock.TabIndex = 10;
            this.btn2_GenerateSignatureBlock.Text = "Generate &Signature Block using Sender\'s (Bob) Private Key";
            this.btn2_GenerateSignatureBlock.UseVisualStyleBackColor = false;
            this.btn2_GenerateSignatureBlock.Click += new System.EventHandler(this.btn2_GenerateSignatureBlock_Click);
            // 
            // txtPlainText
            // 
            this.txtPlainText.BackColor = System.Drawing.SystemColors.Control;
            this.txtPlainText.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtPlainText.Location = new System.Drawing.Point(192, 20);
            this.txtPlainText.Multiline = true;
            this.txtPlainText.Name = "txtPlainText";
            this.txtPlainText.Size = new System.Drawing.Size(628, 104);
            this.txtPlainText.TabIndex = 8;
            this.txtPlainText.Text = "Hello";
            // 
            // lblEnterPlaintextForSigning
            // 
            this.lblEnterPlaintextForSigning.BackColor = System.Drawing.SystemColors.Control;
            this.lblEnterPlaintextForSigning.ForeColor = System.Drawing.SystemColors.WindowText;
            this.lblEnterPlaintextForSigning.Location = new System.Drawing.Point(54, 23);
            this.lblEnterPlaintextForSigning.Name = "lblEnterPlaintextForSigning";
            this.lblEnterPlaintextForSigning.Size = new System.Drawing.Size(132, 20);
            this.lblEnterPlaintextForSigning.TabIndex = 7;
            this.lblEnterPlaintextForSigning.Text = "Enter Plaintext for signing :";
            // 
            // lblTitle
            // 
            this.lblTitle.BackColor = System.Drawing.SystemColors.Control;
            this.lblTitle.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblTitle.ForeColor = System.Drawing.SystemColors.WindowText;
            this.lblTitle.Location = new System.Drawing.Point(107, 4);
            this.lblTitle.Name = "lblTitle";
            this.lblTitle.Size = new System.Drawing.Size(566, 32);
            this.lblTitle.TabIndex = 8;
            this.lblTitle.Text = "Cryptography - Elliptic Curve Digital Signature Algorithm (ECDSA)";
            this.lblTitle.TextAlign = System.Drawing.ContentAlignment.TopCenter;
            // 
            // grpClientSide
            // 
            this.grpClientSide.BackColor = System.Drawing.SystemColors.Control;
            this.grpClientSide.Controls.Add(this.txtPlainTextReceiver);
            this.grpClientSide.Controls.Add(this.btn3b_VerifySignature);
            this.grpClientSide.ForeColor = System.Drawing.SystemColors.WindowText;
            this.grpClientSide.Location = new System.Drawing.Point(12, 424);
            this.grpClientSide.Name = "grpClientSide";
            this.grpClientSide.Size = new System.Drawing.Size(826, 119);
            this.grpClientSide.TabIndex = 11;
            this.grpClientSide.TabStop = false;
            this.grpClientSide.Text = "ClientSide (Alice)";
            // 
            // txtPlainTextReceiver
            // 
            this.txtPlainTextReceiver.BackColor = System.Drawing.SystemColors.Control;
            this.txtPlainTextReceiver.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtPlainTextReceiver.Location = new System.Drawing.Point(167, 20);
            this.txtPlainTextReceiver.Multiline = true;
            this.txtPlainTextReceiver.Name = "txtPlainTextReceiver";
            this.txtPlainTextReceiver.ReadOnly = true;
            this.txtPlainTextReceiver.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtPlainTextReceiver.Size = new System.Drawing.Size(643, 93);
            this.txtPlainTextReceiver.TabIndex = 13;
            // 
            // btn3b_VerifySignature
            // 
            this.btn3b_VerifySignature.BackColor = System.Drawing.SystemColors.Control;
            this.btn3b_VerifySignature.ForeColor = System.Drawing.SystemColors.WindowText;
            this.btn3b_VerifySignature.Location = new System.Drawing.Point(6, 20);
            this.btn3b_VerifySignature.Name = "btn3b_VerifySignature";
            this.btn3b_VerifySignature.Size = new System.Drawing.Size(155, 61);
            this.btn3b_VerifySignature.TabIndex = 0;
            this.btn3b_VerifySignature.Text = "Verify Signature block using sender\'s (Bob) Public Key";
            this.btn3b_VerifySignature.UseVisualStyleBackColor = false;
            this.btn3b_VerifySignature.Click += new System.EventHandler(this.btn3b_VerifySignature_Click);
            // 
            // btnExit
            // 
            this.btnExit.BackColor = System.Drawing.SystemColors.Control;
            this.btnExit.ForeColor = System.Drawing.SystemColors.WindowText;
            this.btnExit.Location = new System.Drawing.Point(696, 549);
            this.btnExit.Name = "btnExit";
            this.btnExit.Size = new System.Drawing.Size(136, 24);
            this.btnExit.TabIndex = 10;
            this.btnExit.Text = "E&xit";
            this.btnExit.UseVisualStyleBackColor = false;
            this.btnExit.Click += new System.EventHandler(this.btnExit_Click);
            // 
            // grpPublicKey
            // 
            this.grpPublicKey.BackColor = System.Drawing.SystemColors.Control;
            this.grpPublicKey.Controls.Add(this.txtPublicKey);
            this.grpPublicKey.ForeColor = System.Drawing.SystemColors.WindowText;
            this.grpPublicKey.Location = new System.Drawing.Point(12, 243);
            this.grpPublicKey.Name = "grpPublicKey";
            this.grpPublicKey.Size = new System.Drawing.Size(826, 175);
            this.grpPublicKey.TabIndex = 12;
            this.grpPublicKey.TabStop = false;
            this.grpPublicKey.Text = "Public Key";
            // 
            // txtPublicKey
            // 
            this.txtPublicKey.BackColor = System.Drawing.SystemColors.Control;
            this.txtPublicKey.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtPublicKey.Location = new System.Drawing.Point(11, 20);
            this.txtPublicKey.Multiline = true;
            this.txtPublicKey.Name = "txtPublicKey";
            this.txtPublicKey.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtPublicKey.Size = new System.Drawing.Size(809, 149);
            this.txtPublicKey.TabIndex = 13;
            // 
            // lblIOriginalAuthor
            // 
            this.lblIOriginalAuthor.AutoSize = true;
            this.lblIOriginalAuthor.Location = new System.Drawing.Point(13, 559);
            this.lblIOriginalAuthor.Name = "lblIOriginalAuthor";
            this.lblIOriginalAuthor.Size = new System.Drawing.Size(165, 13);
            this.lblIOriginalAuthor.TabIndex = 13;
            this.lblIOriginalAuthor.Text = "Based on code by Adnan Samuel";
            // 
            // linkLabel1
            // 
            this.linkLabel1.AutoSize = true;
            this.linkLabel1.Location = new System.Drawing.Point(184, 559);
            this.linkLabel1.Name = "linkLabel1";
            this.linkLabel1.Size = new System.Drawing.Size(248, 13);
            this.linkLabel1.TabIndex = 14;
            this.linkLabel1.TabStop = true;
            this.linkLabel1.Text = "Code Project - Implementing Digital Signing in .NET";
            this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(857, 593);
            this.Controls.Add(this.linkLabel1);
            this.Controls.Add(this.lblIOriginalAuthor);
            this.Controls.Add(this.grpServer);
            this.Controls.Add(this.lblTitle);
            this.Controls.Add(this.grpClientSide);
            this.Controls.Add(this.btnExit);
            this.Controls.Add(this.grpPublicKey);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.grpServer.ResumeLayout(false);
            this.grpServer.PerformLayout();
            this.grpMetrics.ResumeLayout(false);
            this.grpMetrics.PerformLayout();
            this.grpClientSide.ResumeLayout(false);
            this.grpClientSide.PerformLayout();
            this.grpPublicKey.ResumeLayout(false);
            this.grpPublicKey.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        internal System.Windows.Forms.GroupBox grpServer;
        internal System.Windows.Forms.TextBox txtNonEncryptedSignatureBlock;
        internal System.Windows.Forms.Button btn2_GenerateSignatureBlock;
        internal System.Windows.Forms.TextBox txtPlainText;
        internal System.Windows.Forms.Label lblEnterPlaintextForSigning;
        internal System.Windows.Forms.Label lblTitle;
        internal System.Windows.Forms.GroupBox grpClientSide;
        internal System.Windows.Forms.TextBox txtPlainTextReceiver;
        internal System.Windows.Forms.Button btn3b_VerifySignature;
        internal System.Windows.Forms.Button btnExit;
        internal System.Windows.Forms.GroupBox grpPublicKey;
        internal System.Windows.Forms.TextBox txtPublicKey;
        private System.Windows.Forms.Label lblIOriginalAuthor;
        private System.Windows.Forms.LinkLabel linkLabel1;
        private System.Windows.Forms.MaskedTextBox maskTxtKeySize;
        internal System.Windows.Forms.Label lblKeySize;
        private System.Windows.Forms.GroupBox grpMetrics;
    }
}



C# - RSA Digital Signatures

I am grateful to Adnan Samuel who has penned a Code Project article called Implementing Digital Signing in .NET which uses .NET cryptography classes to show example of encrypting and digitally signing a message. I am indebted to this code base but because I don't need encryption I simplified the code, also I folded all the code from separate class files into one file so it is easier to paste in the reader's instance of Visual Studio.

Another change I made is to segment the logic because actually in my use case one half on the code will run on a server and the other half will run on the client and the given code base tended to keep global variables hanging around and pass them between the two halves and I thought this unrealistic. I have added an extra textbox on the form to show the public key being exported.

Also I changed some of the textboxes to be writeable and so tamperable, so that one can tamper with the values and the signature report to provoke a verification fail.

Also another change is to convert to C# because since the time of writing of the article (2004) C# has stretched its lead over VB (IMHO). [Also I like the partial class split way the Forms are defined in C#].

Also, I have updated the SHA hash from SHA1 to SHA256 more in line with current standards of FIPS 140/2 Suite B

Here is amended code, create a C# Windows Forms (.NET Framework) project and then paste in the following code into Form1.cs


// Based on code by Adnan Samuel, Code Project,
// https://www.codeproject.com/script/Membership/View.aspx?mid=1307566

using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        // instance of class ServerSide (Bob)
        public ServerSide mySender = new ServerSide();

        // instance of class ClientSide (Alice)
        public ClientSide myReceiver = new ClientSide();


        public Form1()
        {
            InitializeComponent();
        }

        private void btnExit_Click(object sender, EventArgs e)
        {
            myReceiver = null;
            mySender = null;
            Application.Exit();
        }

        private void btn2_GenerateSignatureBlock_Click(object sender, EventArgs e)
        {
            //* Hash and Sign only
            Byte[] nonencryptedSignature;
            nonencryptedSignature = mySender.HashAndSign(Encoding.UTF8.GetBytes(
                                                              txtPlainText.Text));

            txtNonEncryptedSignatureBlock.Text = Convert.ToBase64String(
                                                           nonencryptedSignature);

            grpPublicKey.Text = $"Public Key ({mySender.KeySize} bit)";
            txtPublicKey.Text = mySender.ToString2();

            btn3b_VerifySignature.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            btn3b_VerifySignature.Enabled = false;

            // build the link
            LinkLabel.Link link = new LinkLabel.Link();
            link.LinkData = 
            "https://www.codeproject.com/Articles/8868/Implementing-Digital-Signing-in-NET";
            linkLabel1.Links.Add(link);
        }

        private void btn3b_VerifySignature_Click(object sender, EventArgs e)
        {
            Byte[] signature;
            signature = Convert.FromBase64String(
                                              txtNonEncryptedSignatureBlock.Text);

            Byte[] plainTextAsBytes;
            plainTextAsBytes = Encoding.UTF8.GetBytes(txtPlainText.Text);

            if (myReceiver.VerifyHash(txtPublicKey.Text, plainTextAsBytes, 
                                                                       signature))
            {
                txtPlainTextReceiver.Text = txtPlainText.Text;
                MessageBox.Show("Signature Valid", "Verification Results", 
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                txtPlainTextReceiver.Text = "";
                MessageBox.Show("Invalid Signature", "Verification Results", 
                                MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        private void linkLabel1_LinkClicked(object sender, 
                                                  LinkLabelLinkClickedEventArgs e)
        {
            // Send the URL to the operating system.
            Process.Start(e.Link.LinkData as string);
        }
    }

    public class ServerSide //Bob
    {
        //========================================================================
        //  Bob  is the Server who will hash and sign license documents
        //========================================================================
        private RSACryptoServiceProvider rsaCSPServerSide = 
                                                   new RSACryptoServiceProvider(); 

        public RSAParameters PublicParameters
        {
            get
            {
                return rsaCSPServerSide.ExportParameters(false);
            }
        }

        public int KeySize
        {
            get
            {
                return rsaCSPServerSide.KeySize;
            }
        }

        public byte[] HashAndSign(byte[] input)
        {
            // create new instance of SHA256 hash algorithm to compute hash
            SHA256Managed hash = new SHA256Managed();

            // a byte array to store hash value
            byte[] hashedData;

            // compute hash with algorithm specified as here we have SHA256
            hashedData = hash.ComputeHash(input);

            // sign Data using private key and  OID is simple name 
            // of the algorithm for which to get the object identifier (OID)
            return rsaCSPServerSide.SignHash(hashedData, 
                                                CryptoConfig.MapNameToOID("SHA256"));
        }

        public string ToString2()
        {
            return rsaCSPServerSide.ToXmlString(false);
        }

    }


    public class ClientSide
    {
        //'========================================================================
        //' Alice is Client who will import Public Key and verify signed hash
        //'========================================================================

        // Manually performs hash and then verifies hashed value.
        public Boolean VerifyHash(String rsaParamsXml, byte[] signedData, 
                                                                  byte[] signature)
        {
            // create new instance of RSACryptoServiceProvider
            RSACryptoServiceProvider rsaCSP = new RSACryptoServiceProvider();

            // create new instance of SHA256 hash algorithm to compute hash
            SHA256Managed hash = new SHA256Managed();

            // a byte array to store hash value
            byte[] hashedData;

            // import  public key params into instance of RSACryptoServiceProvider
            rsaCSP.FromXmlString(rsaParamsXml);


            // compute hash with algorithm specified as here we have SHA256
            hashedData = hash.ComputeHash(signedData);

            // Sign Data using public key and  OID is simple name of the algorithm 
            // for which to get the object identifier (OID)
            return rsaCSP.VerifyHash(hashedData, CryptoConfig.MapNameToOID("SHA256"), 
                                                                        signature);
        }
    }
}


and the code behind the designer, Form1.Designer.cs. Sorry for small font, designer generated code tends to spill over the column of my blog. There's nothing much to see here though.

namespace WindowsFormsApp1
{
    partial class Form1
    {
        /// 
        /// Required designer variable.
        /// 
        private System.ComponentModel.IContainer components = null;


        /// 
        /// Clean up any resources being used.
        /// 
        /// true if managed resources should be disposed; 
        /// otherwise, false.
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// 
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// 
        private void InitializeComponent()
        {
            this.grpServer = new System.Windows.Forms.GroupBox();
            this.txtNonEncryptedSignatureBlock = new System.Windows.Forms.TextBox();
            this.btn2_GenerateSignatureBlock = new System.Windows.Forms.Button();
            this.txtPlainText = new System.Windows.Forms.TextBox();
            this.lblEnterPlaintextForSigning = new System.Windows.Forms.Label();
            this.lblTitle = new System.Windows.Forms.Label();
            this.grpClientSide = new System.Windows.Forms.GroupBox();
            this.txtPlainTextReceiver = new System.Windows.Forms.TextBox();
            this.btn3b_VerifySignature = new System.Windows.Forms.Button();
            this.btnExit = new System.Windows.Forms.Button();
            this.grpPublicKey = new System.Windows.Forms.GroupBox();
            this.txtPublicKey = new System.Windows.Forms.TextBox();
            this.lblIOriginalAuthor = new System.Windows.Forms.Label();
            this.linkLabel1 = new System.Windows.Forms.LinkLabel();
            this.grpServer.SuspendLayout();
            this.grpClientSide.SuspendLayout();
            this.grpPublicKey.SuspendLayout();
            this.SuspendLayout();
            // 
            // grpServer
            // 
            this.grpServer.BackColor = System.Drawing.SystemColors.Control;
            this.grpServer.Controls.Add(this.txtNonEncryptedSignatureBlock);
            this.grpServer.Controls.Add(this.btn2_GenerateSignatureBlock);
            this.grpServer.Controls.Add(this.txtPlainText);
            this.grpServer.Controls.Add(this.lblEnterPlaintextForSigning);
            this.grpServer.ForeColor = System.Drawing.SystemColors.WindowText;
            this.grpServer.Location = new System.Drawing.Point(12, 39);
            this.grpServer.Name = "grpServer";
            this.grpServer.Size = new System.Drawing.Size(826, 184);
            this.grpServer.TabIndex = 9;
            this.grpServer.TabStop = false;
            this.grpServer.Text = "ServerSide (Bob)";
            // 
            // txtNonEncryptedSignatureBlock
            // 
            this.txtNonEncryptedSignatureBlock.BackColor = System.Drawing.SystemColors.Control;
            this.txtNonEncryptedSignatureBlock.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtNonEncryptedSignatureBlock.Location = new System.Drawing.Point(192, 130);
            this.txtNonEncryptedSignatureBlock.Multiline = true;
            this.txtNonEncryptedSignatureBlock.Name = "txtNonEncryptedSignatureBlock";
            this.txtNonEncryptedSignatureBlock.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtNonEncryptedSignatureBlock.Size = new System.Drawing.Size(628, 48);
            this.txtNonEncryptedSignatureBlock.TabIndex = 15;
            // 
            // btn2_GenerateSignatureBlock
            // 
            this.btn2_GenerateSignatureBlock.BackColor = System.Drawing.SystemColors.Control;
            this.btn2_GenerateSignatureBlock.ForeColor = System.Drawing.SystemColors.WindowText;
            this.btn2_GenerateSignatureBlock.Location = new System.Drawing.Point(11, 130);
            this.btn2_GenerateSignatureBlock.Name = "btn2_GenerateSignatureBlock";
            this.btn2_GenerateSignatureBlock.Size = new System.Drawing.Size(160, 48);
            this.btn2_GenerateSignatureBlock.TabIndex = 10;
            this.btn2_GenerateSignatureBlock.Text = "Generate &Signature Block using Sender\'s (Bob) Private Key";
            this.btn2_GenerateSignatureBlock.UseVisualStyleBackColor = false;
            this.btn2_GenerateSignatureBlock.Click += new System.EventHandler(this.btn2_GenerateSignatureBlock_Click);
            // 
            // txtPlainText
            // 
            this.txtPlainText.BackColor = System.Drawing.SystemColors.Control;
            this.txtPlainText.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtPlainText.Location = new System.Drawing.Point(192, 20);
            this.txtPlainText.Multiline = true;
            this.txtPlainText.Name = "txtPlainText";
            this.txtPlainText.Size = new System.Drawing.Size(628, 104);
            this.txtPlainText.TabIndex = 8;
            this.txtPlainText.Text = "Hello";
            // 
            // lblEnterPlaintextForSigning
            // 
            this.lblEnterPlaintextForSigning.BackColor = System.Drawing.SystemColors.Control;
            this.lblEnterPlaintextForSigning.ForeColor = System.Drawing.SystemColors.WindowText;
            this.lblEnterPlaintextForSigning.Location = new System.Drawing.Point(8, 24);
            this.lblEnterPlaintextForSigning.Name = "lblEnterPlaintextForSigning";
            this.lblEnterPlaintextForSigning.Size = new System.Drawing.Size(168, 32);
            this.lblEnterPlaintextForSigning.TabIndex = 7;
            this.lblEnterPlaintextForSigning.Text = "Enter Plaintext for signing :";
            // 
            // lblTitle
            // 
            this.lblTitle.BackColor = System.Drawing.SystemColors.Control;
            this.lblTitle.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblTitle.ForeColor = System.Drawing.SystemColors.WindowText;
            this.lblTitle.Location = new System.Drawing.Point(286, 4);
            this.lblTitle.Name = "lblTitle";
            this.lblTitle.Size = new System.Drawing.Size(176, 32);
            this.lblTitle.TabIndex = 8;
            this.lblTitle.Text = "Digital Signatures";
            this.lblTitle.TextAlign = System.Drawing.ContentAlignment.TopCenter;
            // 
            // grpClientSide
            // 
            this.grpClientSide.BackColor = System.Drawing.SystemColors.Control;
            this.grpClientSide.Controls.Add(this.txtPlainTextReceiver);
            this.grpClientSide.Controls.Add(this.btn3b_VerifySignature);
            this.grpClientSide.ForeColor = System.Drawing.SystemColors.WindowText;
            this.grpClientSide.Location = new System.Drawing.Point(12, 424);
            this.grpClientSide.Name = "grpClientSide";
            this.grpClientSide.Size = new System.Drawing.Size(826, 119);
            this.grpClientSide.TabIndex = 11;
            this.grpClientSide.TabStop = false;
            this.grpClientSide.Text = "ClientSide (Alice)";
            // 
            // txtPlainTextReceiver
            // 
            this.txtPlainTextReceiver.BackColor = System.Drawing.SystemColors.Control;
            this.txtPlainTextReceiver.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtPlainTextReceiver.Location = new System.Drawing.Point(167, 20);
            this.txtPlainTextReceiver.Multiline = true;
            this.txtPlainTextReceiver.Name = "txtPlainTextReceiver";
            this.txtPlainTextReceiver.ReadOnly = true;
            this.txtPlainTextReceiver.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtPlainTextReceiver.Size = new System.Drawing.Size(643, 93);
            this.txtPlainTextReceiver.TabIndex = 13;
            // 
            // btn3b_VerifySignature
            // 
            this.btn3b_VerifySignature.BackColor = System.Drawing.SystemColors.Control;
            this.btn3b_VerifySignature.ForeColor = System.Drawing.SystemColors.WindowText;
            this.btn3b_VerifySignature.Location = new System.Drawing.Point(6, 20);
            this.btn3b_VerifySignature.Name = "btn3b_VerifySignature";
            this.btn3b_VerifySignature.Size = new System.Drawing.Size(155, 61);
            this.btn3b_VerifySignature.TabIndex = 0;
            this.btn3b_VerifySignature.Text = "Verify Signature block using sender\'s (Bob) Public Key";
            this.btn3b_VerifySignature.UseVisualStyleBackColor = false;
            this.btn3b_VerifySignature.Click += new System.EventHandler(this.btn3b_VerifySignature_Click);
            // 
            // btnExit
            // 
            this.btnExit.BackColor = System.Drawing.SystemColors.Control;
            this.btnExit.ForeColor = System.Drawing.SystemColors.WindowText;
            this.btnExit.Location = new System.Drawing.Point(696, 549);
            this.btnExit.Name = "btnExit";
            this.btnExit.Size = new System.Drawing.Size(136, 24);
            this.btnExit.TabIndex = 10;
            this.btnExit.Text = "E&xit";
            this.btnExit.UseVisualStyleBackColor = false;
            this.btnExit.Click += new System.EventHandler(this.btnExit_Click);
            // 
            // grpPublicKey
            // 
            this.grpPublicKey.BackColor = System.Drawing.SystemColors.Control;
            this.grpPublicKey.Controls.Add(this.txtPublicKey);
            this.grpPublicKey.ForeColor = System.Drawing.SystemColors.WindowText;
            this.grpPublicKey.Location = new System.Drawing.Point(12, 243);
            this.grpPublicKey.Name = "grpPublicKey";
            this.grpPublicKey.Size = new System.Drawing.Size(826, 175);
            this.grpPublicKey.TabIndex = 12;
            this.grpPublicKey.TabStop = false;
            this.grpPublicKey.Text = "Public Key";
            // 
            // txtPublicKey
            // 
            this.txtPublicKey.BackColor = System.Drawing.SystemColors.Control;
            this.txtPublicKey.ForeColor = System.Drawing.SystemColors.WindowText;
            this.txtPublicKey.Location = new System.Drawing.Point(11, 20);
            this.txtPublicKey.Multiline = true;
            this.txtPublicKey.Name = "txtPublicKey";
            this.txtPublicKey.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtPublicKey.Size = new System.Drawing.Size(809, 149);
            this.txtPublicKey.TabIndex = 13;
            // 
            // lblIOriginalAuthor
            // 
            this.lblIOriginalAuthor.AutoSize = true;
            this.lblIOriginalAuthor.Location = new System.Drawing.Point(13, 559);
            this.lblIOriginalAuthor.Name = "lblIOriginalAuthor";
            this.lblIOriginalAuthor.Size = new System.Drawing.Size(165, 13);
            this.lblIOriginalAuthor.TabIndex = 13;
            this.lblIOriginalAuthor.Text = "Based on code by Adnan Samuel";
            // 
            // linkLabel1
            // 
            this.linkLabel1.AutoSize = true;
            this.linkLabel1.Location = new System.Drawing.Point(184, 559);
            this.linkLabel1.Name = "linkLabel1";
            this.linkLabel1.Size = new System.Drawing.Size(248, 13);
            this.linkLabel1.TabIndex = 14;
            this.linkLabel1.TabStop = true;
            this.linkLabel1.Text = "Code Project - Implementing Digital Signing in .NET";
            this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(857, 583);
            this.Controls.Add(this.linkLabel1);
            this.Controls.Add(this.lblIOriginalAuthor);
            this.Controls.Add(this.grpServer);
            this.Controls.Add(this.lblTitle);
            this.Controls.Add(this.grpClientSide);
            this.Controls.Add(this.btnExit);
            this.Controls.Add(this.grpPublicKey);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.grpServer.ResumeLayout(false);
            this.grpServer.PerformLayout();
            this.grpClientSide.ResumeLayout(false);
            this.grpClientSide.PerformLayout();
            this.grpPublicKey.ResumeLayout(false);
            this.grpPublicKey.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        internal System.Windows.Forms.GroupBox grpServer;
        internal System.Windows.Forms.TextBox txtNonEncryptedSignatureBlock;
        internal System.Windows.Forms.Button btn2_GenerateSignatureBlock;
        internal System.Windows.Forms.TextBox txtPlainText;
        internal System.Windows.Forms.Label lblEnterPlaintextForSigning;
        internal System.Windows.Forms.Label lblTitle;
        internal System.Windows.Forms.GroupBox grpClientSide;
        internal System.Windows.Forms.TextBox txtPlainTextReceiver;
        internal System.Windows.Forms.Button btn3b_VerifySignature;
        internal System.Windows.Forms.Button btnExit;
        internal System.Windows.Forms.GroupBox grpPublicKey;
        internal System.Windows.Forms.TextBox txtPublicKey;
        private System.Windows.Forms.Label lblIOriginalAuthor;
        private System.Windows.Forms.LinkLabel linkLabel1;
    }
}



C# - Sadly VBA's GetObject's Custom Activation Syntax is not implemented in C#/.NET

For a while I have been curious as to a syntax of VBA's GetObject which has become quite prevalent.  GetObject is used to get an object that have been registered in the RunningObjectTable, C# has the equivalent of Marshal.GetActiveObject.  VBA's GetObject has an extra use cases in that if called with a filename and the file is not loaded into an application then it is loaded on demand, I have yet to establish if C# can do this.

The real extra use case of GetObject that caught my eye is when using either WMI or LDAP.  Here is some sample code instantiating LDAP

    Set objUser = GetObject("LDAP://" & strUserDN) 

And here is some code instantiating WMI

    Set oWMIService = GetObject("winmgmts:\\.\root\cimv2")

And third and final case is a Windows Communication Foundation WCF Moniker

Set typedServiceMoniker = GetObject(  
"service4:address=http://localhost/ServiceModelSamples/service.svc, binding=wsHttpBinding,   
contractType={9213C6D2-5A6F-3D26-839B-3BA9B82228D3}") 

So you can see some very different syntaxes beyond the plain COM Server's ProgId of "Excel.Application" and filenames then. There is some pattern, they all start with a text string and then a colon ":", after the colon they can be custom. This is a custom activation syntax. Sadly, it is not callable/useable from C#. This is ironic because WCF services are written in C# (though C# to C# code should clearly avoid VBA patterns).

I'm afraid to say I've wasted some time investigating this. I will post my interim findings here but not to a conclusion.

A key resource is a good book by Guy and Henry Eddon titled Essential COM, fortunately there is an online version. The relevant section is The MkParseDisplayName Function .  Here is a quote

MkParseDisplayName accepts two primary string formats. The first ... The second string format is the more general and thus more important of the two formats. In this format, MkParseDisplayName accepts any string in the form ProgID:ObjectName, where ProgID is a registered program identifier. This architecture allows anyone to write a custom moniker that hooks into the COM+ namespace simply by creating a program identifier (ProgID) entry in the registry.  
The following steps are executed when MkParseDisplayName encounters a string that has the ProgID:ObjectName format: 

  1. The ProgID is converted to a CLSID using the CLSIDFromProgID function. The result is the CLSID of the moniker. 
  2. CoGetClassObject is called to instantiate the moniker. 
  3. IUnknown::QueryInterface is called to request the IParseDisplayName interface. 
  4. The IParseDisplayName::ParseDisplayName method is called to parse the string passed to MkParseDisplayName. 
  5. In the moniker's IParseDisplayName::ParseDisplayName method, a moniker that names the > object identified by the string is created. 
  6. The resulting IMoniker pointer is returned to the client. 

For example, if the string "Hello:Maya" is passed to MkParseDisplayName, the HKEY_CLASSES_ROOT section of the registry is searched for the ProgID Hello. If Hello is found, the CLSID subkey below the ProgID is used to locate and load the moniker. The moniker's IParseDisplayName::ParseDisplayName method is then called to create a moniker object that names the Maya object. Figure 11-2 shows the registry entries involved in this hypothetical example; the numbered labels indicate the order in which the information is obtained from the registry.

So I found this fascinating and I knew of LDAP, WMI and a WCF service moniker which I had developed much earlier I wondered what other COM servers followed this pattern.  I wrote some VBA code which scanned the registry looking for the above pattern and write to a file.  I need a separate C++ program to scan through that file and instantiate the COM server candidates and QueryInterface for IParseDisplayName.  The C++ program is given here.

// C++LookingForCustomActivation.cpp : Defines the entry point for the console application.
//

#include <iostream>
#include <vector>
#include <fstream>
#include <sstream> 
#include "stdafx.h"
#include "Objbase.h"  // required for CoInitialize
#include "atlbase.h"  // required for CComBSTR

using namespace std;

bool ClassImplementsInterface(CLSID clsid1, IID interfaceId, std::string progidInfo);

int _tmain(int argc, _TCHAR* argv[])
{
	::CoInitialize(0);

	std::string idsFilename = "N:\\ProgIdsClassIds.txt";
	std::ifstream idsFileStream(idsFilename, ios_base::in);

	std::string progid;
	std::string clsid;
	std::vector<std::pair<std::string, std::string>> ids;

	while (idsFileStream >> progid >> clsid) {
		bool goodClsId = false ;
		if (clsid.size() == 38) {
			if (clsid[0] == '{' && clsid[37] == '}') {
				goodClsId = true;
			}
		}

		if (goodClsId) {
			
			auto id = std::make_pair(progid, clsid);
			ids.push_back(id);
		}
		else
		{
			cout << "Problem!";
		}
	}

	CLSID iParseDisplayName;
	CLSIDFromString(CComBSTR("{0000011A-0000-0000-C000-000000000046}"), 
                   &iParseDisplayName);

	for (std::vector<std::pair<std::string, std::string>>::iterator
                          it = ids.begin(); it != ids.end(); ++it) {
		progid = it->first;
		clsid = it->second;

		std::wstring stemp = std::wstring(clsid.begin(), clsid.end());
		LPCWSTR sw = stemp.c_str();

		CLSID clsidLoop;
		CLSIDFromString(sw, &clsidLoop);

		if (ClassImplementsInterface(clsidLoop, iParseDisplayName, progid)) {
			std::cout << progid << endl;
		}
		else
		{
			//std::cout << "Nope.";
		}
	}

	int wait;
	std::cin >> wait;

	::CoUninitialize();
	return 0;
}

bool ClassImplementsInterface(CLSID clsid1, IID interfaceId, 
                                                    std::string progidInfo)
{
	CLSID iUnknown;
	CLSIDFromString(CComBSTR("{00000000-0000-0000-C000-000000000046}"), 
                                                               &iUnknown);

	IUnknown* pUnk = NULL;
	HRESULT hr;
	bool abort = false;
	try {
		hr = CoCreateInstance(clsid1,
			NULL,
			CLSCTX_INPROC_SERVER,
			iUnknown,
			reinterpret_cast<void**>(&pUnk));
	}
	catch (const std::exception& e)
	{
		cout << "failed to CoCreateInstance " << progidInfo;
		abort = true;
	}
	if (!abort)
	{
		if (hr == S_OK) {
			void* pItf = NULL;
			hr = pUnk->QueryInterface(interfaceId, &pItf);
			if (hr == S_OK) {
				return true;
			}
			else
			{
				return false;
			}
		}
	}
}

Unfortunately this threw out hundreds of false positives so I was stuck with my three examples of LDAP, WMI and WCF. At this point I turned to writing my own example and I got it working though it does little. Here is the C# code.

    using System;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;

    namespace MonikerParseDisplayName
    {
        // https://www.microsoft.com/msj/1199/wicked/wicked1199.aspx

        // In Project Properties->Build->Check 'Interop for COM'
        // In AssemblyInfo.cs [assembly: ComVisible(true)]
        // compile with /unsafe 
        // In Project Properties->Build->Check 'Allow unsafe code'

        public static class Win32PInvoke
        {
            [DllImport("ole32.dll")]
            public static extern int CreateClassMoniker([In] ref Guid rclsid, 
                                                    out IMoniker ppmk);
        }

        [Guid("0FD50B85-CE66-47E2-9C71-2E780EBB8D54")]
        public interface ICalculator
        {
            double add(double a, double b);
            double mult(double a, double b);
        }

        [ComImport]
        [System.Security.SuppressUnmanagedCodeSecurity]
        [Guid("0000011a-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IParseDisplayName
        {
            void ParseDisplayName(IBindCtx pbc,
                [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName,
                IntPtr pchEaten, IntPtr ppmkOut);
            //void ParseDisplayName(IBindCtx pbc,
            //    [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName,
            //    out int  pchEaten, out IMoniker  ppmkOut);
        }

        [Guid("30194303-435D-44E1-9FB2-A625CEDB8B68")]
        [ClassInterface(ClassInterfaceType.None)]
        [ComDefaultInterface(typeof(ICalculator))]
        public class Calculator : ICalculator, IParseDisplayName,
            IMoniker
        {
            [ComVisible(true)]
            [ComRegisterFunction()]
            public static void DllRegisterServer(string sKey)
            {

                Microsoft.Win32.RegistryKey key;
                key = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey 
                     ("SimonsCalc");
                key.SetValue("", 
                   "Simon's experiment with GetObject(\"SimonsCalc:adder\")");
                Microsoft.Win32.RegistryKey subkey;
                subkey = key.CreateSubKey("Clsid");
                subkey.SetValue("", "{30194303-435D-44E1-9FB2-A625CEDB8B68}");
                subkey.Close();
                key.Close();

            }
            [ComVisible(true)]
            [ComUnregisterFunction()]
            public static void DllUnregisterServer(string sKey)
            {

                try
                {
                    Microsoft.Win32.Registry.ClassesRoot.DeleteSubKeyTree(
                           "SimonsCalc");
                }
                catch (Exception)
                {}
            }

            public static void Log(string logMessage, TextWriter w)
            {
                w.Write("\r\nLog Entry : ");
                w.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),
                    DateTime.Now.ToLongDateString());
                w.WriteLine("  :");
                w.WriteLine("  :{0}", logMessage);
                w.WriteLine("-------------------------------");
            }

            public double add(double a, double b)
            {
                return a + b;
            }
            public double mult(double a, double b)
            {
                return a * b;
            }


            void IParseDisplayName.ParseDisplayName(IBindCtx pbc, 
                string pszDisplayName, IntPtr pchEaten, IntPtr ppmkOut)
            {
                using (StreamWriter w = File.AppendText("n:\\log.txt"))
                {
                    IMoniker mon=null;
                    int retVal=0;

                    try { 
                        //consume the whole lot
                        System.Runtime.InteropServices.Marshal.WriteInt32(pchEaten, 
                               pszDisplayName.Length);
                        
                    }
                    catch (Exception ex)
                    {
                        Log("Unsuccessful attempt to populate pchEaten:" + 
                              ex.Message , w);
                    }

                    try
                    {
                        Guid rclsid = new 
                              Guid("30194303-435D-44E1-9FB2-A625CEDB8B68");
                        retVal = Win32PInvoke.CreateClassMoniker(ref rclsid, 
                              out mon );
                    }
                    catch (Exception ex)
                    {
                        Log("Unsuccessful attempt to call CreateClassMoniker:" + 
                               ex.Message, w);
                    }
                    const int S_OK = 0;
                    if (retVal==S_OK)
                    {
                        //unsafe { 
                        //void* pvMon = (void*)mon;
                        //ppmkOut = new IntPtr(pvMon);
                        //} [StructLayout(LayoutKind.Explicit)]
                        try
                        {
                            //ppmkOut=mon;
                            //Marshal.StructureToPtr(mon, ppmkOut, true);
                            //https://searchcode.com/file/115732569/mcs/ ...
                            // ...class/referencesource/System.ServiceModel/ ...
                            // ...System/ServiceModel/ComIntegration/ ...
                            // ...ServiceMoniker.cs#l-159
                            
                            IntPtr ppv = InterfaceHelper.GetInterfacePtrForObject(
                                typeof(IMoniker).GUID, this);

                            System.Runtime.InteropServices.Marshal.WriteIntPtr(
                                  ppmkOut, ppv);
                            
                        }
                        catch (Exception ex)
                        {
                            Log("Unsuccessful attempt to " +
                                "call Marshal.StructureToPtr:" 
                                + ex.Message, w);
                        }
                    }
                }
            }


            void IMoniker.BindToObject(IBindCtx pbc, IMoniker pmkToLeft, 
                ref Guid riidResult, out object ppvResult)
            {
                ppvResult = this;
                //throw new NotImplementedException();
            }

            void IMoniker.BindToStorage(IBindCtx pbc, IMoniker pmkToLeft, 
                ref Guid riid, out object ppvObj)
            {
                throw new NotImplementedException();
            }

            void IMoniker.CommonPrefixWith(IMoniker pmkOther, 
                out IMoniker ppmkPrefix)
            {
                throw new NotImplementedException();
            }

            void IMoniker.ComposeWith(IMoniker pmkRight, bool fOnlyIfNotGeneric, 
                out IMoniker ppmkComposite)
            {
                throw new NotImplementedException();
            }

            void IMoniker.Enum(bool fForward, out IEnumMoniker ppenumMoniker)
            {
                throw new NotImplementedException();
            }

            void IMoniker.GetClassID(out Guid pClassID)
            {
                throw new NotImplementedException();
            }

            void IMoniker.GetDisplayName(IBindCtx pbc, IMoniker pmkToLeft, 
                out string ppszDisplayName)
            {
                throw new NotImplementedException();
            }

            void IMoniker.GetSizeMax(out long pcbSize)
            {
                throw new NotImplementedException();
            }

            void IMoniker.GetTimeOfLastChange(IBindCtx pbc, IMoniker pmkToLeft, 
                out System.Runtime.InteropServices.ComTypes.FILETIME pFileTime)
            {
                throw new NotImplementedException();
            }

            void IMoniker.Hash(out int pdwHash)
            {
                throw new NotImplementedException();
            }

            void IMoniker.Inverse(out IMoniker ppmk)
            {
                throw new NotImplementedException();
            }

            int IMoniker.IsDirty()
            {
                throw new NotImplementedException();
            }

            int IMoniker.IsEqual(IMoniker pmkOtherMoniker)
            {
                throw new NotImplementedException();
            }

            int IMoniker.IsRunning(IBindCtx pbc, IMoniker pmkToLeft, 
                                                 IMoniker pmkNewlyRunning)
            {
                throw new NotImplementedException();
            }

            int IMoniker.IsSystemMoniker(out int pdwMksys)
            {
                throw new NotImplementedException();
            }

            void IMoniker.Load(IStream pStm)
            {
                throw new NotImplementedException();
            }

            void IMoniker.ParseDisplayName(IBindCtx pbc, IMoniker pmkToLeft, 
                    string pszDisplayName, out int pchEaten, out IMoniker ppmkOut)
            {
                throw new NotImplementedException();
            }

            void IMoniker.Reduce(IBindCtx pbc, int dwReduceHowFar, 
                          ref IMoniker ppmkToLeft, out IMoniker ppmkReduced)
            {
                throw new NotImplementedException();
            }

            void IMoniker.RelativePathTo(IMoniker pmkOther, 
                                      out IMoniker ppmkRelPath)
            {
                throw new NotImplementedException();
            }

            void IMoniker.Save(IStream pStm, bool fClearDirty)
            {
                throw new NotImplementedException();
            }
        }

        internal static class InterfaceHelper
        {
            // only use this helper to get interfaces that 
            // are guaranteed to be supported
            internal static IntPtr GetInterfacePtrForObject(Guid iid, object obj)
            {
                IntPtr pUnk = Marshal.GetIUnknownForObject(obj);
                if (IntPtr.Zero == pUnk)
                {
                    //throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                    //new ArgumentException(SR.GetString(SR.UnableToRetrievepUnk)));
                }

                IntPtr ppv = IntPtr.Zero;
                int hr = Marshal.QueryInterface(pUnk, ref iid, out ppv);

                Marshal.Release(pUnk);

                if (hr != 0)
                {
                    throw new Exception("QueryInterface should succeed");
                }

                return ppv;
            }
        }    
    }

A shout out has to be made to a code search resoure called searchcode.com which helped me find sample fragments simply not anywhere on StackOverflow or on official Microsoft documentation.

On reflection there is nothing in this custom activation syntax that cannot be shipped in a method call after a COM server has been instantiated by New or CreateObject or the C# equivalents. If the COM server is remote then I suppose one saves a network round trip. LDAP would be a remote call in an enterprise. WMI could query remote resources. WCF is a remote-ing technology. So you can see the use cases. For same machine calls custom activation syntax's absence is no great loss.

C# - Embed Html page as resource in C# Winforms

So in previous posts I have shown a Winforms WebBrowser control navigating to a local html file.  It is possible to set the WebBrowser's DocumentStream property to a System.IO.Stream.  With the following code it is possible to create a System.IO.Stream from a string


        public static System.IO.Stream GenerateStreamFromString(string s)
        {
            MemoryStream stream = new MemoryStream();
            StreamWriter writer = 
                  new StreamWriter(stream,System.Text.Encoding.Unicode);
            writer.Write(s);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }


The string would be an Html string but we want to avoid files so it looks like we can add the file as an embedded resource.

I named mine HTMLPage1 and then it can be referred to in code with


            string myFile2 = WebBrowserInWinForms.Properties.Resources.HTMLPage1;

            System.IO.Stream s = GenerateStreamFromString(myFile2);


But beware, the document needs to complete loading and whilst that is happening the stream needs to kept alive so no early disposal. To solve define stream variable at class level and dispose later. So the code becomes

using System;
using System.Windows.Forms;
using System.IO;

namespace WebBrowserInWinForms
{
    public partial class Form1 : Form
    {
        Stream s = null;

        public Form1()
        {
            
            InitializeComponent();

            string myFile2 = 
               WebBrowserInWinForms.Properties.Resources.HTMLPage1;

            this.webBrowser1.DocumentCompleted += webBrowser1_DocumentCompleted;
            s = GenerateStreamFromString(myFile2);
            this.webBrowser1.DocumentStream = s;

        }

        private void webBrowser1_DocumentCompleted(object sender, 
                                   WebBrowserDocumentCompletedEventArgs e)
        {
            s.Dispose(); // finished with this now
            System.Windows.Forms.HtmlElement clickMe = 
                              this.webBrowser1.Document.Body.All["clickMe"];
            clickMe.Click += clickMe_Click;
        }

        public static Stream GenerateStreamFromString(string s)
        {
            MemoryStream stream = new MemoryStream();
            StreamWriter writer = 
                      new StreamWriter(stream,System.Text.Encoding.Unicode);
            writer.Write(s);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }
        
        void clickMe_Click(object sender, HtmlElementEventArgs e)
        {
            //...
        }
    }
}

Copying the HTML string into the resource editor is clunky, the resource string editor is probably just for words and phrases when one ships an edition in a foreign language. It would be nice if in Debug environment we could use the file navigate method and then for Release we'd do a custom build step and pack the files into the embedded resources with code. We'd need conditional compilation.

The embedded resources can be found in <$SolutionDirector$>/Properties/Resources.resx which is Xml so they are entitised, this looks possible for one day.

C# - Creating a simple Winforms and WebBrowser application to shows disk drives

Ok, so I've seen enough to be able to give a series of instructions as to how to build a simple Winforms app that uses the WebBrowser control and an HTML Gui to display a list of disk drives.

To begin we need an HTML file with a clickable anchor and the shell of a table into which we will writes rows (and delete rows when re-running).

1. Create a new Winforms project called WinformsBrowserShowDrives.
2. In Solution Explorer for the C# project select menu 'Add->New Item' and select an HTML Page, HTMLPage1.html.

Add the following HTML to HTMLPage1.html and save


<!DOCTYPE html>
<html>

<head>
    <title>Disk Drive List</title>
    <style>
        table {
            border: 1px solid black;
        }

        th, td {
            padding: 5px;
            text-align: left;
            font-family: Arial;
            font-size: 10pt;
        }

        th {
            background-color: darkblue;
            color: white;
        }

        tr:nth-child(even) {
            background-color: #f2f2f2;
        }

        a#clickMe:hover {
            background-color: #1A365B;
            color: white;
        }

        a#clickMe {
            background-color: #E0F1EA;
            color: #000000;
        }
    </style>
</head>

<body>
    <a id="clickMe">Click Me</a>
    <table>
        <tbody id="diskDrives">
            <tr>
                <th>Disk Drive</th>
                <th>Interface Type</th>
                <th>DeviceDesc</th>
                <th>DeviceSize</th>
            </tr>
        </tbody>
    </table>
</body>
</html>

3. Add a WebBrowser control to the WinForms.Form1. Expand the form to something close to a browser window's size.
4. Add the following code to the Form1.cs class

    public Form1()
    {
        InitializeComponent();

        Assembly myExe = Assembly.GetExecutingAssembly();
        string filePath = Path.GetDirectoryName(new Uri(myExe.CodeBase).LocalPath);
        string myFile = Path.Combine(filePath, @"..\..\HTMLPage1.html");

        if (File.Exists(myFile))
        {
            //this.webBrowser1.DocumentCompleted +
            this.webBrowser1.Navigate(myFile);
        }
    }

5. In the above code, right-click on "Assembly" and Resolve to get the correct Using.
6. In the above code, right-click on "Path" and Resolve to get the correct Using.
7. Build the Project, it should compile then run the project it should run and show the HTML page, if not diagnose file locations.

OK, so at this point the project should build and run and show the HTML in the form, yay! But it doesn't do much as the moment so let's add some active content. First thing we need to do is write an event handler for the DocumentComplete event because we have to wait for completion before accessing the Dom.

8. In the above code un-comment the commented line go to the plus sign at the end and type an equals sign this should bring up hints.
9. Follow the hint to press TAB and repeat. The C# IDE should create an event handler for you.

The code should now look like this


    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            Assembly myExe = Assembly.GetExecutingAssembly();
            string filePath = Path.GetDirectoryName(new Uri(myExe.CodeBase).LocalPath);
            string myFile = Path.Combine(filePath, @"..\..\HTMLPage1.html");

            if (File.Exists(myFile))
            {
                this.webBrowser1.DocumentCompleted += webBrowser1_DocumentCompleted;
                this.webBrowser1.Navigate(myFile);
            }
        }

        void webBrowser1_DocumentCompleted(object sender, 
                                         WebBrowserDocumentCompletedEventArgs e)
        {
            throw new NotImplementedException();
        }
    }

10. In the newly created code block replace with the following

    void webBrowser1_DocumentCompleted(object sender, 
                WebBrowserDocumentCompletedEventArgs e)
    {
        System.Windows.Forms.HtmlElement clickMe = 
            this.webBrowser1.Document.Body.All["clickMe"];
        //clickMe.Click +
    }

11. Use same trick as steps 8. and 9. to create a click handler for the clickMe anchor element. Code should look like this

    public Form1()
    {
        InitializeComponent();

        Assembly myExe = Assembly.GetExecutingAssembly();
        string filePath = Path.GetDirectoryName(new Uri(myExe.CodeBase).LocalPath);
        string myFile = Path.Combine(filePath, @"..\..\HTMLPage1.html");

        if (File.Exists(myFile))
        {
            this.webBrowser1.DocumentCompleted += webBrowser1_DocumentCompleted;
            this.webBrowser1.Navigate(myFile);
        }
    }

    void webBrowser1_DocumentCompleted(object sender, 
                WebBrowserDocumentCompletedEventArgs e)
    {
        System.Windows.Forms.HtmlElement clickMe = 
            this.webBrowser1.Document.Body.All["clickMe"];
        clickMe.Click += clickMe_Click;
    }

    void clickMe_Click(object sender, HtmlElementEventArgs e)
    {
        throw new NotImplementedException();
    }
}

Next we need to write a class that gives us some data, we'll Wiindows Management Instrumentation (WMI) to provide something interesting to look at.

11. In the Solution Explorer, on the C# Project node right-click and take 'Add->New Itm' and add a Class file called WMIDriveDeviceLister.cs
12. Add using directive "using System.Management;" at top of class
13. Add Reference to System.Management.dll otherwise you'll get compilation errors
14. Copy in the following code

using System;
using System.Collections.Generic;
using System.Text;
using System.Management;           // Add Reference to System.Management.dll

namespace WinformsBrowserShowDrives
{
    internal class WMIDriveDeviceLister
    {
        internal string ListDiskDevicesAsHTML()
        {
            List<Tuple<int, string, string, string>> driveDetails;
            driveDetails = this.ListDiskDevices();

            StringBuilder rows = new StringBuilder();
            foreach (Tuple<int, string, string, string> driveDetail in driveDetails)
            {
                rows.Append("<tr>"
                        + "<td>" + driveDetail.Item1 + "</td>"
                        + "<td>" + driveDetail.Item2 + "</td>"
                        + "<td>" + driveDetail.Item3 + "</td>"
                        + "<td>" + driveDetail.Item4 + "</td>"
                        + "</tr>");
            }

            return rows.ToString();
        }

        internal List<Tuple<int, string, string, string>> ListDiskDevices()
        {
            // Add Reference to System.Management.dll
            System.Management.ManagementScope scope = new
                System.Management.ManagementScope(@"\\.");
            System.Management.ObjectQuery query = new
                System.Management.ObjectQuery("Select * from Win32_DiskDrive");
            ManagementObjectSearcher searcher = 
                      new ManagementObjectSearcher(scope, query);

            Dictionary<Int32, ManagementObject> dicUnSort = 
                      new Dictionary<Int32, ManagementObject>();

            ManagementObjectCollection queryCollection = searcher.Get();

            foreach (ManagementObject m in queryCollection)
            {
                int driveIndex = Int32.Parse(m["Index"].ToString());
                dicUnSort.Add(driveIndex, m);
            }

            List<Tuple<int, string, string, string>> sortedList = 
                                 new List<Tuple<int, string, string, string>>();
            for (int i = 0; i < dicUnSort.Count; i++)
            {
                ManagementObject m = dicUnSort[i];
                string s1 = m["InterfaceType"].ToString();
                string s2 = m["Caption"].ToString();
                string s3 = (m["Size"] != null) ? m["Size"].ToString() : "";
                Tuple<int, string, string, string> driveDetails =
                    new Tuple<int, string, string, string>(i,
                    m["InterfaceType"].ToString(),
                    m["Caption"].ToString(),
                    (m["Size"] != null) ? 
                      (Int64.Parse(m["Size"].ToString()) / (1000000)).ToString()
                      : "");

                sortedList.Add(driveDetails);
            }
            return sortedList;
        }
    }
}


Now we're going to write a class to handle the population of the disk drive HTML table

15. In the Solution Explorer, on the C# Project node right-click and take 'Add->New Item' and add a Class file called DrivesTable.cs
16. Add using directive "using System.Windows.Forms;" at top of class
17. Copy in code below

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace WinformsBrowserShowDrives
{
    public static class MyHTMLExtensions
    {
        public static HtmlElement CreateElementWithInnerText(
                   this HtmlDocument doc, string elementTag, string innerText)
        {
            HtmlElement ele = doc.CreateElement(elementTag);
            ele.InnerText=innerText;
            return ele;
        }
    }

    internal class DrivesTable
    {
        protected HtmlDocument doc = null;
        protected HtmlElement tBody = null;

        internal DrivesTable(HtmlDocument doc, string tBodyId)
        {
            this.doc = doc;
            this.tBody = doc.Body.All[tBodyId];
        }

        internal void AddRows()
        {
            WMIDriveDeviceLister lister=new WMIDriveDeviceLister();
            List<Tuple<int, string, string, string>> driveDetails;
            driveDetails = lister.ListDiskDevices();

            foreach (Tuple<int, string, string, string> driveDetail in driveDetails)
            {
                HtmlElement tr = doc.CreateElement("tr");
                tr.Id = "diskDriveEntry";

                tr.AppendChild(doc.CreateElementWithInnerText("td", 
                    driveDetail.Item1.ToString()));

                tr.AppendChild(doc.CreateElementWithInnerText("td", 
                    driveDetail.Item2));

                tr.AppendChild(doc.CreateElementWithInnerText("td", 
                    driveDetail.Item3));

                tr.AppendChild(doc.CreateElementWithInnerText("td", 
                    driveDetail.Item4));

                tBody.AppendChild(tr);
            }
        }
    }
}


//internal void RemoveRows()
//{
//    HTMLDocument htmlDoc = (HTMLDocument)doc.DomDocument;
//    bool skippedFirst = false;
//    foreach (HtmlElement row in rotTBody.Children)
//    {
//        if (skippedFirst)
//        {
//            IHTMLDOMNode node = htmlDoc.getElementById(row.Id) as IHTMLDOMNode;
//            node.parentNode.removeChild(node);
//        }
//        skippedFirst = true;
//    }
//}


The final step is make the anchor click handler call into the new classes. So in the Form1.cs change button handler code to this below.

        void clickMe_Click(object sender, HtmlElementEventArgs e)
        {
            DrivesTable table = new DrivesTable(webBrowser1.Document, "diskDrives");
            table.AddRows();
        }

So the code should run and you should see a "ClickMe anchor" (sadly hover CSS does not work at the moment, SO Q pending) and if you clik on it the C# code will query WMI and then write out an HTML table. Press it again and it add more rows because currently remove rows is commented out to simplify this blog entry.