I am trying to make an ActiveX DLL with instancing type of GlobalMultiUse, and I want a constant in it to be accessible outside of the class it's defined in, like Functions and Subs already are. Yet whenever I try to define a constant as Public or Global, I get the error message "Constants, bla bla bla bla, are not allowed as members of object modules". The constant MUST be placed within the class if it is going to get the benefits of the class having an instancing type of GlobalMultiUse. I want it so that when a reference to the DLL is added in a VB6 program, all the functions are immediately available (no instances need to be created of the class), and I also want it so that all the CONSTANTS are immediately available. How do I do this?
Last edited by Ben321; Mar 28th, 2015 at 01:50 PM.
Re: How do I put global constants in my ActiveX DLL?
You create a new class, say: xyzGlobals. The class instancing property in the property page for that class must be: GlobalMultiUse. Within that class, declare all functions/subs, enums, and UDTs that you want exposed to the user. Declare these as Public. Off top of my head, don't recall if declaring constants as public generates an error or not. If it does, simply create a dummy Enumeration and add your constants in that
Now when a user references your AcitveX DLL, they should be able to use those declarations
Insomnia is just a byproduct of, "It can't be done"
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by LaVolpe
You create a new class, say: xyzGlobals. The class instancing property in the property page for that class must be: GlobalMultiUse. Within that class, declare all functions/subs, enums, and UDTs that you want exposed to the user. Declare these as Public. Off top of my head, don't recall if declaring constants as public generates an error or not. If it does, simply create a dummy Enumeration and add your constants in that
Now when a user references your AcitveX DLL, they should be able to use those declarations
Tried that with Constants, and it doesn't work. Only works with enums. Is this a bug in VB6? I think I've downloaded ActiveX DLLs from 3rd parties before that do have working constants, but I don't know how they make them. I doubt they use VC++, which (though it has more features than VB6) can only create Windows API style (StdCall) DLLs. It can't create ActiveX DLLs.
Re: How do I put global constants in my ActiveX DLL?
Each enum member is a constant. The simple workaround is as I described. Create a dummy enum, maybe titled: enumConstants? Add your constants to that enumeration. Enumeration values do not have to be referenced by their enumeration type, i.e., vbOkOnly is part of VB's VbMsgBoxStyle enum, but you can use it directly & don't have to use it like: VbMsgBoxStyle.vbOkOnly. Same applies in your case
However, one strong recommendation. Prefix all your constants/enum members with a couple characters that uniquely identify them as yours. Otherwise, your users can face the problem where a constant in your dll is named same as a constant in another project reference. That's never a good thing & to resolve that, the user then must use the duplicated constant with the appropriate enum name and/or DLL name. This really applies to every PUBLIC item in that class: enum, types, methods, etc
Edited: There is another workaround if you want to go that route
Create a public read-only property named as your "constant" that returns the value your constant would return. This is a standard workaround of providing string constants and/or a constant that needs to be a specific data type: double, date, integer, etc
Last edited by LaVolpe; Mar 28th, 2015 at 02:48 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: How do I put global constants in my ActiveX DLL?
It can occasionally be annoying but I think it was meant as a "feature" not a bug, i.e. it was never implemented intentionally to prevent abuse.
The documentation addresses this directly:
Providing Non-Numeric and Non-Integer Constants
The members of an Enum can have any value that fits in a Long. That is, they can assume any integer value from -2,147,483,648 to 2,147,483,647. When you declare a variable using the name of an Enum as the data type, you’re effectively declaring the variable As Long.
Occasionally you may need to provide a string constant, or a constant that isn’t an integer value. Visual Basic doesn’t provide a mechanism for adding such values to your type library as public constants, but you can get a similar effect using a global object with read-only properties.
If your component doesn’t contain a global object, such as Application, add a public class module named GlobalConstants to your project. Set the Instancing property to GlobalMultiUse.
For each constant you want to provide, add to the GlobalConstants class module a Property Get procedure that returns the desired value. For example, the following code provides Avogadro’s Number as a constant, and mimics the vbCrLf constant in Visual Basic.
Code:
Public Property Get Avogadro() As Double
Avogadro = 6.02E+23
End Property
Public Property Get vbCrLf() As String
vbCrLf = Chr$(13) & Chr$(10)
End Property
Because the Instancing property is GlobalMultiUse, a user of the component doesn’t have to explicitly create an instance of the GlobalConstants class in order to use the constants.
Whether intentional or accidental, it is what it is. Microsoft was clearly aware of it. Since we'll never get an updated native VB language there isn't any point in worrying about it.
Re: How do I put global constants in my ActiveX DLL?
Problem with Enums is they return variants. They don't return longs even. They return variants of type Long. Variants are a waste of memory space and take longer to process than normal data types.
Re: How do I put global constants in my ActiveX DLL?
Am I wrong in my assumption that VC++ 2010 Express can create only StdCall DLLs, and cannot create ActiveX DLLs? Is it possible to create ActiveX DLLs with VC++2010? Because I NEED to have a constant of type Double, because I want Pi to be a universally available constant, after a reference is added to my ActiveX DLL. If VB6 can't do it. I'm willing to (though I don't want to have to, I'm willing to) switch to VC++ to get it done. Can VC++2010 create ActiveX DLLs, or not?
Enums can only do Long type variants, not Double type variants.
Properties Gets are in fact Functions. They are NOT Constants. They take as much space in the final DLL file as a full fledged function. That is a waste of space. I only need 8 bytes for a Double type Constant. I need a lot more than that for a Function.
Last edited by Ben321; Mar 28th, 2015 at 03:49 PM.
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by Ben321
Problem with Enums is they return variants. They don't return longs even. They return variants of type Long. Variants are a waste of memory space and take longer to process than normal data types.
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by Ben321
Problem with Enums is they return variants. They don't return longs even. They return variants of type Long. Variants are a waste of memory space and take longer to process than normal data types.
Another way to look at this: MsgBox (vbLong = VarType(vbOkOnly))
Edited: Ok, VarType(variantVariable_thatcontains_longvalue)=vbLong also
But this should resolve the question:
Code:
Dim lVal As Long, v as Variant
v = -1&
CopyMemory lVal, v, 4& ' lVal will not contain -1 because v is Variant and memory address doesn't contain -1
CopyMemory lVal, vbYesNo, 4& ' lVal does equal vbYesNo
Originally Posted by Ben321
Properties Gets are in fact Functions. They are NOT Constants. They take as much space in the final DLL file as a full fledged function. That is a waste of space. I only need 8 bytes for a Double type Constant. I need a lot more than that for a Function.
Maybe a pertinent argument if one were trying to provide lots of 'double' type constants vs. just one?
It's already stated you are not going to get that type of public constant using VB. Also as stated by Microsoft (quote posted by Dilettante), not gonna happen. Whether VC allows it or not, don't know.
Last edited by LaVolpe; Mar 28th, 2015 at 04:49 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by LaVolpe
Another way to look at this: MsgBox (vbLong = VarType(vbOkOnly))
Edited: Ok, VarType(variantVariable_thatcontains_longvalue)=vbLong also
But this should resolve the question:
Code:
Dim lVal As Long, v as Variant
v = -1&
CopyMemory lVal, v, 4& ' lVal will not contain -1 because v is Variant and memory address doesn't contain -1
CopyMemory lVal, vbYesNo, 4& ' lVal does equal vbYesNo
Maybe a pertinent argument if one were trying to provide lots of 'double' type constants vs. just one?
It's already stated you are not going to get that type of public constant using VB. Also as stated by Microsoft (quote posted by Dilettante), not gonna happen. Whether VC allows it or not, don't know.
In a waveform generating program, it's very useful to have Pi available. But calling it as a Property Get millions of times, once for each sample at 48000 samples per second, would be a HUGE waste of time. Better to have Pi universally defined as 3.14159... in an ActiveX DLL so that it's accessible in any VB6 program that includes a reference that DLL. Problem is, it can't be done. So I need something, but can't get what I need. So what should I do?
Re: How do I put global constants in my ActiveX DLL?
Well, when speed is important I always declare what I need as local, even when values are globally accessible. In case of constants, as it seems is what you need, why don't you declare it where you need it? Aren't they just compiler directives in VB6 (replaced by their value before exe generation)?
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by Ben321
So what should I do?
Maybe perform a simple test. Loop a 100k times by calling a constant from your DLL and then another 100k calling the PI property. See how significant the difference really is. If you find the difference is unacceptable, then maybe cache the property to a variable & use the variable instead? Inform users of your DLL that PI is a property and not a constant?
Afterthought: A property that returns a long would be a more fair test. A double does require 8 byte assignment vs 4 bytes
Last edited by LaVolpe; Mar 28th, 2015 at 11:51 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: How do I put global constants in my ActiveX DLL?
What you seem to want is something like the ability to "inject" named constant values into the DLL's typelib, e.g.:
Code:
[
helpstring("Global constants.")
]
module Global
{
const double Pi = 3.141592653589;
};
Even if you managed this somehow you'll likely get garbage for the value. The problem is that the MIDL compiler does not support floating-point values here even though you get no compile error.
The double type is one of the base types of the interface definition language (IDL). This type can appear as a type specifier in typedef declarations, general declarations, and function declarators (as a function-return-type specifier and a parameter-type specifier). For the context in which type specifiers appear, see Interface Definition (IDL) File.
The double type cannot appear in const declarations.
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by dilettante
It can occasionally be annoying but I think it was meant as a "feature" not a bug, i.e. it was never implemented intentionally to prevent abuse.
The documentation addresses this directly...
Whether intentional or accidental, it is what it is. Microsoft was clearly aware of it. Since we'll never get an updated native VB language there isn't any point in worrying about it.
It's the right call by Microsoft. Many other languages and development tools explicitly disallow "global" constants for 3rd-party controls too.
The reasons are obvious: developers don't want 3rd party binary components to "inject" global constants into their project. What if a developer is already using his own version of that constant? What if their copy uses a different type, or is modified for some internal purpose?
For something as generic as Pi, it's ridiculous to try and force your version of the constant on everyone that uses your project.
Microsoft's provided suggestion (expose the constant as a property) is good advice. Another alternative is to supply a module with your component, with any constants the developer may find useful. They can then choose to comment out or rename duplicates as necessary.
Originally Posted by Ben321
In a waveform generating program, it's very useful to have Pi available. But calling it as a Property Get millions of times, once for each sample at 48000 samples per second, would be a HUGE waste of time. Better to have Pi universally defined as 3.14159... in an ActiveX DLL so that it's accessible in any VB6 program that includes a reference that DLL. Problem is, it can't be done. So I need something, but can't get what I need. So what should I do?
Obviously your component doesn't access Pi via the property. It accesses an internal copy of the constant, presumably the same constant that the Property Get statement returns. So performance isn't an issue.
The property only exists so outside developers can read the constant, if necessary.
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by dilettante
What you seem to want is something like the ability to "inject" named constant values into the DLL's typelib, e.g.:
. . .
Even if you managed this somehow ...
Constant types other than Long can be inserted into an existing ActiveX DLL by:
decompiling its TYPELIB resource via OLE/COM Object Viewer (OleView.exe) or similar tools,
editing the resulting IDL/ODL file,
compiling it
and finally, replacing the original TYPELIB resource with the updated TLB file via Resource Hacker (ResHacker.exe) or equivalent utilities.
Originally Posted by dilettante
... you'll likely get garbage for the value. The problem is that the MIDL compiler does not support floating-point values here even though you get no compile error.
. . .
The same goes for float (Single).
The obsolete MKTYPLIB tool performs better than the recommended MIDL tool in this regard. For example, the attached TLB file compiled from this ODL script:
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: How do I put global constants in my ActiveX DLL?
Yes, I think was listed among the differences between MIDL.EXE and MKTYPLIB.EXE somewhere on MSDN. But again Microsoft must have had their reasons, and considering how much time has passed with no reversal in policy the reasons must be holding up.
I'm sort of curious why the "Pi heavy" calculations aren't done in some Class in the DLL anyway. Time to refactor and get more encapsulation?
Re: How do I put global constants in my ActiveX DLL?
Also would be VERY useful to wrap constants in for APIs. I want to create a wrapper for file operation based APIs like CreateFile, and I want it in an ActiveX DLL file. Of course those APIs require certain constants like GENERIC_READ and GENERIC_WRITE. These constants would need to be included in my ActiveX DLL file for the DLL functions (that are wrappers for the API functions) to actually be useful.
GENERIC_READ, for example, would only need to ever be defined ONCE in any given project. There's not more than one possible value for it. There never would be any conflict between different developers, or between different projects.
Re: How do I put global constants in my ActiveX DLL?
Since there is no code involved you only need a typelib, not a DLL. There are tons of those around already, but you could always compile your own.
However there often are conflicts because things such as parameters and return values can be defined various ways to handle mappings between the entrypoint and VB6. For example entrypoints returning HRESULT-style values can be handled as returned Long values or "properly" in VB6 terms. Parameters might be handled as "ByRef" types or as ByVal Long pointer values. Many structs come in different versions of different lengths, structs with unions can be dealt with different ways.
Lots of possible compatibility issues, which is why there is no universal typelib and programmers still make new ones to fit specific scenarios.
Re: How do I put global constants in my ActiveX DLL?
Originally Posted by dilettante
Since there is no code involved you only need a typelib, not a DLL. There are tons of those around already, but you could always compile your own.
However there often are conflicts because things such as parameters and return values can be defined various ways to handle mappings between the entrypoint and VB6. For example entrypoints returning HRESULT-style values can be handled as returned Long values or "properly" in VB6 terms. Parameters might be handled as "ByRef" types or as ByVal Long pointer values. Many structs come in different versions of different lengths, structs with unions can be dealt with different ways.
Lots of possible compatibility issues, which is why there is no universal typelib and programmers still make new ones to fit specific scenarios.
How do you create a TypeLib with VB6? Can it be done? Does it require something other than VB6 to create one?
Also, how do you register and unregister a TLB? With AX DLLs and OCX controls, you just use regsvr32 (without, or with, the /u command line switch). I like to keep my computer clean, so if I ever need to unregister a TLB file, I would like to know how. Can you do it with regsvr32, or do I need some other tool?
Re: How do I put global constants in my ActiveX DLL?
You have to use one of the Microsoft IDL compilers or possibly a 3rd party compiler.
No, regsvr32.exe does not deal with typelibs. You might consider VB Type Library Registration Utility but be sure to add an elevation manifest or at least mark it "run as admin" to avoid trouble.
VB6 can use an unregistered typelib via the Browse button in the References dialog. This will often register it, so unregister it after ward. As long as your Project has been saved it will no longer need the typelib to be registered though it must still be wherever you had it in order to use it.
Careful though. I've seen cases where that utility will say a typelib is registered when it isn't and vice versa. Not quite sure what's up but you can always do a registry search on the typelib ID (a UUID) to be sure it gets unregistered.
You can also unregister a typelib with a script:
Code:
'Unregister a type library (.TLB file).
'
'RUN THIS AS AN ADMIN USER (on Vista or later you will
'be prompted for elevation).
'
'Drag the TLB's icon onto the icon for this file, or
'execute it from a command prompt as in:
'
' TLBUnreg.vbs fullpathtoTLBfile
'
'Based on:
' Registering/Unregistering TLB Files.
' Created by Jason Bock, 05/04/1999
' Original Idea Conceived by Bruce McKinney
'
Option Explicit
Private Const E_FAIL = &H80004005
Private Const TYPE_E_REGISTRYACCESS = &H8002801C
Private WinVer
Private Sub TLBAction()
With CreateObject("TLI.TLIApplication")
On Error Resume Next
.TypeLibInfoFromFile(WScript.Arguments(0)).UnRegister
If Err.Number = 0 Then
MsgBox "Succeeded", vbOkOnly, WScript.ScriptName
ElseIf Err.Number = E_FAIL Or Err.Number = TYPE_E_REGISTRYACCESS Then
MsgBox "Already unregistered?", vbOkOnly, WScript.ScriptName
Else
MsgBox "Failed" & vbNewLine & vbNewLine _
& "Unregistration error: " & Hex(Err.Number) _
& ", " & Err.Description, _
vbOkOnly, WScript.ScriptName
End If
End With
End Sub
With CreateObject("WScript.Shell")
WinVer = .RegRead("HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion")
If WScript.Arguments.Count < 1 Then
MsgBox "No arguments. Drag and drop the TLB file onto this script's icon."
ElseIf WScript.Arguments.Count < 2 Then
'One argument so we are the initial run.
If Fix(CSng(WinVer)) < 6 then
'Win2K or XP (run by admin user).
TLBAction
Else
'Vista or later, run ourself again passing the TLB name and requesting elevation
'passing a second (dummy) argument to let us know we're elevated on that run.
With CreateObject("Shell.Application")
.ShellExecute "wscript", _
"""" & WScript.ScriptFullName & """ """ _
& WScript.Arguments(0) & """ ELEVATED", , "runas"
End With
End If
Else
'We have our arguments so we are the secondary, elevated run on Vista or later.
TLBAction
End If
End With
The benefit of this approach is that an unregistered TLB won't get registered in the system. To verify this for yourself, run the test project attached to post #17 above.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Matt Curland has a plugin that can edit current ActiveX DLL/OCX typelib as post-compile action and append constants/apply other modifications.
cheers,
</wqw>
p.s. I'm regularly using EditTLB to check my components for external typelibs references on public interfaces, which is really dangerous.