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();
                }
            }
        }
    }
}

No comments:

Post a Comment