views:

147

answers:

2

I have a VB6 project that references COMSVCSLib and one of the methods makes calls to COMSVCSLib's SharedPropertyGroupManager.CreatePropertyGroup passing LockMethod and Process as parameters.

Cleaned up VB6 code:

Dim groupName       As String
Dim spmMgr          As COMSVCSLib.SharedPropertyGroupManager
Dim spmGroup        As COMSVCSLib.SharedPropertyGroup

Dim bGroupExists    As Boolean

Set spmMgr = New COMSVCSLib.SharedPropertyGroupManager

With spmMgr
    Set spmGroup = .CreatePropertyGroup(groupName, LockMethod, Process, bGroupExists)

End With

Having not worked with VB6 for several years now, at first I thought LockMethod and Process were variables or constants defined somewhere else within the project.

After a little research on the Object Browser I found out that they were both being exposed as constants in COMSVCSLib.

Object Browser

But looking at their definition in OLE/COM Object Viewer, they seem to be defined as values of an enumeration:

typedef enum {
    LockSetGet = 0,
    LockMethod = 1
} __MIDL___MIDL_itf_autosvcs_0469_0002;

Why aren't IDL/TypeLib enums from COMSVCSLib being exposed as enums to Visual Basic 6.0?

+1  A: 

It is exposed as an enum. Select LockModes in the classes list and take a look at the lower information section. You'll see that it is an enum. Or you could type LockModes. in your code and you would get the two options.

In the object viewer, each item within an enum is identified as a constant value but it is not a standalone const. Standalone consts are listed separately when you select the <globals> item in the classes list.

Corin
Yeah... You're right about it being an enum. I ended up seeing that in the object browser a little bit before seeing your response. I also tried prefixing with LockModes and even __MIDL___MIDL_itf_autosvcs_0469_0002 without success. The code complies as it is. If I prefix it, it won't compile.
Alfred Myers
+5  A: 

Disclaimer: I'm not an expert on IDL (Interface Definition Language, which is the language used to define COM types) or the Microsoft IDL compiler (MIDL), but I came to the conclusions below after playing around with the type library for scrrun.dll, which has a similar problem with enum's. Some of this information was gleaned from a quick look at this DevX article on IDL and VB6: IDL for VB Tutorial

VB6 expects the actual enum to have a name, not just a enum that is typedef'd to a name. The __MIDL___MIDL_itf_autosvcs_0469_0002 name is a placeholder, since the original typelib didn't define the enum name in the same typedef where the enum constants are defined.

When you view the type library in OLE Viewer, the enum probably looks like this:

typedef [public] __MIDL___MIDL_itf_autosvcs_0469_0002 LockModes;

typedef enum {
    LockSetGet = 0,
    LockMethod = 1
} __MIDL___MIDL_itf_autosvcs_0469_0002;

The first typedef creates the public name LockModes as an alias for the auto-generated MIDL___MIDL_itf_autosvcs_0469_0002 name that was given to the enum. When the original type libary was compiled, the midl compiler generated the long __MIDL name for the original enum and automatically created a typedef alias pointing back to it.

The original IDL probably defined the enum like this:

typedef enum {
     LockSetGet = 0,
     LockMethod = 1
} LockModes;

When the midl compiler processes an enum definition written this way, it auto-generates a name for the enum (since it is missing - it should appear after the enum keyword). This is what the __MIDL name is that you see when you view the type library in OLE Viewer. The midl compiler also automatically generates a second typedef that aliases the typedef name to the auto-generated enum name.

The problem is that VB6 can't understand enum's that are created this way. It expects everything to be in a single typedef (i.e. you give the enum a name, as well as naming the typedef):

typedef enum LocksMode {
    LockSetGet = 0,
    LockMethod = 1
} LocksMode;

IDL treats typedef's the same way that C or C++ does: you don't have to give the enum itself a name, because the typedef already has a name, but you can give the enum a name if you choose. In other words, the typedef and the enum are actually two separate entities. VB6 happens to recognize the typedef and the enum as being two distinct, but vaguely-related things, so in your case it sees a typedef named __MIDL___MIDL_itf_autosvcs_0469_0002, and it sees that this is an alias to an unnamed enum, and it also sees a typedef for LockModes, which is a public alias for the other typedef.

Since the first typedef is public, you will see an entry for LockModes in the Object Browser, and because it is an alias for an enum, you will see the enum constants in the Object Browser as well. However, the actual enum itself does not have a name (so it gets the funky auto-generated name assigned to it in the browser), and VB6 can't use the enum because the auto-generated name happens to be illegal in VB6 (names with double-underscores are automatically hidden in VB6).

To demonstrate that last point, if you type this in your VB6 code, Intellisense will work and it will compile, but obviously, it's not very ideal:

MsgBox COMSVCSLib.[__MIDL___MIDL_itf_autosvcs_0469_0002].LockMethod

The reason this code works is because you can put names that normally cause syntax errors (such as names that start with underscores) in brackets to allow VB6 to accept the normally-illegal name. In addition, prefixing the constants with the auto-generated name works with Intellisense because it is the actual name that VB6 associates with the enum (remember the other typedef is just an alias back to this "real", but auto-generated name, and VB6 apparently can't put all the pieces together to realize both names refer to the same enum).

Rather than typing out the ridiculously-long name like above, you can also access enum constants by prefixing them with the library name, for example, COMSVCSLib.LockMethod should work. It's less clear to me why this actually works, and I'm not sure what would happen if two different enum's define constants with the same name.

Lastly, you could fix this problem a different way by using the IDL from the OLE Viewer to create a custom IDL file, in which you replace the existing enum typedefs with a single typedef for each enum that simply gives both the enum and the typedef the same name (i.e. typedef enum LockModes { ... } LockModes;), but since the OLE Viewer doesn't necessarily generate valid IDL, you would probably have to tweak it even more to get it to actually compile. If you can get this to work, then you can reference your custom .tlb from your VB6 project (instead of the COMSVCSLib library), and the enum's will work like you would expect them to.

If you want to go that route, there are two other tools you need, which should already been installed on your development machine (but you may need to search for them):

  • midl.exe: This tool can generate a typelib file (*.tlb) from a .idl file. So you could copy the IDL from the OLE Viewer into Notepad, modify the enum definitions as described above, save it as a .idl file, and pass it to midl.exe to make a new typelib:

    midl my-custom-typelib.idl

  • regtlib.exe: This tool can register a .tlb file, which is required if you want to be able to add it as a reference to your VB6 project:

    regtlib.exe my-custom-typelib.tlb

However, creating a custom typelib for this is probably overkill, and as already mentioned, it might be hard to get a compilable IDL file based off the output from the OLE Viewer, since it displays reverse-engineered IDL for the type library, not the original IDL.

Mike Spross
This is a long but inaccurate explanation of how things work. There is no problem with "unnamed" enums in VB6 .
wqw
@wqw: I'm not sure on all of the technical details - this answer is based on empirical tests. For example, if you take the IDL for scrrun.dll that OLE Viewer generates, and compile it into a new type library with `midl.exe`, VB6 can only see the enum's correctly if you give the enum's _and_ the typedef a name in the IDL. Otherwise, you will see the `enum` name in the Object Browser, but IntelliSense won't work correctly, and if you look at the enum constants in the Object Browser, it will say the enum's are a member of an enum with an auto-generated name.
Mike Spross
You don't even have to create a new type library to see this: Just add a reference to `scrrun.dll` to a new VB6 project, then open the Object Browser and look at the `SpecialFolderConst` enum. Yes, it shows up in the Object Browser, and yes the constants are listed for it, but if you click on the `SystemFolder` constant, for example, the Object Browser says it's a member of `Scripting.__MIDL___MIDL_itf_scrrun_0001_0000_0002`. It _should_ say that the constant is a member of `Scripting.SpecialFolderConst`, but the way the type library is defined is not 100% compatible with VB6.
Mike Spross
Also, I'm saying trying to say my answer is perfect (that' why I put a disclaimer there), but as far as I can tell, this explanation fits with actual testing. I created a new type library for sccrun.dll and changed the enum typedef's to include enum names in addition to the typedef names, and when I add that type library to VB6, the enum's show up correctly in the Object Browser, the enum constants are listed as members of the correct enum, and IntelliSense works correctly. If, however, I have the actual technical details wrong, could you provide an answer with the correct information?
Mike Spross
I tested this theory some more. If you iterate through all of the constants in `scrrun.dll` with `tlbinf32.dll` (a library that lets you programmatically read the data in a type library), it lists 2 entries for the `SpecialFolderConst` enum. One is an `ALIAS` (i.e. a `typedef`), and the other one is the actual `ENUM`. The real enum has the auto-generated `__MIDL` name. The `_MIDL` name is auto-generated by the `midl` compiler when you don't give an enum its own name. IntelliSense only works with the `ENUM` types in the type library, it doesn't recognize `ALIAS`'s.
Mike Spross
The `COMSVCSLib` type library has the same problem. The IDL defines atypedef for the `LockModes` enum (the enum itself has an auto-generated name), and a second typedef that aliases that typedef to the name `LockModes`. I tested this also: the `midl` compiler automatically creates the `ALIAS` and generates a random name for the `ENUM` definition if you don't give the enum its own name in the IDL: if you write `typedef enum {...} MyEnum;` and then compile it, midl will create an `ALIAS` called `MyEnum` that points to the actual `ENUM`, which is given an auto-generated name.
Mike Spross
Conversely, if you do give the enum a name in the IDL (i.e. `typedef MyEnum {...} MyEnum;` and compile it with `midl`, `midl` won't create an `ALIAS` - it will create a single `ENUM` entry in the type library, and it will have the name `MyEnum`. In this case, IntelliSense in VB6 will work the way you'd expect.
Mike Spross
@wqw: Maybe you can tip in telling us what is happening
Alfred Myers
@Mike Spross: IntelliSense in VB6 IDE works as expected in both cases. I'm putting a reference to COM+ Services and after `Dim eValue As LockModes` on `eValue = |` I get a dropdown with correct values. In `Private Sub pvTest(ByVal eValue As LockModes)` on `If eValue = |` I get correct values. Calling `pvTest |` I get correct values. The OP is looking at the Object Browser and it says `Const LockMethod = 1` and he think this is a constant in the IDL but it's not. Constants in the IDL get listed in a module in Object Browser not an enum (different icons).
wqw
wqw
@wqw: OK, we are talking about two different aspects of IntelliSense. Yes, when you do `Dim eValue As LockModes` and then type `eValue =`, IntelliSense shows you the `enum` members. _However_, if you type `eValue = LockModes.`, IntelliSense doesn't work correctly, and if you type `eValue = LockModes.LockMethod`, you will get a compile error saying that `LockModes` is not defined. That is the real issue here. VB6 can auto-complete the list if you don't use the `enum` name in your code , but if you do use the `enum` name (i.e. `LockModes.LockMethod`), it does not work.
Mike Spross
What I am saying is that the reason `LockModes.LockMethod` doesn't work is because in the `comsvcs` type library, the enum is not called `LockModes`, it is called `__MIDL___MIDL_itf_autosvcs_0469_0002`. The `LockModes` you see in the Object Browser is an alias to this auto-generated name, and this causes issues with IntelliSense.
Mike Spross
You are right. Apparently there *is* an issue with aliased enums. So this is a long *and* accurate explanation of how things work :-))
wqw
@wqw: No worries, it took me a while to figure it out exactly what the problem was, and we were both accurate about what we were saying, we just didn't realize it :-)
Mike Spross