views:

424

answers:

8

I have rephrased this question.

When .net objects are exposed to COM Clients through COM iterop, a CCW (COM Callable Wrapper) is created, this sits between the COM Client and the Managed .net object.

In the COM world, objects keep a count of the number of references that other objects have to it. Objects are deleted/freed/collected when that reference count goes to Zero. This means that COM Object termination is deterministic (we use Using/IDispose in .net for deterministic termination, object finalizers are non deterministic).

Each CCW is a COM object, and it is reference counted like any other COM object. When the CCW dies (reference count goes to Zero) the GC won't be able to find the CLR object the CCW wrapped, and the CLR object is eligible for collection. Happy days, all is well with the world.

What I would like to do is catch when the CCW dies (i.e. when its reference count goes to zero), and somehow signal this to the CLR object (e.g. By calling a Dispose method on the managed object).

So, is it possible to know when the reference count of a COM Callable Wrapper for a CLR class goes to Zero?
and/or
Is it possible to provide my implementation of AddRef & ReleaseRef for CCWs in .net?

If not the alternative is to implement these DLLs in ATL (I don't need any help with ATL, thanks). It wouldn't be rocket science but I'm reluctant to do it as I'm the only developer in-house with any real world C++, or any ATL.

Background
I'm re-writing some old VB6 ActiveX DLLs in .net (C# to be exact, but this is more a .net / COM interop problem rather than a C# problem). Some of the old VB6 objects depend on reference counting to carry out actions when the object terminates (see explaination of reference counting above). These DLL's don't contain important business logic, they are utilities and helper functions that we provide to clients that integrate with us using VBScript.

What I'm not trying to do

  • Reference count .net objects instead of the using the Garbage Collector. I'm quite happy with the GC, my problem isn't with the GC.
  • Use object finalizers. Finalizers are non deterministic, in this instance I need deterministic termination (like the Using/IDispose idiom in .net)
  • Implement IUnknown in unmanaged C++
    If I've to go the C++ route I'll use ATL, thanks.
  • Solve this using Vb6, or re-using the VB6 objects. The entire point of this exercise is to remove our build dependence on Vb6.

Thanks
BW

The Accepted Answer
Folks a thousand thanks to Steve Steiner, who came up with the only (possibly workable) .net based answer, and Earwicker, who came up with a very simple ATL solution.

However the accepted answer goes to Bigtoe, who suggests wrapping the .net objects in VbScript objects (which I hadn't considered to be honest), effectively providing a simple VbScript solution to a VbScript problem.

Thanks to all.

A: 

Why don't shift paradigm. What about to create your own aggregate around exposed and extend with notification methods. It even can be done in .Net not only by ATL.

EDITED: Here is some link that may be describe some another way(http://msdn.microsoft.com/en-us/library/aa719740(VS.71).aspx). But following steps explains my idea above.

Create create new .Net class that implements your legacy interface(ILegacy), and new interface (ISendNotify) with single method:

interface ISendNotify 
{
     void SetOnDestroy(IMyListener );
}

class MyWrapper : ILegacy, ISendNotify, IDisposable{ ...

Inside the MyClass create instance of your real legacy object, and delegate all calls from MyClass to this instance. This is an aggregation. So lifetime of aggregate now depends on MyClass. Since MyClass is a IDisposable now you can intercept when instance is deleted, so you can send notification by IMyListener

EDIT2: Taken there (http://vb.mvps.org/hardcore/html/countingreferences.htm) simplest impl of IUnknown with sending event

Class MyRewritten
    ...
    Implements IUnknown
    Implements ILegacy
    ...
    Sub IUnknown_AddRef()
        c = c + 1
    End Sub

    Sub IUnknown_Release()
        c = c - 1
        If c = 0 Then
            RaiseEvent Me.Class_Terminate
            Erase Me
        End If
    End Sub
Dewfy
With the what now? Coul you expand on these concepts, possibly with links?
Binary Worrier
@Binary Worrier just add some explanations
Dewfy
Thanks, but I can't see how that help with referencing counting CLR objects created in VBScript through COM Interop. What am I missing?
Binary Worrier
@Binary Worrier: Yes, it doesn't give you reference counter to hand, but you know when object is ready to be shutdowned by overriding IDisposable.Dispose
Dewfy
Thanks, but it does not answer my question
Binary Worrier
Why? You wrote " COM wrapper for a CLR class goes to Zero?" - it is disposed, when instance goes zero, that is why you can use it.
Dewfy
@dewfy: I need to re-write old vb6 classes. Not re-use them. The new classes must behave the same as the old, with deterministic finalization when ref count goes to zero. This must work when the objects are accessed through COM interop, by VbScript (not vb.net). How does your aggregation know that VbScript has no live references to the CLR object? VbScript has reference count, not gc, and I cannot put Dispose method on COM interface, it must remain as is in vb6. So please explain, with vb script sample, how your solution answers my question. Thanks.
Binary Worrier
@Binary Worrier: Idea to create new stand alone adapter, written on .Net. This adapter takes old vb6 class, makes wrap around and add method to notify disposing. (By the way this adapter can also be written on vb6 - since COM's Release returns actual reference counter - it is not recommended, but why not?) So vb-script only needs change CLSID to create another instance and add listener to ISendNotify.SetOnDestroy
Dewfy
As I've said several times now, I am rewriting the vb6 objects, I cannot reuse them. You answer does not solve my problem. Thanks
Binary Worrier
If you rewriting the vb6 objects, then you can override Release/AddRef methods WITHOUT any troubles and tricks. You will control time of deletion itself.
Dewfy
Ok, how? Can you modify your answer, include samples or links that show how to do this? Thanks.
Binary Worrier
Just added edited2
Dewfy
Dewfy, that is pre .Net Visual Basic. I need to RE-WRITE old, pre .Net Visual Basic in either .Net or C++ (but I'd rather not use C++). Please stop just trawling throug Google trying to find something that matches what I'm looking for, I've done that my self. So please, do you know how to answer my question? If so please do so, if not, tell me. If you tell me you either a) do not understand the question, or b) do not know how to answer the question. Say "a" or "b" and I will will remove the downvote, then you should delete your answer, as it does not answer my question. Thank you.
Binary Worrier
@Binary Worrier: I deal with COM over 15 years - the first thing is ever studied is "how to override AddRef/Release". If you even don't know how to do it - just change title "COM for dummies". I've provided for you at least 2 way how to work around you problem. Code in EDIT2 doesn't specific for .Net and is compiled on vb6. That's all - I wouldn't answer you anymore.
Dewfy
Sorry, do you understand what I am trying to do? I don't think you do. You say "Code in EDIT2 doesn't specific for .Net and is compiled on vb6" I've said several times I am moving away from VB6. Code that is compiled on VB6 is no good to me. I cannot use the VB6 compiler. I cannot use VB6. Я не могу использовать VB6. Thanks.
Binary Worrier
A: 

I guess the reason for this not being possible is that a refcount of 0 does not mean that the object is not in use, because you might have a call graph like

VB_Object
   |
   V
   |
Managed1 -<- Managed2

in which case the object Managed1 is still in use even if the VB object drops its reference to it and its refcount therefore is 0.

If you really need to do what you say, I guess you could create wrapper classes in unmanaged C++, which invokes the Dispose method when the refcount drops to 0. These classes could probably be codegen'd from metadata, but I have no experience whatsoever in how to do implement this kind of thing.

erikkallen
Thanks, I'll look into that. I've been thinking if it comes to C++ I'd be happier to go with ATL, these objects are mostly self contained utilities, and it would not be rocket science to port them to C++, the trouble is I'm the only developer here with any C++, and management and the team are understandably resistant to introducing C++. However if we have to we will. Thanks.
Binary Worrier
Actually if you follow the link for "COM Callable Wrapper", you'll see that COM clients reference count a CCW object, which keeps the CLR object alive. That reference count can be trusted, if only there was some way to hook into that CCW object. But thanks again.
Binary Worrier
Yes, you can, in theory, find out when the CCW refcount goes to zero, if not through any other means so at least by using some unsafe code and pointers. However, unless you have other special knowledge about the usage of the object (that there is no managed reference to it), you can't know that it is safe to dispose of it. My guess is that MS chose not to make this thing easy in order to avoid a ton of support questions like "I keep getting ObjectDisposedExceptions but I haven't yet called Dispose".
erikkallen
OK, in this instance, I can honestly say there will be NO managed references to this object. How theoretical is the theory behind finding when CCW refcount goes to Zero? Thanks.
Binary Worrier
Sorry, I have no idea whatsoever. I have never worked with this, and everytime I have to do COM interop, I feel a little uneasy.
erikkallen
No worries (I am the Binary Worrier after all, I'll worry for you). Thanks for your input mate :)
Binary Worrier
+2  A: 

OK Folks, here's another attempt at it. You can actually use "Windows Script Components" to wrap your .NET COM objects and get finalization that way. Here's a full sample using a simple .NET Calculator which can Add values. I'm sure you'll get the concept from there, this totally avoids the VB-Runtime, ATL issues and uses the Windows Scripting Host which is available on every major WIN32/WIN64 platform.

I created a simple COM .NET Class called Calculator in a namespaces called DemoLib. Note this implements IDisposable where for demo purpose I put something up on the screen to show it has terminated. I'm sticking totally to vb here in .NET and script to keep things simple, but the .NET portion can be in C# etc. When you save this file you'll need to register it with regsvr32, it will need to be saved as something like CalculatorLib.wsc.

<ComClass(Calculator.ClassId, Calculator.InterfaceId, Calculator.EventsId)> _
Public Class Calculator
    Implements IDisposable
#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class 
    ' and its COM interfaces. If you change them, existing 
    ' clients will no longer be able to access the class.
    Public Const ClassId As String = "68b420b3-3aa2-404a-a2d5-fa7497ad0ebc"
    Public Const InterfaceId As String = "0da9ab1a-176f-49c4-9334-286a3ad54353"
    Public Const EventsId As String = "ce93112f-d45e-41ba-86a0-c7d5a915a2c9"
#End Region
    ' A creatable COM class must have a Public Sub New() 
    ' with no parameters, otherwise, the class will not be 
    ' registered in the COM registry and cannot be created 
    ' via CreateObject.
    Public Sub New()
        MyBase.New()
    End Sub
    Public Function Add(ByVal x As Double, ByVal y As Double) As Double
        Return x + y
    End Function
    Private disposedValue As Boolean = False        ' To detect redundant calls
    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                MsgBox("Disposed called on .NET COM Calculator.")
            End If
        End If
        Me.disposedValue = True
    End Sub
#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region
End Class

Next I create A Windows Script Component called Calculator.Lib which has a single method which returns back a VB-Script COM class which exposes the .NET Math Library. Here I pop up something on the screen during Construction and Destruction, note in the Destruction we call the Dispose method in the .NET library to free up resources there. Note the use of the Lib() function to return the .NET Com Calculator to the caller.

<?xml version="1.0"?>
<component>
<?component error="true" debug="true"?>
<registration
    description="Demo Math Library Script"
    progid="Calculator.Lib"
    version="1.00"
    classid="{0df54960-4639-496a-a5dd-a9abf1154772}"
>
</registration>
<public>
  <method name="GetMathLibrary">
  </method>
</public>
<script language="VBScript">
<![CDATA[
Option Explicit
'-----------------------------------------------------------------------------------------------------
' public Function to return back a logger.
'-----------------------------------------------------------------------------------------------------
function GetMathLibrary()
    Set GetMathLibrary = New MathLibrary
end function
Class MathLibrary
    private dotNetMatFunctionLib
  private sub class_initialize()
    MsgBox "Created."
    Set dotNetMatFunctionLib = CreateObject("DemoLib.Calculator")
  end sub
  private sub class_terminate()
        dotNetMatFunctionLib.Dispose()
        Set dotNetMatFunctionLib = nothing
    MsgBox "Terminated."
  end sub
  public function Lib()
    Set Lib = dotNetMatFunctionLib
  End function
end class
]]>
</script>
</component>

Finally to tie it all together here's s sample VB script where you get dialogues showing creation, the calculation, dispose being called in the .NET library and finally Terminate in the COM component exposing the .NET Component.

dim comWrapper
dim vbsCalculator
set comWrapper = CreateObject("Calculator.Lib")
set vbsCalculator = comWrapper.GetMathLibrary()
msgbox "10 + 10 = " & vbsCalculator.lib.Add(10, 10)
msgbox "20 + 20 = " & vbsCalculator.lib.Add(20, 20)
set vbsCalculator = nothing
MsgBox("Dispose & Terminate should have been called before here.")
Bigtoe
Thanks, I'm not at all surprised to hear it can't be done. Still, we've a week before the bounty expires for someone - who doesn't know it's impossible - to find a way to do it :)
Binary Worrier
Please see my answer, as I believe everyone is missing a very intrinsic behavior of the GC that was designed to handle scenarios such as this.
jrista
So maybe I managed to do it after all? bit of a hack but seems to work and would need testing, it also bypasses the issues and concerns raised in the question.
Bigtoe
Dude this looks like pure gold. Use VbScript to fix a VbScript problem, excellent. If you needed this for complex business objects you could refelect over the .net assemblies and generate the wrapping wsc. Will have a look Tuesday. Thanks Pal :)
Binary Worrier
+1  A: 

.Net framework works differently, see:
The .NET Framework provides memory management techniques that differ from the way memory management worked in a COM-based world. The memory management in COM was through reference counting. .NET provides an automatic memory management technique that involves reference tracing. In this article, we'll take a look at the garbage collection technique used by the Common Language Runtime CLR.

nothing to be done

[EDITED] more one round...

Take a look at this alternative Importing a Type Library as an Assembly
As you yourself said using CCW you can access reference-counte in traditional COM fashion.

[EDITED] Persistence is a virtue
You know WinAPIOverride32? With it you can capture and study how it works. Another tool that can help is Deviare COM Spy Console.
This will not be easy.
Good luck.

lsalamon
Yes, however I don't want to reference count .net objects per se, I'm quite happy with the garbage collector to be honest. Each .net object exposed through COM Interop has a CCW, this is still reference counted. What I'd like to do is tap into the CCW's reference count.
Binary Worrier
Yes, but as I say in my question, I need to hook into (or replace) the reference counting that happens in the CCW. Any ideas how to do this? Thanks.
Binary Worrier
+3  A: 

I haven't verified this, but here is what I would try:

First, here is a CBrumme Blog article about the clr's default implementation of IMarshal. If your utilities are used across COM apartments you won't get proper com behavior from a direct port of VB6 to the CLR. Com objects implemented by the CLR act as if they aggregated the free threaded marshaller rather than apartment threaded model that VB6 exposed.

You can implement IMarshal (on the clr class you are exposing as a com object). My understanding is that will allow you control over creating the COM proxy (not the interop proxy). I think this will allow you to trap the Release calls in the object you returned from UnmarshalInterface and signal back to your original object. I'd wrap the standard marshaller (e.g. pinvoke CoGetStandardMarshaler) and forward all calls to it. I believe that object will have a lifetime tied to the lifetime of the the CCW.

again ... this is what I'd try if I had to solve it in C#.

On the other hand, would this kind of solution really be easier than implementing in ATL? Just because the magic part is written in C# doesn't make the solution simple. If what I propose above does solve the problem, you'll need to write a really big comment explaining what was going on.

Steve Steiner
Excellent, this is the first promising response. I'll have a look at this first thing Monday. Thanks mate :)
Binary Worrier
Steve, I had a poke at this, and it does look good. I found articles (like the one you link) saying they'd implemented IMarshal on .net objects, but I couldn't find anything to get me started on that, even what assembly / TypeLib to reference to get IMarshal. Any ideas on how to get started with this? Thanks.
Binary Worrier
Use pinvoke.net to find the signatures. You shouldn't need a typelib you can define everything in C#.http://pinvoke.net/default.aspx/ole32/CoGetStandardMarshal.htmlI don't see a contribution for IMarshal but you can probably glean what the definition should be by looking at other interfaces (under Interfaces) that pass similar args. I'd suggest using PreserveSig.
Steve Steiner
A: 

To my knowledge, the GC already provides support for what you are trying to do. It is called Finalization. In a purely managed world, best practice is to avoid Finalization, as it has some side effects that can negatively impact the performance and operation of the GC. The IDisposable interface provides a clean, managed way of bypassing object finalization and providing cleanup of both managed and unmanaged resources from within managed code.

In your case, you need to initiate cleanup of a managed resource once all unmanaged references have been released. Finalization should excel at solving your problem here. The GC will finalize an object, always, if a finalizer is present, regardless of how the last references to a finalizable object were released. If you implement a finalizer on your .NET type (just implement a destructor), then the GC will place it in the finalization queue. Once the GC collection cycle is complete, it will process the finalization queue. Any cleanup work you perform in your destructor will occur once the finalization queue is processed.

It should be noted that if your finalizable .NET type contains references to other .NET objects which in turn require finalization, you could invoke a lengthy GC collection, or some of the objects may survive for longer than they would without finalization (which would mean they survive a collection and reach the next generation, which is collected less frequently.) However, if the cleanup work of your .NET objects that use CCW's is not time sensitive in any fashion, and memory usage is not a huge issue, some extra lifetime shouldn't matter. It should be noted that finalizable objects should be created with care, and minimizing or eliminating any class instance level references to other objects can improve your overall memory management via the GC.

You can read more about finalization in this article: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx. While it is a rather old article from back when .NET 1.0 was first released, the fundamental architecture of the GC is unchanged as of yet (the first significant changes to the GC will be arriving with .NET 4.0, however they are related more to concurrent GC execution without freezing the application threads than changes to its fundamental operation.)

jrista
Thanks, but I need deterministic finalization (Using/IDispose), when COM releases references to the CCW. the standard managed finaliser isn't deterministic. Thanks anyway.
Binary Worrier
Could you explain in more detail exactly why? Even if finalization is unsatisfactory, the System.Runtime.InteropServices namespace is very rich in unmanaged to managed marshaling tools. There may still be something that will solve your problem.
jrista
Some of the existing vb6 objects open file handles on creation, and close them on when the object terminates. This can be done in .net with using/IDispose, this is not the issue. What I need is to rewrite the vb6 objects and have them work exactly as is. The client code using these objects (written in VbScript) cannot change. The question is, can this be done in C#. Thanks
Binary Worrier
Well, the only other suggestion I have is to look into hosting the CLR yourself. You would have to write C++ code to initiate the CLR host, however there are quite a few events and notifications you can hook into for just about everything, including the GC. I am not sure if there is actually any way to solve your problem this way, but it might be worth a shot.
jrista
+1  A: 

As far as I'm aware, the best coverage of this subject is in the book The .NET and COM Interoperability Handbook By Alan Gordon, and that link should go to the relevant page in Google Books. (Unfortunately I don't have it, I went for the Troelsen book instead.)

The guidance there implies that there isn't a well-defined way of hooking into the Release/reference counting in the CCW. Instead the suggestion is that you make your C# class disposable, and encourage your COM clients (in your case the VBScript authors) to call Dispose when they want deterministic finalisation to occur.

But happily there is a loophole for you because your clients are late-binding COM clients, because VBScript uses IDispatch to make all calls to objects.

Suppose your C# classes were exposed via COM. Get that working first.

Now in ATL/C++ create a wrapper class, using the ATL Simple Object wizard, and in the options page choose Interface: Custom instead of Dual. This stops the wizard putting in its own IDispatch support.

In the class's constructor, use CoCreateInstance to magic up an instance of your C# class. Query it for IDispatch and hold onto that pointer in a member.

Add IDispatch to the wrapper class's inheritance list, and forward all four methods of IDispatch straight through to the pointer you stashed away in the constructor.

In the FinalRelease of the wrapper, use the late binding technique (Invoke) to call the Dispose method of the C# object, as described in the Alan Gordon book (on the pages I linked to above).

So now your VBScript clients are talking via the CCW to the C# class, but you get to intercept the final release and forward it to the Dispose method.

Make your ATL library expose a separate wrapper for each "real" C# class. You'll probably want to use inheritance or templates to get good code reuse here. Each C# class you support should only require a couple of lines in the ATL wrapping code.

Daniel Earwicker
Thanks, nice ideas, I'd already considered generating wrapper objects in ATL. In this instance it would be simpler to do the full re-write in ATL if I've to venture out of the .net sandbox at all. Don't tell anyone else, but I gave up on .net for this and went with ATL, simpler and cleaner in the long run. I left the question open and raised the bounty because I'm quite surprised it can't be done. I was half sure it couldn't be done, and half sure there'd be a straight forward way to do it. I'm quite surprised there isn't TBH. Thanks anyway mate. P.s Remember, Mums the word . . .
Binary Worrier
Depends what you're more comfortable with. I worked in C++/ATL for many years, switch to C# a couple of years ago, and no power on earth could persuade me to go back! In this case I suspect that the short-run pain of setting up a reusable wrapping system would be paid back by productivity gains of developing your objects in C#.
Daniel Earwicker
I hear what you're saying, however these are once off utility classes for VbScript clients, the VB6 code hasn't changed in 8+ years. They are now an issue as we're moving to a 64 bit build enviroment, Visual Studio 6 has reached end of life and isn't supported, we're looking to make our server products fully 64bit compliant, and we've a legal obligation to be able to build all non 3rd party code. QED Port the code. There may be workarounds to getting VS6 to run on 64bit machines, but TBH I'm glad to see the back of it, and the re-write is more than justified for other reasons. Thanks.
Binary Worrier
A: 

From the .NET, request an IUnknown on the object. Call AddRef(), then Release(). Then take the return value of AddRef() and run with it.

Seva Alekseyev
I'm sorry, I have no idea what you mean.
Binary Worrier