0

I have a wierd application loading mechanism. I have a boostrapping exe file with all the other DLLs and the application itself inside it's resources. These files (assemblies) are extracted from resources and loaded ond emand because i'm attaching to the Assemblyresolve event of the current AppDomain.

 [STAThread]
        static void Main()
        {
            // if the command line contains extract then extract the dlls, next run will resolve assemblies from disk
            bool saveDllsToDisk = new List<string>(Environment.GetCommandLineArgs()).Contains("extract");
            // if the command line contains bin then use the bin folder instead of temp to extract dlls
            bool useBinFolder = new List<string>(Environment.GetCommandLineArgs()).Contains("bin");

            if (!Directory.Exists(tempFolder)) {
                Directory.CreateDirectory(tempFolder);
            }

            // the assembly resolver will get here because it will not find the dlls in the bin folder
            // we load assemblies in our specific way:
            // - if exists in our temp/bin folder load from there
            // - else load from resources
            // - if specified extract DLLs to the temp/bin folder
            AppDomain.CurrentDomain.AssemblyResolve += (sender, args2) =>
            {
                string name = new AssemblyName(args2.Name).Name;
                Debug.WriteLine("START LOADING " + name);

                Assembly assembly = null;
                string folder = useBinFolder ? binFolder : Path.Combine(tempFolder, APP_NAME);
                string fileName = name.Replace(".","_").Replace("#EXE#", ""); // in resources we use _ instead of .
                string extension = name.Contains("#EXE#") ? "exe" : "dll"; // hack for our embedded exe files
                name = name.Replace("#EXE#", ""); // hack for our embedded exe files

                if (File.Exists(Path.Combine(folder,String.Format("{0}.{1}", name, extension))))
                {
                    // load from file in app temp folder
                    assembly = Assembly.LoadFile(Path.Combine(folder, String.Format("{0}.{1}", name, extension)));
                }
                else
                {
                    // extract assembly from resources
                    byte[] assemblyBytes = (byte[])resMan.GetObject(fileName, CultureInfo.InvariantCulture);
                    assembly = Assembly.Load(assemblyBytes);

                    // if selected save to file so the next run JIT will resolve from disk
                    if (saveDllsToDisk)
                    {
                        string outDll = Path.Combine(folder, String.Format("{0}.{1}", name, extension));
                        using (var fs = File.Create(outDll))
                        {
                            fs.Write(assemblyBytes, 0, assemblyBytes.Length);
                        }
                    }
                }

                Debug.WriteLine("LOADED " + name);
                return assembly;
            };

            splashScreen = new frmSplash();
            // as soon as splashscreen starts animating assembly preloading will be launched on this eventhandler
            splashScreen.Started += new EventHandler(splash_Started);
            // splashscreen has finished fadein we must now wait for all libraries to be preloaded and set CanContinue
            splashScreen.Finishing += new EventHandler(splash_Finishing);
            splashScreen.CanContinue = false;

            // run splashscreen while preloading of DLLs is going on
            Application.Run(splashScreen);
        }

This bootstrapper has only one reference and that is to the main application, but it does not reference it in the main method to avoid assembly search at start. What I do is force assembly loading during the splashscreen animation:

ObjectHandle instance;

                string[,] assemblies = {
                     {"WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","dummy"}, // will go from GAC, not our resolver
                     {"PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","dummy"}, // will go from GAC, not our resolver
                     {"PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","dummy"}, // will go from GAC, not our resolver
                     {"WindowsFormsIntegration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","dummy"}, // will go from GAC, not our resolver
                     {"PresentationFramework.Aero, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","dummy"}, // will go from GAC, not our resolver
                     {"System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","dummy"}, // will go from GAC, not our resolver                     
                     {"AvalonDock, Version=1.3.3585.0, Culture=neutral, PublicKeyToken=85a1e0ada7ec13e4", "dummy"}, // out reference inside resources
                     {"AvalonDock.Themes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "dummy"}, // out reference inside resources
                     {"ICSharpCode.AvalonEdit, Version=4.0.0.5950, Culture=neutral, PublicKeyToken=9cc39be672370310", "dummy"}, // out reference inside resources
                     {"WPG, Version=2.0.4120.37542, Culture=neutral, PublicKeyToken=null","dummy"}, // out reference inside resources
                     {"WPGBrushEditor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","dummy"}, // out reference inside resources
                     {"HLSLEditor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","dummy"} // preload even our main file that will launch after the splashscreen
                };

                for (int i = 0; i <= assemblies.GetUpperBound(0); i++)
                {
                    try
                    {
                        instance = AppDomain.CurrentDomain.CreateInstance(assemblies[i,0], assemblies[i,1]);
                    }
                    catch (Exception ex) {
                        /* must not raise errors, it will fail because we are not actually asking for a
                         *  valid type and we only need this assembly loaded right now*/
                    }
                }

Now you see that there are no actual dll files in any folder but are assemblies directly loaded from resources.

My problem now is: How to register a COM object delared in one of my dll files?

RegAsm uses filepaths to register a COM object... :(

Any help appreciated!

3
  • Well, to my knowledge, you'll have to deserialize all your COM-visible assembly resources to the local filesystem anyway, or the regasm registration step will be meaningless. Your loading mechanism is indeed weird, can you clarify why you're jumping through those hoops? Commented Apr 18, 2011 at 20:54
  • the idea is to have one exe file with everything inside not requiring installation and that could be run from a read only folder if required... ultimately it will become a mixed c++ app with the bootstrapper checking for prerequisites (ie .Net) and alerting of missing ones, if nothing is missing it will run the app like it is now. The only problem I see are embedded COMs. Commented Apr 19, 2011 at 3:50
  • you can see a sample here: marino.boletus.hr/hallie.exe, but i'm unable to embed COMs in it :( Commented Apr 19, 2011 at 4:01

2 Answers 2

1

I'm doing something very similar in Excel-DNA. You don't have to deserialize the COM types to disk, or register them in advance. What I did is to define a class that implements IClassFactory, then register it as the current class factory for the particular CLSID (type) by calling CoRegisterClassObject. When COM tries to create the object, your class factory is called and can return an instance of your .NET type.

Depending on the context, you might have to deal with the ProgID / CLSID registration on the fly.

(If you contact me I'd be happy to help you grab the right bits from the Excel-DNA code to get you started.)

Sign up to request clarification or add additional context in comments.

2 Comments

I would like to see this for sure!
Thinking about this a bit more - you might be able to get away with just calling RegisterTypeForComClients. msdn.microsoft.com/en-us/library/….
0

I digged a little and maybe implementing DllRegisterServer() intot he main executable and then registering all the COM types in all the embedded DLLs would work...

Can anyone confirm?

EDIT: Yes it can be done, but the main executable must be a COM proxy to the DLLs inside.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.