Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Class_terminate not fired for module level objects

3 views
Skip to first unread message

Paul Blackburn

unread,
Feb 19, 2002, 12:16:05 PM2/19/02
to
Hi,

I have an apartment threaded activeX DLL which contains
three Classes.

The DLL has a sub Main() entry point within which it
creates an instance of two of the
components private classes.

I would expect the Class_Terminate events for these
private objects to fire when the DLL closes.
The Class_Terminate events are fired ONLY when running
through the IDE.

The Main module (modMain.bas) is as follows

' modMain -------------------------------------------------
--------------------
Option Explicit

Private Declare Sub OutputDebugString Lib "kernel32"
Alias "OutputDebugStringA" (ByVal lpOutputString As String)
Private Declare Sub GetLocalTime Lib "kernel32"
(lpSystemTime As SYSTEMTIME)

Public Type SYSTEMTIME
wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer
End Type

Private m_oPrivate As PPrivate
Private m_oChild As PChild


Public Sub Main()
ShowDebug "DLL Apartment Sub Main() -> " & App.ThreadID
Set m_oPrivate = New PUKDLLTHREADREF.PPrivate
Set m_oChild = New PUKDLLTHREADREF.PChild
End Sub

'+---------------------------------------------------------
--------------------
'| Public Function ShowDebug()
'+---------------------------------------------------------
--------------------
'| Purpose : Display debug information
'| Arguments : <psInfo> - Debug string to display
'+---------------------------------------------------------
--------------------
Public Sub ShowDebug(psInfo As String)
Dim lpStruct As SYSTEMTIME
GetLocalTime lpStruct
psInfo = Format$(Time, "hh:mm:ss") & ":" &
Format$(lpStruct.wMilliseconds, "000") & " " & psInfo
Call OutputDebugString(psInfo & vbCrLf)
Debug.Print psInfo
End Sub
'+---------------------------------------------------------
--------------------
'| EOF Public Function ShowDebug()
'+---------------------------------------------------------
--------------------

' EOF modMain ---------------------------------------------
------------------------

The class modules are as follows

' PChild class --------------------------------------------
--------------------
Option Explicit

Private Sub Class_Initialize()
ShowDebug "PChild Class_Initialize() -> " & App.ThreadID
End Sub

Private Sub Class_Terminate()
ShowDebug "PChild Class_Terminate() -> " & App.ThreadID
End Sub
' EOF PChild ----------------------------------------------
--------------------

' PPrivate class ------------------------------------------
--------------------
Option Explicit

Private Sub Class_Initialize()
ShowDebug "PPrivate Class_Initialize() -> " &
App.ThreadID
End Sub

Private Sub Class_Terminate()
MsgBox "PPrivate terminated"
ShowDebug "PPrivate Class_Terminate() -> " & App.ThreadID
End Sub
' EOF PPrivate class --------------------------------------
--------------------

' PPublic class -------------------------------------------
--------------------
Option Explicit

Private Sub Class_Initialize()
ShowDebug "PPublic Class_Initialize() -> " & App.ThreadID
End Sub

Private Sub Class_Terminate()
ShowDebug "PPublic Class_Terminate() -> " & App.ThreadID
End Sub
' EOF PPublic class ---------------------------------------
---------------------

The client just creates an instance of PPublic and then
destroys it.
Running through the IDE I see the message box from the
terminate event of PPrivate and
in the immediate window the likes of..

17:10:45:375 DLL Apartment Sub Main() -> 348
17:10:45:375 PPrivate Class_Initialize() -> 348
17:10:45:375 PChild Class_Initialize() -> 348
17:10:45:375 PPublic Class_Initialize() -> 348
17:10:45:375 PPublic Class_Terminate() -> 348
17:10:49:191 PPrivate Class_Terminate() -> 348
17:10:49:191 PChild Class_Terminate() -> 348

As I would expect, however running compiled and viewing
the debug output I get

17:12:38:297 DLL Apartment Sub Main() -> 376
17:12:38:297 PPrivate Class_Initialize() -> 376
17:12:38:297 PChild Class_Initialize() -> 376
17:12:38:297 PPublic Class_Initialize() -> 376
17:12:38:297 PPublic Class_Terminate() -> 376

The two private class instances are NOT terminated?
Even stranger is that if I comment out one of the private
class creations I get the main
entry point called each time so the private instance is
created and destroyed with PPublic.

Any help/ideas greatly appreciated.

Ivan Demkovitch

unread,
Feb 19, 2002, 1:44:45 PM2/19/02
to
Try set this objects to Nothing manually

"Paul Blackburn" <pblack...@hotmail.com> wrote in message
news:634b01c1b969$1d1785b0$9ae62ecf@tkmsftngxa02...

Joe "Nuke Me Xemu" Foster

unread,
Feb 19, 2002, 6:50:50 PM2/19/02
to
"Paul Blackburn" <pblack...@hotmail.com> wrote in message <news:634b01c1b969$1d1785b0$9ae62ecf@tkmsftngxa02>...

> The DLL has a sub Main() entry point within which it


> creates an instance of two of the
> components private classes.
>
> I would expect the Class_Terminate events for these
> private objects to fire when the DLL closes.
> The Class_Terminate events are fired ONLY when running
> through the IDE.

You're right -- only external references to public objects "count".
Perhaps your public classes can manage the lifetimes of your internal
objects. Air-code:

----module globals----
global pi as double

global i1 as internal_1
global i2 as internal_2

private refcount as long

' reserve sub main for initializing things that won't need shutting down
sub main()
pi = 4 * atn(1)
end sub

public sub acquire()
debug.assert refcount >= 0
debug.assert (i1 is nothing) = (refcount = 0)
debug.assert (i2 is nothing) = (refcount = 0)

if refcount = 0 then
set i1 = new internal_1
set i2 = new internal_2
refcount = 1

else
refcount = refcount + 1
end if
end sub

public sub release()
debug.assert refcount > 0
debug.assert (i1 is nothing) = false
debug.assert (i2 is nothing) = false

if refcount = 1 then
set i1 = nothing
set i2 = nothing
refcount = 0

else
refcount = refcount - 1
end if
end sub
----end module----

----class multiuse_1----
private sub class_initialize()
globals.acquire
end sub

private sub class_terminate()
globals.release
end sub
----end class----

----class public_not_creatable_1----
private sub class_initialize()
globals.acquire
end sub

private sub class_terminate()
globals.release
end sub
----end class----

--
Joe Foster <mailto:jlfoster%40znet.com> Space Cooties! <http://www.xenu.net/>
WARNING: I cannot be held responsible for the above They're coming to
because my cats have apparently learned to type. take me away, ha ha!


Paul Blackburn

unread,
Feb 20, 2002, 9:12:05 AM2/20/02
to

>-----Original Message-----
>"Paul Blackburn" <pblack...@hotmail.com> wrote in
message <news:634b01c1b969$1d1785b0
$9ae62ecf@tkmsftngxa02>...
>
>> The DLL has a sub Main() entry point within which it
>> creates an instance of two of the
>> components private classes.
>>
>> I would expect the Class_Terminate events for these
>> private objects to fire when the DLL closes.
>> The Class_Terminate events are fired ONLY when running
>> through the IDE.
>
>You're right -- only external references to public
objects "count".
>Perhaps your public classes can manage the lifetimes of
your internal
>objects. Air-code:
<Snip>
In my real world problem I wanted an object to live the
duration of the apartment/thread.
In the scenario you suggested the thread specific object
would be initialised and terminated
many times over.
With VB it is not possible to determine when to explicitly
destroy global objects (ie we have no
way of knowing when a thread detaches as you do in C++)
and I HAD made the assumption that when
the DLL was unloaded the global objects would be
destroyed, reference count reduced to zero,
and the terminate event called.
The bizarrre thing I find is that the behaviour between
one and two global objects is so different
(this seems to have been corrupted and left out from my
original post)
A single global object results in the Sub Main() called
every time a new public object is created
and destroyed, which sort of makes sense, ie DLL loaded
for new public object, pubic object terminated,
dll unloaded.
I have found a workaround which involves using C++ to
manage the object lifetime but this means I'm
going have to review all VB components when it comes to
module level objects.

Many thanks for your feedback.

Cheers
Paul

Steffen Ramlow

unread,
Feb 20, 2002, 12:17:03 PM2/20/02
to
"Paul Blackburn" <pblack...@hotmail.com> wrote in message
news:55b401c1ba18$93455320$39ef2ecf@TKMSFTNGXA08...

> I have found a workaround which involves using C++ to
> manage the object lifetime but this means I'm
> going have to review all VB components when it comes to
> module level objects.

how?

--
Steffen Ramlow: s.ra...@gmx.de
Enterprise Development: http://www.dev-purgatory.org/

Joe "Nuke Me Xemu" Foster

unread,
Feb 20, 2002, 8:50:22 PM2/20/02
to
"Paul Blackburn" <pblack...@hotmail.com> wrote in message <news:55b401c1ba18$93455320$39ef2ecf@TKMSFTNGXA08>...

> In my real world problem I wanted an object to live the
> duration of the apartment/thread.
> In the scenario you suggested the thread specific object
> would be initialised and terminated
> many times over.

Perhaps you can use a factory class to help control these resources.
A GlobalMultiUse factory class instance will stick around as long as
the apartment exists (or until the instance is released explicitly),
and I haven't been able to get its Class_Terminate event to not fire,
at least not without using Task Manager to kill the task outright. As
an added bonus, a GMU class can simulate parameterized constructors,
right now, without making you migrate to VB.NOT:

----class factory (globalmultiuse)----
public function a(x, y, z) as a
set a = new a
a.setup x, y, z
end function

public function b(p, q) as b
set b = new b
b.setup p, q
end function

private sub class_initialize()
globals.acquire
end sub

private sub class_terminate()
globals.release
end sub
----end class----

----class a (publicnotcreatable)----
friend sub setup(x, y, z)
' etc etc
end sub

private sub class_initialize()
globals.acquire
end sub

private sub class_terminate()
globals.release
end sub
----end class----

----class b (publicnotcreatable)----
friend sub setup(p, q)
' etc etc
end sub

private sub class_initialize()
globals.acquire
end sub

private sub class_terminate()
globals.release
end sub
----end class----

--
Joe Foster <mailto:jlfoster%40znet.com> Got Thetans? <http://www.xenu.net/>

Paul Blackburn

unread,
Feb 21, 2002, 10:33:58 AM2/21/02
to
>> In my real world problem I wanted an object to live the
>> duration of the apartment/thread.

>Perhaps you can use a factory class to help control these

>resources. A GlobalMultiUse factory class instance will
> stick around as long as the apartment exists (or until >
the instance is released explicitly),

<Snip>
A new instance of a GMU object is created (and existing
instance terminated) when referenced in another ActiveX
component. ie call a method from say your client exe,
a support dll and a client hosted OCX and you will see the
object created and destroyed three times. The thread data
I wish to keep would be lost.

Many thanks for the suggestions


Paul Blackburn

unread,
Feb 21, 2002, 10:51:44 AM2/21/02
to
> I have found a workaround which involves using C++ to
> manage the object lifetime but this means I'm
> going have to review all VB components when it comes to
> module level objects.

I've got an ATL class with a static CMapWordToPtr array
containing the objects I wish to store mapped by the
thread id. These do not have AddRef() called.

I have a public property (GET) which will either
a) if there is an object for the current thread id then
return it or
b) create a new instance of the object I'm storing, store
it in the mapped array and return it

The FinalRelease() (Class_Terminate equivalent) is called
just fine and my objects are thread specific and last for
the the threads lifetime, so I can now have an apartment
threaded DLL with a thread wide data store.

e-mail me if you'd like the C++ code.

Cheers
Paul

Joe "Nuke Me Xemu" Foster

unread,
Feb 21, 2002, 2:49:24 PM2/21/02
to
"Paul Blackburn" <pblack...@hotmail.com> wrote in message <news:613d01c1baed$2dbcadc0$a4e62ecf@tkmsftngxa06>...

Those bastards... (I seem to be saying that about Microsoft
quite a bit lately!)

Hey, Class_Terminate seems to be more reliable in ActiveX DLLs
compiled to P-Code instead of to Native Code, but as before, I
haven't really tested this very thoroughly...

--
Joe Foster <mailto:jlfoster%40znet.com> Sacrament R2-45 <http://www.xenu.net/>

0 new messages