Strange Case of the missing method: SXS and Contro

2020-03-04 09:08发布

I have a project written in VB6 that uses a UserControl, The project runs fine when the OCX is registered, but if I run the same project with a side by side manifest it results in an error.

I can use the Control with no problem as long as it's loaded statically (added before on the form) but if I add a dynamic control to form on any use of the new control (property or method) I get this error:

Object doesn't support this property or method

This error can be reproduced this way:

  1. Create an OCX project in VB6
  2. Add a user control
  3. Add a method, e.g. DoSomething to the control
  4. Create an exe project
  5. Add the control to form, e.g. UserControl1
  6. In an event call DoSomething
  7. Load dynamically Like:

    Dim y As Control
    UserControl1.DoSomething        '<-------- CASE(1) THIS IS ALLRIGHT!'
    Set y = Controls.Add("Project1.UserControl1", "y")
    y.DoSomething                   '<---- (CASE 2) THIS WILL FAIL USING SXS'
    

I tracked the error in WinDbg back to IDispatch::GetIDsOfNames that when called in second case will fail.

Any Idea?!

Edit: My bad, Here is the manifest.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

<assemblyIdentity name="client.exe" version="1.0.0.0" type="win32" processorArchitecture="x86"/>

<file name="Project1.ocx">
 <comClass
     clsid="{C8CF7991-A8F2-4360-9404-03C9A052C245}"
     description="Project1.UserControl1"
     tlbid="{47853CCC-7BE6-4377-9C82-38A0B7755F65}"
     threadingModel="apartment"
     miscStatusContent="recomposeonresize,cantlinkinside,insideout,activatewhenvisible,setclientsitefirst"
     progid="Project1.UserControl1"/>
 <typelib
     tlbid="{47853CCC-7BE6-4377-9C82-38A0B7755F65}"
     version="1.0"
     helpdir=""
     flags="control,hasdiskimage"/>
</file>

<comInterfaceExternalProxyStub 
     iid="{0E4F313E-7EF3-4FE6-9591-9F7D2D819AEE}"
     name="UserControl1"
     proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"/>
<comInterfaceExternalProxyStub 
     iid="{53307849-4F14-4A59-B0CA-DE4950CE499D}"
     name="UserControl1"
     proxyStubClsid32="{00020420-0000-0000-C000-000000000046}"/>

</assembly>

标签: c++ com vb6 ole sxs
2条回答
Deceive 欺骗
2楼-- · 2020-03-04 09:31

The short answer: This is a known problem with VB6. The "Controls.Add" statement does not work with side-by-side. Try using a "Load" statement instead.

The long answer: VB6 compiles the CLSID of the control into the executable even though you use a ProgID to create the control. Execution of "Control.Add" verifies that the CLSID is the same. Don't ask why, this is a mystery better not touched. At the same time win32 side-by-side (as opposed by .Net side-by-side -- another topic) must be prepared to handle the same ProgID being used by more than one manifest (when you flip-flop between activation contexts, for example) so it internally generates a new, temporary, CLSID for each ProgID. In the end when you call CLSIDFromProgID you will get the temporary CLSID. If you then call CoCreateInstance it works fine - sxs honors the CLSID. But if you go looking for the CLSID anywhere (registry, your internal table) you will not find it. And here comes the VB6 program calling CLSIDFromProgID then checking if the CLSID it receives is in the internal table. Fail.

查看更多
走好不送
3楼-- · 2020-03-04 09:42

This is a case of VB magic that is incompatible with manifests. The problem stems from VB's behavior with user control references. Even if you strongly Dim a variable As UserControl, accessing methods/properties on this reference is late-bound! VB creates an extension class on each referenced user control to expose common methods (like SetFocus, Move, etc) so when you Dim something As UserControl this is compiled not as a reference to the UserControl in the controls typelib, but to a VBControlExtender inherited class, in any case an auto-generated wrapper on UserControl.

What I'm doing since discovering the chapter on user controls in Curland's Advanced Visual Basic 6 book is to create a custom Direct User Controls typelib that forces VB not to use wrappers. Basically it looks like this:

[
  uuid(GUIDHERE-0000-1111-2222-2B5E1A72D6BF),
  version(1.0),
  helpstring("Direct User Controls Typelib 1.0")
]
library <<mytypelib>>
{
    importlib("stdole2.tlb");
    importlib("C:\\WINDOWS\\system32\\COMCTL32.ocx");
    importlib("C:\\WINDOWS\\system32\\COMCT232.ocx");
    importlib("C:\\WINDOWS\\system32\\shdocvw.dll");
    ...

    typedef [public] ComctlLib.ImageList                DirectImageList;
    typedef [public] ComctlLib.ListView                 DirectListView;
    typedef [public] ComctlLib.ProgressBar              DirectProgressBar;
    typedef [public] ComctlLib.Slider                   DirectSlider;
    typedef [public] ComctlLib.StatusBar                DirectStatusBar;
    typedef [public] ComctlLib.TabStrip                 DirectTabStrip;
    typedef [public] ComctlLib.Toolbar                  DirectToolbar;
    typedef [public] ComctlLib.TreeView                 DirectTreeView;

    typedef [public] ComCtl2.Animation                  DirectAnimation;
    typedef [public] ComCtl2.UpDown                     DirectUpDown;

    typedef [public] SHDocVw.WebBrowser                 DirectWebBrowser;
    ...
}

In my projects a keep "direct" references, calling methods early-bound. I use Controls.Add like this

Dim oCtl As DirectXxx
Set oCtl = pvCastVBControlExtender(Controls.Add(PROGID_Xxx, sName)).Object

where casting helper is something like this

Private Function pvCastVBControlExtender(oCtl As VBControlExtender) As VBControlExtender
    Set pvCastVBControlExtender = oCtl
End Function

This snippet works as expected in the three cases: in VBIDE, with registered controls and with registration free controls.

查看更多
登录 后发表回答