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:
- Create an OCX project in VB6
- Add a user control
- Add a method, e.g.
DoSomething
to the control
- Create an exe project
- Add the control to form, e.g.
UserControl1
- In an event call
DoSomething
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>
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.
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.