locked
Interop problem using legacy COM RRS feed

  • Question

  • It was suggested that i look for more help in this forum.

    A legacy .tlb file is working fine using vb6.  The .tlb imports fine into vb.net and becomes an interop dll.

    a few of the classes seem to work ok or at least an obj can be created, however most will not work producing error

    Retrieving the COM class factory for component with CLSID failed due to the following error: 80040154

    AFter finding the legacy app documentation and reinstalling the part of the app as document says, (hoping to add to registry), still not working.  Then i meticulously created/imported .reg files to emulate the clsid's that worked.  The only change was a slightly different error of 80040111, still no help.  I have tried using the proexcep.exe and procmon.exe and depends.exe walker that does not seem to help not knowing exactly what to look for.  I do know the dlls that are used by the .tlb as written in the documentation.

    Would anyone know what to do next?  Thanks for any suggestions....

     


    Fingerstyler
    Friday, January 6, 2012 7:43 PM

Answers

  • Yes, it basically makes no sense to me that VB6 doesn't look up the registry keys.  It just doesn't.  COM object creation is usually done like this (simplified):

    1. From the "friendly name" of the coclass (COM object), obtain the CLSID from the registry.  Example:  Scripting.Dictionary is the friendly name and it maps to CLSID {EE09B103-97E0-11CF-978F-00A02463E06F} (see HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Scripting.Dictionary\CLSID).
    2. Using that CLSID, CoCreateInstance() is called.  Internally this does the lookup of the COM server binary in HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID.  In our example, HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{EE09B103-97E0-11CF-978F-00A02463E06F}\InprocServer32 with a value of C:\Windows\system32\scrrun.dll.
    3. LoadLibrary() is used to load the dll and then the entry point for DllGetClassObject() is obtained.
    4. DllGetClassObject() is called using the CLSID desired.  This returns an IClassFactory pointer that is ultimately used to create the desired object.

    All this should be happening in both .Net and VB6.  I see no other way around it.  In your case, the IClassFactory is returning 0x80040111 if the loaded DLL doesn't supply the COM object that corresponds to the given CLSID; and you get 0x80040111 probably from step 2 from the call to CoCreateInstance() when it doesn't find the desired CLSID under HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID.

    Now, you meantion TreatAs.  TreatAs is used when you upgrade a component in a very specific way:  You replace the old component with a brand new one.

    If a new coclass were created to replace Scripting.Dictionary called Scripting.SuperDictionary, then in order to maintain backwards compatiblity for users of Scripting.Dictionary, the SuperDictionary coclass would implement the old IDictionary interface.  But that is just not enough on systems simply lacking the old DLL (scrrun.dll) and having only the new dll (superscrrun.dll just to give it a name).  In this case, the entry for the OLD CLSID (the one for Scripting.Dictionary) is still added to the registry so it can be found and avoid error 0x80040111, but a TreatAs subkey is added to tell the COM subsystem that any COM consumers of this (old and long gone) coclass should receive an object of the new coclass (Scripting.SuperDictionary) by specifying the CLSID of Scripting.SuperDictionary in TreatAs.

    This effectively re-routes CoCreateInstance() to the new CLSID where the new dll name is picked up, meaning the consumer of the dictionary coclass will end up with what is in reality an instance of a SuperDictionary coclass, all this without breaking the old consumer because COM is in the end all about interfaces.

    Do you need TreatAs?


    Jose R. MCP
    • Marked as answer by Rob Pan Monday, February 6, 2012 9:00 AM
    Wednesday, January 11, 2012 8:18 PM

All replies

  • In .net, create a Win32Exception object passing to the constructor the error number.  Then examing the Message property.  This should give you the error message associated with the error number, just in case the exception you are currently getting doesn't have this already.

    What is the error message that you see?


    Jose R. MCP
    Friday, January 6, 2012 7:58 PM
  • I created a tiny test app where just trying to create 1 obj ->

    Dim

    myCALwork As New CALTypeLib.CALWorkitemWorkstepList

    It is really all the vb.net app is for the moment and as soon as that line is reached: 

    Retrieving the COM class factory for component with CLSID {id is here} failed due to the following error: 80040154


    Fingerstyler
    Friday, January 6, 2012 8:13 PM
  • That exception probably has an inner exception that depicts exactly the problem.

    But I can tell you this:  Not 20 minutes ago I was asked by a co-worker about this exact error message.  He had forgotten to register the COM dll using regsvr32.exe.  So I'm guessing that registration entries are missing for your COM server.


    Jose R. MCP
    Friday, January 6, 2012 8:19 PM
  • Ok, so I went to the trouble of using a small error lookup utility I have.  The error message is "Class not registered".  So there, registration entries are missing for that COM class.
    Jose R. MCP
    Friday, January 6, 2012 8:22 PM
  • That's it all right...Class not registered, but no matter what i do regsvr32 with all the dll's, even adding manually the most probable entry as mentioned above...i could only get a different error# that means about the same thing "80040111"

    So...something else wrong probably with the legacy tlb/dlls as far as interoperablity is concerned (works fine with vb6).  Is there anything else to try.  Same results on another work station.

     


    Fingerstyler
    Friday, January 6, 2012 8:58 PM
  • Error 0x80040111 is "ClassFactory cannot supply requested class".  I imagine this could happen if InprocServer32 points to the wrong DLL.

    Your errors boil down to the same thing:  A problem with the registration entries in the sytem registry.  You said you imported a type library.  That alone is not enough.  The actual COM servers that provide those objects must also be available in the PC and in the location specified by InprocServer32 or LocalServer32.  Also note that the windows registry is virtualized:  It is different for 64-bit and 32-bit applications.  When you register a COM object, it will register itself in the registry view that corresponds to it.  Are you working on 64-bit project?  Which regsvr32.exe file did you use for registration?  The 64-bit one is under C:\Windows\System32, while the 32-bit one is @ C:\Windows\SysWOW64.


    Jose R. MCP
    Friday, January 6, 2012 9:38 PM
  • Thanks both for your input.

    It is XP 32bit.  ALL The COM servers are registered (regsvr32.exe).  The dll's are where they are expected to be according to InprocServ32 etc. 


    Fingerstyler
    Monday, January 9, 2012 8:32 PM
  • Are the interfaces in these COM servers inheriting from IDispatch, or IUnknown?  If they inherit from IDispatch, can you instantiate all the required COM objects from a simple VBScript?  If they are not automatable (inheriting from IUnknown only), see if you can instantiate the COM objects using a simple C++ test project.


    Jose R. MCP
    Monday, January 9, 2012 8:39 PM
  • Here is the obj that won't work in a simple vb.net with interop dll. Not present in registry
      [
          odl,
          uuid(F86DE170-2A5B-11CF-A2A6-08005AC10759),
          helpstring("ICALWorkitemWorkstepList Interface"),
          dual,
          oleautomation
        ]
        interface _ICALWorkitemWorkstepList : IDispatch {
            [id(0x80013001), propget]
            HRESULT Count([out, retval] long* retval);
            [id(0xfffffffc), propget]
            HRESULT _NewEnum([out, retval] IUnknown** retval);
            [id(0x80013002), propget]
            HRESULT Item(
                            [in] long lOrdinal, 
                            [out, retval] _ICALEnumItem** retval);
        };
    
        [
          uuid(F86DE171-2A5B-11CF-A2A6-08005AC10759)
        ]
        coclass CALWorkitemWorkstepList {
            [default] interface _ICALWorkitemWorkstepList;
        };
    
    Here is one that does work vb.net with interop dll. present in registry.
       [
          odl,
          uuid(F2B991B6-1EA0-11CF-A2A0-08005AC10759),
          helpstring("ICALQueue Interface"),
          dual,
          oleautomation
        ]
        interface _ICALQueue : IDispatch {
            [id(0x80013001), propget]
            HRESULT Name([out, retval] BSTR* retval);
            [id(0x80013001), propput]
            HRESULT Name([in] BSTR retval);
            [id(0x80013002), propget]
            HRESULT Type([out, retval] QueueTypeConstants* retval);
            [id(0x80013002), propput]
            HRESULT Type([in] QueueTypeConstants retval);
            [id(0x80013003), propput]
            HRESULT Client([in] IDispatch* rhs);
            [id(0x80013004)]
            HRESULT GetNext(
                            [in] GetNextConstants Options, 
                            [out, retval] IDispatch** retval);
            [id(0x80013005)]
            HRESULT GetContents(
                            [in] long MaxMatches, 
                            [in] QueueContentsConstants Options, 
                            [out, retval] IDispatch** retval);
        };
    
        [
          uuid(F2B991B7-1EA0-11CF-A2A0-08005AC10759)
        ]
        coclass CALQueue {
            [default] interface _ICALQueue;
        };
    




    Fingerstyler

    Tuesday, January 10, 2012 6:32 PM
  • Ok so your intefaces are dual and use only OLE automation data types.  This means you can create a simple VBScript to test them out.  If VBScript can instantiate the classes, then so should .net.  If VBScript cannot, then you know you have a problem outside of .net.  So try this out:  Simple VBScript that instantiates the COM objects.
    Jose R. MCP
    Tuesday, January 10, 2012 8:04 PM
  • They work fine in VB6.  Will i still have to try the VBScript?
    Fingerstyler
    Tuesday, January 10, 2012 9:10 PM
  • If they work in classic VB6, I guess a VBScript test is not needed.  What I would do at this point is delete the references to the interop dlls, delete the auto-generated interop dll's and then retry adding the COM reference.  This will create new interop dll's, which are bound to work because registration issues no longer seem to be in the way.  Give it a shot.
    Jose R. MCP
    Wednesday, January 11, 2012 2:10 AM
  • Thanks Jose,

    Still the same error.  For some reason vb6 does not care about the registry as much as .net.  Even after the 3rd party app is reinstalled the registry is the same.  i have  checked 2 other workstations also the registry is without the keys that the .net error is complaining about yet the vb6 app works those stations as well.  don't know where to go next.

     


    Fingerstyler
    Wednesday, January 11, 2012 4:16 PM
  • Is the VB6 code equivalent to the .net code?  Meaning:  Are you doing the same thing in both sample projects?
    Jose R. MCP
    Wednesday, January 11, 2012 4:35 PM
  • just about exact.   vb.NET

    Imports CALTypeLib
    Imports System.Diagnostics
    
    Public Class Form1
        'CWM declares.  Declare for all CAL apps
        Public objCALMaster As CALMaster
        Public objCALClient As CALClient
    
        Private Sub Button1_Click() Handles Button1.Click
            Try
                Dim myCALwork As New CALTypeLib.CALWorkitemWorkstepList
                MessageBox.Show("OBJECT Instantiated.", "CALTest")
            Catch ex As Exception
                Dim myLog As EventLog = New EventLog()
                myLog.Source = "CALTest"
                myLog.WriteEntry(ex.ToString)
                Debug.Assert(False, ex.Message)
            End Try
        End Sub
    End Class
    

    vb6

    'CWM declares.  Declare for all CAL apps
    Public objCALMaster As CALMaster
    Public objCALClient As CALClient
    
    
    Private Sub cmdTest1_Click()
        Dim mycalWWList As New CALWorkitemWorkstepList
        
        MsgBox "OBJECT Instantiated.", vbOKOnly, "Success"
    End Sub
    
    




    Fingerstyler
    Wednesday, January 11, 2012 5:45 PM
  • i have the procmon logs (.pml) for each and one can easily see that vb.net calling registry as one would expect to create obj according to the idl (looking for clsid).  Those registry entries do not exist and legacy app does not create them during install.  Vb6 does not look for those keys in the registry even though one can see the code above is about same.  So after creating and importing several reg files based on a clsid for an obj that will work i can get from error 80040154 to 80040111 and looking at the procmon log kinda says it needs some more keys like "treatAs" which i won't have any idea on what class id to enter there?

     


    Fingerstyler
    Wednesday, January 11, 2012 5:57 PM
  • Yes, it basically makes no sense to me that VB6 doesn't look up the registry keys.  It just doesn't.  COM object creation is usually done like this (simplified):

    1. From the "friendly name" of the coclass (COM object), obtain the CLSID from the registry.  Example:  Scripting.Dictionary is the friendly name and it maps to CLSID {EE09B103-97E0-11CF-978F-00A02463E06F} (see HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Scripting.Dictionary\CLSID).
    2. Using that CLSID, CoCreateInstance() is called.  Internally this does the lookup of the COM server binary in HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID.  In our example, HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{EE09B103-97E0-11CF-978F-00A02463E06F}\InprocServer32 with a value of C:\Windows\system32\scrrun.dll.
    3. LoadLibrary() is used to load the dll and then the entry point for DllGetClassObject() is obtained.
    4. DllGetClassObject() is called using the CLSID desired.  This returns an IClassFactory pointer that is ultimately used to create the desired object.

    All this should be happening in both .Net and VB6.  I see no other way around it.  In your case, the IClassFactory is returning 0x80040111 if the loaded DLL doesn't supply the COM object that corresponds to the given CLSID; and you get 0x80040111 probably from step 2 from the call to CoCreateInstance() when it doesn't find the desired CLSID under HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID.

    Now, you meantion TreatAs.  TreatAs is used when you upgrade a component in a very specific way:  You replace the old component with a brand new one.

    If a new coclass were created to replace Scripting.Dictionary called Scripting.SuperDictionary, then in order to maintain backwards compatiblity for users of Scripting.Dictionary, the SuperDictionary coclass would implement the old IDictionary interface.  But that is just not enough on systems simply lacking the old DLL (scrrun.dll) and having only the new dll (superscrrun.dll just to give it a name).  In this case, the entry for the OLD CLSID (the one for Scripting.Dictionary) is still added to the registry so it can be found and avoid error 0x80040111, but a TreatAs subkey is added to tell the COM subsystem that any COM consumers of this (old and long gone) coclass should receive an object of the new coclass (Scripting.SuperDictionary) by specifying the CLSID of Scripting.SuperDictionary in TreatAs.

    This effectively re-routes CoCreateInstance() to the new CLSID where the new dll name is picked up, meaning the consumer of the dictionary coclass will end up with what is in reality an instance of a SuperDictionary coclass, all this without breaking the old consumer because COM is in the end all about interfaces.

    Do you need TreatAs?


    Jose R. MCP
    • Marked as answer by Rob Pan Monday, February 6, 2012 9:00 AM
    Wednesday, January 11, 2012 8:18 PM
  • Thanks Jose,

    The only class related hit on the registry for the vb6 app according to procmon is the standard IDispath interface.

    Wont' be needing TreatAs.  Don't know what to try next.


    Fingerstyler
    Wednesday, January 11, 2012 10:00 PM