locked
Launching a .NET COM server from a native 32-bit process on Win7 64-bit

    Question

  • The thread "Launching a COM server from a service on WinXP 64-bit" (http://social.msdn.microsoft.com/Forums/en/netfx64bit/thread/42ec0c11-9df4-47b6-9a0e-a4c2c839c3ec) is the reverse of my situation.

    I have a Any CPU .NET out of process COM service that is invoked by a 32bit native application.  This process works on w2k3 64bit and win7 32bit.  It is not working on win7 64bit.  So instead of removing the COM registrations from the Wow6432Node areas, I removed the entries from the normal parts of the registry (non Wow6432Node sections).  This made no change to the behaviour, neither did removing the entries from the Wow6432Node areas instead.

    When it fails, the COM server process is started and as far as it is concerted, the RegisterTypeForComClients call is successful:

    RegistrationServices reg = new RegistrationServices();
    int type_library_cookie = reg.RegisterTypeForComClients(
    typeof(COMForm),
    RegistrationClassContext.LocalServer,
    RegistrationConnectionType.SingleUse);

    The code then enters a message loop and awaits a COM call to create the main object.  This request never arrives as back at the client side the CoCreateInstance call does not return for another 40 seconds (if I close the window opened for the COM server, which exists the process, if I wait it out, it takes 2 minutes).  When it does return, the result is 0x8008005 (CO_E_SERVER_EXEC_FAILURE).  The call is made as follows:

    HRESULT hr = CoCreateInstance( CLSID_Message, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&m_pObj )

    If I recompile the COM server as an x86 executable, it all works, but this should not be required.  The app works fine if launched manually (not for COM), or (as stated above) on w2k3 64bit.  On w2k3, the COM server is running as 64bit.

    Why is this not working?
    Any other ideas?
    Any other information required?

    Thank you,
    Warren.

    • Edited by Warren Stanley Friday, September 02, 2011 6:14 AM 2nd attempt
    Friday, September 02, 2011 6:01 AM

All replies

  • I have now looked at this further, and this has nothing to do with the bitness of the client COM process.  If the server process is compiled as Any CPU and the OS is 64bit, the COM call will fail as described on Win7 and Windows 2008 R2, but works on Windows 2003 R2.

     

    After some more research, this is just one more nail in the coffin off Any CPU executables (http://blogs.msdn.com/b/rmbyers/archive/2009/06/09/anycpu-exes-are-usually-more-trouble-then-they-re-worth.aspx).  It does not explain why it fails, and I still don’t think it should fail, but I am just going to set the project to x86 now and move on.

     

    However I would still like to know why it does not work.

     

    Below is some code that can be used to produce this issue (build with VS2008):

     

    using System;
    using System.Linq;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Security.Principal;
    using System.Threading;
    using Microsoft.Win32;
    
    namespace OutOfProcComServer
    {
        [ComVisible(true), Guid("7AD3F647-B72B-457F-A5A9-666898394B96")]
        public class ComObject
        {
            public const string USER_TYPE = "Test Object";
            public static Guid ClsID
            {
                get
                {
                    GuidAttribute a = (GuidAttribute)typeof(ComObject).GetCustomAttributes(typeof(GuidAttribute), false)[0];
                    return new Guid(a.Value);
                }
            }
    
            public static ManualResetEvent Created = new ManualResetEvent(false);
    
            public ComObject()
            {
                Console.WriteLine("ComObject()");
                Created.Set();
            }
        }
    
        class Program
        {
            public const int E_CANCELLED = 1223;
    
            static bool CreateIfNeeded(string path, out RegistryKey key)
            {
                Console.WriteLine("CreateIfNeeded({0})", path);
                RegistryKey base_key = Registry.ClassesRoot;
                key = base_key.OpenSubKey(path);
                if (key != null)
                {
                    key.Close();
                    base_key.DeleteSubKeyTree(path);
                }
                key = base_key.CreateSubKey(path);
                return true;
            }
    
            static string clsid = ComObject.ClsID.ToString("B").ToUpper();
            static string fullCommand = AppDomain.CurrentDomain.BaseDirectory + Environment.GetCommandLineArgs()[0];
    
            static void SetUpRegistry()
            {
                Console.WriteLine("SetUpRegistry()");
                string exe = fullCommand;
                Console.WriteLine(exe);
    
                RegistryKey key;
                if (CreateIfNeeded("CLSID\\" + clsid, out key))
                {
                    key.SetValue(null, ComObject.USER_TYPE);  // Human-readable name.
    
                    // Where to find an icon for this class.
                    key.CreateSubKey("DefaultIcon").SetValue(null, exe + ",0");
    
                    // Use OLE's default in-process handler DLL.
                    key.CreateSubKey("InprocHandler32").SetValue(null, "OLE32.DLL");
    
                    // What to run to instantiate a server.
                    key.CreateSubKey("LocalServer32").SetValue(null, String.Format("\"{0}\" /com", exe));
                }
            }
    
            static void DeleteIfExists(string path)
            {
                RegistryKey key = Registry.ClassesRoot;
                RegistryKey deleteKey = key.OpenSubKey(path);
                if (deleteKey != null)
                {
                    deleteKey.Close();
                    key.DeleteSubKeyTree(path);
                }
            }
    
            static void CleanUpRegistry()
            {
                Console.WriteLine("CleanUpRegistry()");
                DeleteIfExists(@"CLSID\" + clsid);
            }
    
            public static bool IsElevated
            {
                get
                {
                    return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
                }
            }
            
            static void Main(string[] args)
            {
                int shift = 0;
    
                bool rerunWithElevation = false;
                if (args.Length > shift && args[shift].ToLower() == "/unreg")
                {
                    if (IsElevated)
                    {
                        CleanUpRegistry();
                        return;
                    }
                    else
                        rerunWithElevation = true;
                }
    
                // Set up registry entries, if required.
                bool force_reg = args.Length > shift && new string[] { "/reg", "/regserver" }.Contains(args[shift].ToLower());
                if (force_reg)
                {
                    if (IsElevated)
                    {
                        SetUpRegistry();
                        return;
                    }
                    else
                        rerunWithElevation = true;
                }
    
                if (rerunWithElevation)
                {
                    try
                    {
                        Process.Start(
                            new ProcessStartInfo(fullCommand, String.Join(" ", args))
                            {
                                Verb = "runas"
                            }
                        );
                    }
                    catch (Win32Exception ex)
                    {
                        if (ex.NativeErrorCode != E_CANCELLED)
                            throw;
                    }
                    return;
                }
    
                bool startedForCom = false;
                if (args.Length > shift)
                {
                    string arg = args[shift].ToLower();
                    if (arg == "/com")
                    {
                        startedForCom = true;
    
                        shift++;
                        if (args.Length > shift)
                        {
                            // Docs say COM will always add -embedding, but I have
                            // not observed this on Windows 2000.
                            arg = args[shift++].ToLower();
                            if (arg == "-embedding" || arg == "/embedding"
                                || arg == "-automation" || arg == "/automation")
                                shift++;
                        }
                    }
                }
    
                if (startedForCom)
                {
                    RegistrationServices reg = new RegistrationServices();
                    int type_library_cookie = reg.RegisterTypeForComClients(typeof(ComObject), RegistrationClassContext.LocalServer, RegistrationConnectionType.SingleUse);
                    Console.WriteLine("waiting for ComObject()");
                    ComObject.Created.WaitOne();
                    Thread.Sleep(1000);
                    reg.UnregisterTypeForComClients(type_library_cookie);
                }
                else
                {
                    Type objectType = Type.GetTypeFromCLSID(ComObject.ClsID, true);
                    Console.WriteLine("attempting to create ComObject()");
                    try
                    {
                        object myObject = Activator.CreateInstance(objectType);
                        Console.WriteLine("created");
                    }
                    catch(COMException ex)
                    {
                        Console.WriteLine("Exception: {0}", ex.Message);
                    }
                }
            }
        }
    }
    
    


     

    Monday, September 05, 2011 5:57 AM
  •  

    Hi,

     

    I can not reproduce the issue as you described.

    Generally speaking, 32-bit COM can not be loaded into a 64-bit process.  If the COM is built for 32-bit, you can only run it on 64-bit OS when you build the application with x86 option.

     

    /platform


    Paul Zhou [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, September 05, 2011 7:24 AM
  • Hi Paul,

    I understand that a 64bit process can only load a 64bit dll, and likewise for 32bit processes, but this is a COM component that is implemented as an out of process component, and so COM should take care of the marshalling the calls across the interface contexts between the processes.

    I have reproduced the issue using a Win7 Ultimate x64 VM (1 or 2 CPUs), with or without SP1.

    The example code compiles in the context of a Visual Studio 2008 c# console application using default settings.  I then added x86 and x64 targets to the default Any CPU architectures and compiled each version.

    The registration code is not very robust after I changed from working with a forms application, so open an administrators command prompt, cd to the users and enter the following commands (assuming the solution was named test and the default locations used):

    ·         cd “Documents\Visual Studio 2008\Projects\test\test\bin\Release”

    ·         Test.exe /reg

    ·         Test.exe

    This last command will fail (or at least is does for me) with the following output after a two minute timeout:

    attempting to create ComObject()
    Exception: Retrieving the COM class factory for component with CLSID {7AD3F647-B72B-457F-A5A9-666898394B96} failed due to the following error: 80080005.

    Registering either of the x86 or x64 versions makes this work from any of the compiled versions, so to follow the sequence, the following will then all work:

    ·         cd ..\x64\Release

    ·         Test.exe /reg

    ·         Test.exe

    ·         ..\..\x86\Release\Test.exe

    ·         ..\..\Release\Test.exe

    ·         Test.exe /unreg

    ·         cd ..\x86\Release

    ·         Test.exe /reg

    ·         ..\..\x64\Release\Test.exe

    ·         ..\..\Release\Test.exe

    ·         Test.exe /unreg

    Each successful use of the COM interface should give the following output:

    attempting to create ComObject()
    created

    As I hope you will be able to see from the above, calling an out of process COM component with a different bitness does work, just not when the COM server is an Any CPU executable.  This command sequence (or similar) all works on win2003r2 x64.

     Regards,

    Warren Stanley.

    Tuesday, September 06, 2011 12:52 AM
  • Hi Warren,

    Seems the sample application has some issues to write application path in the registry key while it started in normal user previlidge command window. After I correct the application path in the registry key, I can repdouce the same issue. If I compile the application in X64, no problems running in the appication.

    I searched some materials, there is no sucn an known issue on Win2008/Win7, but I noticed that you use RegisterTypeForComClients.  Look at RegistrationServices.RegisterTypeForComClients (managed equivalent to CoRegisterClassObject) and RegistrationServices.UnregisterTypeForComClients on MSDN document, the RegistrationServices class doesn't support all the necessary APIs and the docs warn that this may be unpredictable in multi-threaded environments.  That being said, it has been attempted before and here is one such example from our own blog.

    http://blogs.msdn.com/adioltean/archive/2004/06/18/159479.aspx

    Customer may be able to get this working, but the fact is the dev team does not currently test this scenario so it could inadvertently be broken in any release of the .NET runtime.  The recommend approach for building out-of-proc COM servers in .NET which will be exposed to COM clients is to use managed COM+.  Obviously they can't do that with a managed EXE.   They can host a Remote object in their EXE and then provide COM clients with a Remoting component that connected to the Remoting object.  The client can load and call into the Remoting in-proc DLL via COM/Interop.  They could use this to 'automate' the server.  WCF might be something for you to consider since it has superseded Remoting if they want to explore this idea.

    If you have further assistance required on the root cause analysis, the question will fall into the paid support category which requires a more in-depth level of support.  Please visit the below link to see the various paid support options that are available to better meet your needs. http://support.microsoft.com/default.aspx?id=fh;en-us;offerprophone

    Freist Li [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, September 08, 2011 8:58 AM
  • Hi Freist,

    I did say the code for registration was not very robust, and it was also only part of the setup up, not the problem itself.

    In regards to the comments about multi-threaded environments, the process that calls RegistrationServices.RegisterTypeForComClients is single threaded upto the time after returning from the registration call.  I would have though the comment was mostly about having multiple threads calling RegisterTypeForComClients not being supported.

    My take on the last part of your reponse is that using RegisterTypeForComClients for an out of proc COM server not supported, even though it currently does appear to work in most circumstances.  The fact that registering the out of proc service needs to be done manually should be a clue, but the documentation could be improved around this area.

    Thank you,
    Warren.

    Sunday, September 11, 2011 11:40 PM
  • Warren,

    Have you had any progress on this issue? I have the exact same problem under Windows 8 64-bit, with a 64-bit COM server implemented in .NET 4.5. The server will act as if RegisterTypeForComClients is never called (i.e. cause the client to hang, or launch a new instance of the server), but the issue is resolved if the server is compiled as 32-bit (or Any CPU + prefer 32-bit).

    Regards,


    / David

    Wednesday, September 12, 2012 1:55 PM
  • Isn't RegisterTypeForComClients supported for out-of-proc COM server registration? The docs don't say that, and I thought that was the only use of the API.

    / David

    Wednesday, September 12, 2012 2:01 PM