A guide for language/IDE developers who want to reproduce how VB6 surfaces COM
type libraries in its two pickers (References and Components) and decides
the control namespace name (and when to append the Ctl suffix).
Everything below was verified against a real working OCX (UniCCtl.ocx, library
FLAGS=2, no suffix) and a from-scratch one (FLAGS=0, suffix appears).
| Picker | Purpose | Underlying registry source |
|---|---|---|
| References (Project ▸ References) | Nonvisual type libraries: classes, automation servers, enums, structs | HKCR\TypeLib |
| Components (Project ▸ Components, Ctrl+T) | Visual ActiveX controls that go in the Toolbox | HKCR\CLSID\{…} with the Control marker |
A single file (an .ocx) usually registers both a type library and one or
more control coclasses, so it can be a candidate for both lists at once. That
overlap is what creates the naming question.
Enumerate HKCR\TypeLib\{libid}\{version} and, for each, read the
win32 (or win64) sub-key path and the FLAGS value
(= ITypeLib::GetLibAttr().wLibFlags). Include the library unless any of:
wLibFlags & LIBFLAG_FRESTRICTED (0x1) -> never show
wLibFlags & LIBFLAG_FHIDDEN (0x4) -> hide from users
wLibFlags & LIBFLAG_FCONTROL (0x2) -> "describes controls; do not show
in browsers meant for NONVISUAL
objects" -> References hides it
So References shows a library iff wLibFlags has none of FRESTRICTED / FHIDDEN /
FCONTROL. (FCONTROL is the one that matters for controls — it is the switch that
moves a library out of References and into "Components only".)
Pseudo:
for each registered typelib T:
if T.wLibFlags & (FRESTRICTED | FHIDDEN | FCONTROL): continue
referencesList.add(T) # displayed as T.libraryName
Components is not driven by the type library; it is driven by the
component-category / control registration under each CLSID. A CLSID is a control
candidate when it has the legacy Control subkey or lists
CATID_Control ({40FC6ED4-2438-11CF-A3DB-080036F12502}) under
Implemented Categories:
HKCR\CLSID\{clsid}\Control (empty key), or
HKCR\CLSID\{clsid}\Implemented Categories\{CATID_Control}
HKCR\CLSID\{clsid}\InprocServer32 = path, ThreadingModel = Apartment
Enumerate those CLSIDs (preferably via ICatInformation::EnumClassesOfCategories
with CATID_Control). For each, resolve its owning type library (via
HKCR\CLSID\{clsid}\TypeLib + \Version) so you know which library/coclass pair
the control belongs to.
Pseudo:
for each clsid C implementing CATID_Control (or with the Control key):
lib = typelib of C
componentsList.add( controlEntry(lib, coclassNameOf(C)) )
A control entry is later instantiated on a form as <projectedLibName>.<coclass>.
When code/designers reference a type, VB6 projects each type library into a VB
namespace named after the library (ITypeLib::GetDocumentation(-1) name, e.g.
TextBoxExLib). The conflict: the same library can be visible in both lists,
and the form designer must name a control instance unambiguously
(Begin <ns>.<coclass>).
VB6 resolves it like this:
isInReferences = (lib visible in References) # see section 2
isInComponents = (lib owns >=1 CATID_Control coclass) # see section 3
if isInComponents and isInReferences:
# library lives in BOTH worlds -> disambiguate the control projection
controlNamespace = lib.name + "Ctl" # e.g. TextBoxExLibCtl
referenceNamespace = lib.name # e.g. TextBoxExLib
elif isInComponents: # controls only (FCONTROL set -> not in References)
controlNamespace = lib.name # e.g. TextBoxExLib (NO suffix)
else: # references only
referenceNamespace = lib.name
Rule of thumb for the Ctl suffix: append Ctl to the control namespace
iff the same library is simultaneously offered in References and in
Components. Set LIBFLAG_FCONTROL on the library and it drops out of
References, the dual visibility disappears, and the suffix is no longer added.
Notes:
- The suffix is purely a disambiguation device, not a semantic marker. Nothing in COM mandates it; it is VB6 IDE policy.
- The coclass-level
TYPEFLAG_FCONTROL(TYPEATTR.wTypeFlags & 0x20) marks an individual coclass as a control (placeable / toolbox-able). The library-levelLIBFLAG_FCONTROL(wLibFlags & 0x2) is the separate switch that hides the whole library from the nonvisual (References) browser. They are independent.
| Desired behavior | Library wLibFlags |
Result in VB6 |
|---|---|---|
Show in both lists, Ctl suffix on control |
0 |
LibName (References) + LibNameCtl (form) |
| Controls only, no suffix (typical OCX) | FCONTROL = 2 |
only Components, LibName.Coclass |
| Fully hidden from users but usable | FCONTROL|FHIDDEN = 6 |
only Components; host builds wrapper .oca |
The flag is baked into the .tlb from IDL (control attribute on the library
statement) and copied to HKCR\TypeLib\{libid}\{ver}\FLAGS by RegisterTypeLib.
It is not a DllRegisterServer category decision.
# Discovery
references = [ T for T in registeredTypeLibs
if not (T.wLibFlags & (FRESTRICTED|FHIDDEN|FCONTROL)) ]
controls = enumerateClasses(CATID_Control) # -> (clsid, lib, coclass)
# Naming for a control coclass on a form
def controlNamespace(lib):
inRefs = lib in references
inComps = any(c.lib == lib for c in controls)
return lib.name + ("Ctl" if (inRefs and inComps) else "")
That reproduces VB6's observable behavior: which entries appear in each picker,
and exactly when the Ctl suffix is appended.