can i create a class on a module(not class module)?
Printable View
can i create a class on a module(not class module)?
I don't understand the question.
Can you instantiate a class from within a standard (BAS) module? Yes, of course. You'd do that the same way you'd instantiate the class anywhere else:
Code inside any procedure of a standard (BAS) module:
Regards,Code:Dim o As Class1
Set o = New Class1
-- or --
Dim o As New Class1
Elroy
i was asking if i can create class's on bas module
Ohhhh, wait. Are you trying to instantiate a standard (BAS) module? Noooo, you can't do that.
I've been at this long enough to where I remember the old Basic PDS system that ran on MSDOS/PCDOC. In that system, you had to separately compile, and then link your executables. In the linker command, you had to specify what was "core" code and what was pulled in later as "overlays".
The COM architecture is much more sophisticated than that, but, IMHO, the analogy still holds. In other words, standard BAS modules are your "core" code. You only get one copy of it and it's always all pulled into memory when you execute your EXE. All FRM, CLS, and CTL modules are COM objects though and are only "instantiated" upon your request, and then they're pulled from the EXE into memory (very similar to the overlays of days-gone-by).
Regards,
Elroy
EDIT1: Before Olaf jumps on me, let me also say that you only get one copy of the COM object's code as well (FRM, CLS, CTL, etc). However, you get as many copies of the variables declared in those as you do instantiations of them. You only need one copy of the actual code (machine code or p-code) because it's re-entrant, and can be used repeatedly by several actual instantiations of the class/object. But it's nice that the COM object's code is pulled from the EXE only when you instantiate it for the first time, and not upon initial execution of the EXE.
EDIT2: Also, the fact that you can have many BAS modules is really just an organizational convenience. Once compiled, it just all becomes your "core" code. However, that's far from true with respect to CLS, FRM, CTL, etc class modules. In that case, those modules become true COM objects, ready for instantiation.
Say joaquim,
Please take this in a helping light. I'd like to also point out some VB6 (and really, any COM program) language. The "class" is really the compiled code in an executable, waiting to be instantiated, and not yet in memory. Once you've instantiated it, it then becomes the "object". Therefore, an "object" is an instantiation (in memory) of a "class" (in the EXE).
Also, just to make things more confusing, forms and user controls are also classes. They just also have a visual (Windows) component to them.
Best Regards,
Elroy
You can make code in a module behave much like a single Public instance of a Class. A module can (weirdly) contain Properties and so these can be used to manipulate variables which are otherwise Local to the module. Actions can be triggered using Public Subs (Methods) just like a 'real' Class.
The drawbacks are;
You only have one Public instance - a 'real' Class allows use of multiple instances
The code is always loaded into memory - the code for a 'real' Class is only loaded into memory when the first instance is created and that memory is retrieved (typically after a short time delay) when the last instance falls back to Nothing.
Yeah, it's always been a bit strange that you can have BAS level properties, sort of global properties.
Also, just to build on Magic's comment a bit, you can actually use your module names much like class names. For instance, if you have two BAS modules (Module1 & Module2). And let's say they both have a function named MyFunction. If you then try to use this MyFunction, you'll get an "Ambiguous Name" error upon compiling. However, if you use it by saying "Module1.MyFunction" or "Module2.MyFunction", then it'll work just fine.
However, again, this is all just a convenience. Once things are compiled, all of that gets boiled down to memory addresses, and it's a mistake to ever think of those BAS modules as classes.
Regards,
Elroy
But that's essentially what it is - a shared class ... it happens to be shared at the application level, which is what makes it handy for global stuff. But essentially that's what it is... a shared class at the application level.Quote:
it's a mistake to ever think of those BAS modules as classes.
-tg
Say techgnome, it's not worth a squabble, but I don't think that's right. Do you think that they're "instantiated" when the EXE fires up? I've mucked around in the EXE files quite a bit, and I've never been able to pull out a list of BAS module names, but I can pull out a list of class and form module names following a fairly well documented path.
I'm not 100% certain, but I was always under the impression that BAS modules were totally compiled down to machine code (or p-code), with nothing left of them but op-codes and relocatable memory addresses. Whereas class and form modules preserve their name as well as the names of all the public (and friend) methods and properties.
I'd certainly agree that they can "look" like classes when viewed from the source code, but I was always under the impression that all that was gone once they were compiled, i.e., no COM object in sight.
If that's not the case, then, maybe through some trickery, they actually can have other copies instantiated.
Regards,
Elroy
EDIT1: As further supporting evidence of this, there's no mechanism to do something like...
...thereby making aliased references to the BAS objects.Code:Dim m As Module1
Set m = Module1
You can create so-called Light-Weight COM objects. When you've had these objects you have full-control over them. For example you can create an object on the stack or initialize thousands objects very fast. The easiest approach is to create a type library with needed interfaces for light-weight objects. For example, before you create the type library with the test interface ILightWeightObject:
This interface just has the one property TestProp (for reading and writing) and single method ShowMsg. Let the ShowMsg method shows simple MsgBox, and TestProp property keeps the internal object variable which you can modify.Code:[uuid(4d12246f-7e51-444c-1234-e39b969a08d8),
helpstring("LightWeight COM-object"),
version(1.00)]
library LightWeightCOM{
importlib("stdole2.tlb");
[odl,
uuid(8adec83e-64b0-46cf-bae9-7d8adbb6629e)]
interface ILightWeightObject: IUnknown {
[propget]
HRESULT TestProp([out, retval] BSTR *pRet);
[propput]
HRESULT TestProp([in] BSTR pValue);
HRESULT ShowMsg([in] BSTR pPrompt);
};
}
Then you should create a module that contains the code of methods and creation subroutine:
The CreateLightWeightObject routine creates an instance of Light-Weight object and returns the reference to ILightWeightObject interface that object supports. As bonus you can make the constructors and pass the parameters during creation time. It just fills the Virtual-Functions-Table and moves the object (that is represented as UDT) to arbitrary memory. It frees temporary variable by writing zeroes there as well in order to VB6 doesn't free our string member. Then you can test it:Code:Option Explicit
Private Const E_NOINTERFACE As Long = &H80004002
Private Type UUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
Private Type tILightWeightObject_vTable
pQueryInterface As Long
pAddRef As Long
pRelease As Long
pTestProp_get As Long
pTestProp_put As Long
pShowMsg As Long
End Type
Private Type tLightWeightObject
pInterface As Long
lRefCount As Long
sProperty As String
End Type
Private Declare Function IIDFromString Lib "ole32.dll" ( _
ByVal lpsz As String, _
ByVal lpiid As Long) As Long
Private Declare Function IsEqualGUID Lib "ole32" ( _
ByRef rguid1 As UUID, _
ByRef rguid2 As UUID) As Boolean
Private Declare Function CoTaskMemAlloc Lib "ole32" ( _
ByVal cb As Long) As Long
Private Declare Function GetMem4 Lib "msvbvm60" ( _
ByRef src As Any, _
ByRef Dst As Any) As Long
Private Declare Sub SysFreeString Lib "oleaut32.dll" ( _
ByVal bstr As Long)
Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" ( _
ByRef Destination As Any, _
ByRef Source As Any, _
ByVal Length As Long)
Private Declare Sub ZeroMemory Lib "kernel32" _
Alias "RtlZeroMemory" ( _
ByRef Destination As Any, _
ByVal Length As Long)
Private Declare Sub CoTaskMemFree Lib "ole32" ( _
ByRef pv As Any)
Private tIID_IUnknown As UUID: Private Const IID_IUnknown As String = "{00000000-0000-0000-C000-000000000046}"
Private tIID_ILightWeightObject As UUID: Private Const IID_ILightWeightObject As String = "{8adec83e-64b0-46cf-bae9-7d8adbb6629e}"
Private tVTable As tILightWeightObject_vTable
Public Function CreateLightWeightObject( _
ByRef sDefProp As String) As ILightWeightObject
Dim tNewObj As tLightWeightObject
Dim pNewObj As Long
If tVTable.pQueryInterface = 0 Then
tVTable.pQueryInterface = GetAddress(AddressOf ILightWeightObject_QueryInterface)
tVTable.pAddRef = GetAddress(AddressOf ILightWeightObject_AddRef)
tVTable.pRelease = GetAddress(AddressOf ILightWeightObject_Release)
tVTable.pTestProp_get = GetAddress(AddressOf ILightWeightObject_TestProp_get)
tVTable.pTestProp_put = GetAddress(AddressOf ILightWeightObject_TestProp_put)
tVTable.pShowMsg = GetAddress(AddressOf ILightWeightObject_ShowMsg)
End If
tNewObj.lRefCount = 1
tNewObj.pInterface = VarPtr(tVTable)
tNewObj.sProperty = sDefProp
pNewObj = CoTaskMemAlloc(Len(tNewObj))
If pNewObj = 0 Then Exit Function
CopyMemory ByVal pNewObj, ByVal VarPtr(tNewObj), Len(tNewObj)
ZeroMemory ByVal VarPtr(tNewObj), Len(tNewObj)
GetMem4 pNewObj, CreateLightWeightObject
End Function
Private Sub KillLightWeightObject( _
ByRef tObj As tLightWeightObject)
SysFreeString VarPtr(tObj.sProperty)
CoTaskMemFree ByVal VarPtr(tObj.pInterface)
End Sub
Private Function ILightWeightObject_QueryInterface( _
ByRef tObj As tLightWeightObject, _
ByRef tIID As UUID, _
ByRef pOut As Long) As Long
Select Case True
Case IsEqualGUID(tIID, tIID_IUnknown), IsEqualGUID(tIID, tIID_ILightWeightObject)
pOut = VarPtr(tObj.pInterface)
Case Else
ILightWeightObject_QueryInterface = E_NOINTERFACE
pOut = 0
End Select
End Function
Private Function ILightWeightObject_AddRef( _
ByRef tObj As tLightWeightObject) As Long
tObj.lRefCount = tObj.lRefCount + 1
ILightWeightObject_AddRef = tObj.lRefCount
End Function
Private Function ILightWeightObject_Release( _
ByRef tObj As tLightWeightObject) As Long
tObj.lRefCount = tObj.lRefCount - 1
ILightWeightObject_Release = tObj.lRefCount
If tObj.lRefCount = 0 Then KillLightWeightObject tObj
End Function
Private Function ILightWeightObject_TestProp_get( _
ByRef tObj As tLightWeightObject, _
ByRef sRet As String) As Long
sRet = tObj.sProperty
End Function
Private Function ILightWeightObject_TestProp_put( _
ByRef tObj As tLightWeightObject, _
ByVal sValue As String) As Long
tObj.sProperty = sValue
End Function
Private Function ILightWeightObject_ShowMsg( _
ByRef tObj As tLightWeightObject, _
ByVal sValue As String) As Long
MsgBox sValue
End Function
Private Function GetAddress( _
ByVal lValue As Long) As Long
GetAddress = lValue
End Function
Everything work fine.Code:Option Explicit
Public Sub Main()
Dim cObj As ILightWeightObject
Set cObj = CreateLightWeightObject("Def property")
cObj.ShowMsg cObj.TestProp
cObj.TestProp = "Test value"
cObj.ShowMsg cObj.TestProp
Set cObj = Nothing
End Sub
Regards,
Кривоус Анатолий.
Ahhh, interesting Trick. :)
You're turning a TypeLib into an instantiated object that can be used with VB6.
*chuckles and shakes head* Not exactly instantiating a BAS module, but pretty darned close. :)
I think Anatoli was posting the stuff only for better understanding, "what gets loaded" (with regards to multiple
instances of a Class) - and what is so called "shared code" (loaded only once, along with the compiled binary).
Here's a Test-question (meant without offense, only trying to establish "common ground in understanding",
because I've seen quite a few comments from you - and also Magic Ink with regards to "loaded Class-Code"):
When you define the following Class in VB6:
So, this class has the Private Member mSomePrivateLongVariable As Long -Code:Option Explicit
Private mSomePrivateLongVariable As Long
'---- the above being the only Private Member in the Declaration-Section of the Class ----
'---- now the section, which defines and implements a lot of Public Methods and Props ----
Public Sub SomeMethod1(...) As SomeType
...
End Sub
Public Property Get SomeProp1(...) As SomeType
...
End Property
Public Sub SomeMethod100(...) As SomeType
...
End Sub
and in addition also a whole lot of Methods (over 100 of them).
Now let's say, we load 1000 instances of the above Class - how much *more* memory
do you think the running Apps process will take up in this case (after all 1000 instances were loaded)?
(assuming - for ease of calculation - that any VB-Class-instance has a fixed overhead of 100Bytes per instance)
Olaf
You can create such classes in VB6 using either IDispatch or standard Class interface as well.
Generally empty class occupies ≈40 bytes per instance. It depends on many factors, for example implementing adds the enough memory. For example Olaf's class take 44 bytes:
Attachment 142477
and if i add a long member too it'll take 48 bytes:
Attachment 142479
The code is shared.
Ah, umm - well, since Anatoli was revealing that already - I'll answer my previous question now...
1000 instances of the described class (one Private Long-Member and "over 100 methods") will take up:Quote:
Now let's say, we load 1000 instances of the above Class - how much *more* memory
do you think the running Apps process will take up in this case (after all 1000 instances were loaded)?
(assuming - for ease of calculation - that any VB-Class-instance has a fixed overhead of 100Bytes per instance)
1000 * (OverheadOf100Bytes + 4BytesForTheLongMember) only.
(1000 * 104 = 104,000 Bytes, to give an exact number).
So, what I was aiming at was: ... the MethodCount (the "amount of code" in the class)
is totally irrelevant, when we talk about "loading instances into memory".
It's only the Private Members we define in the Declaration-Section of a Class, which
will take up "memory per instance" - the Methods of a given class "remain where they are".
And that is basically just another "static, hidden *.bas-module", where these Methods are compiled into -
the VB6-compiler then "expanding" the Method-signatures in a way, that they follow the COM-ABI,
which means:
When we have e.g. the following Member - and only a single Method defined in a VB6-Class:
It will be compiled/expanded into normal "module-code", which (expressed in VB-syntax) would then look like this:Code:Private mSomeLongMember As Long
Public Function SomeMethod(ByVal SomeParam As Long) As Long
...
End Function
Code:Private Type udtThis '<- only the memory described in this UDT-definition is "instance-relevant"
pVTable As Long
RefCounter As Long
'... some optional Extra-Members, each Class-instance might need
mSomeLongMember As Long
End Type
Public Function SomeMethod(This As udtThis, ByVal SomeParam As Long, ByRef FunctionResult As Long) As HResult
...
End Function
Olaf
I would like to stress that when you use a standard module the compiler/linker can remove function if one is not used.
BTW, the references counter stored at (int*)((char*)*(void**)((char*)pObj + 0x08) + 0x04):
Attachment 142487
Function-removal by the VB6-compiler will (in practice) not happen, because each of
the "COM-interface-related functions" will be "touched", when filling the VTable with the Func-Pointers.
As for the RefCounter-Position in Standard-VB-Classes (within the Instance-UDT-def)...
That's only a VB-Class-specific detail - the RefCounter-Member can be placed anywhere -
only the pVTable-Member needs to be "fixed" at the first position.
I was only trying to make the principle obvious, how a "Compiler compiles the Code of a COM-class" -
and maybe also the fact, that COM-Class-instances *could* be compiled with as low an
Overhead as 8Bytes only!
Meaning, a lightweight COM-Class which defines only Methods (has no Private Members)
would take up only half the memory of a VB-Variant-Variable per instance.
Olaf