Bin*_*ier 17 .net c# reference-counting com-interop
我已经改写了这个问题.
当.net对象通过COM iterop公开给COM客户端时,会创建一个CCW(COM Callable Wrapper),它位于COM客户端和Managed .net对象之间.
在COM世界中,对象保留其他对象对其的引用数量的计数.当引用计数变为零时,将删除/释放/收集对象.这意味着COM对象终止是确定性的(我们在.net中使用Using/IDispose用于确定性终止,对象终结器是非确定性的).
每个CCW都是一个COM对象,它的引用计数与任何其他COM对象一样.当CCW死亡(引用计数变为零)时,GC将无法找到CCW包装的CLR对象,并且CLR对象有资格进行收集.快乐的日子,一切都与世隔绝.
我想要做的是在CCW死时(即当它的引用计数变为零时)捕获,并以某种方式将此信号通知给CLR对象(例如,通过在被管理对象上调用Dispose方法).
那么,是否可以知道CLR类的COM可调用包装器的引用计数何时变为零?
和/或
是否可以在.net中为CCW提供AddRef和ReleaseRef的实现?
如果不是替代方法是在ATL中实现这些DLL(我不需要任何ATL帮助,谢谢).它不是火箭科学,但我不愿意这样做,因为我是内部唯一的开发人员,任何现实世界的C++或任何ATL.
背景
我在.net中重写了一些旧的VB6 ActiveX DLL(确切地说是C#,但这更像是.net/COM互操作问题,而不是C#问题).一些旧的VB6对象依赖于引用计数来在对象终止时执行操作(参见上面引用计数的解释).这些DLL不包含重要的业务逻辑,它们是我们为使用VBScript与我们集成的客户提供的实用程序和帮助程序函数.
我不想做什么
谢谢
BW
接受的答案
分毫不差一千感谢史蒂夫·施泰纳,谁,唯一的(可能是可行的)想出了基于.NET的答案,埃里克,谁用一个非常简单的解决方案ATL上来.
然而,接受的答案是Bigtoe,他建议将.net对象包装在VbScript对象中(我认为不诚实),有效地为VbScript问题提供了一个简单的VbScript解决方案.
谢谢大家.
OK伙计们,这是另一次尝试.实际上,您可以使用"Windows脚本组件"来包装.NET COM对象并以此方式完成最终化.这是一个使用简单的.NET计算器的完整示例,它可以添加值.我相信你会从那里得到这个概念,这完全避免了VB-Runtime,ATL问题,并使用了每个主要WIN32/WIN64平台上都可用的Windows Scripting Host.
我在名为DemoLib的名称空间中创建了一个名为Calculator的简单COM .NET类.注意这实现了IDisposable,为了演示的目的,我在屏幕上放了一些东西以显示它已经终止.我在.NET和脚本中完全坚持vb以保持简单,但.NET部分可以在C#等.当你保存这个文件时你需要用regsvr32注册它,它需要保存就像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
Run Code Online (Sandbox Code Playgroud)
接下来,我创建一个名为Calculator.Lib的Windows脚本组件,它有一个返回VB脚本COM类的方法,该类公开.NET数学库.在构建和销毁期间,我在屏幕上弹出一些内容,请注意在Destruction中我们在.NET库中调用Dispose方法来释放资源.请注意使用Lib()函数将.NET Com Calculator返回给调用者.
<?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>
Run Code Online (Sandbox Code Playgroud)
最后,将所有这些组合在一起的示例VB脚本中,您将获得对话框,显示创建,计算,在.NET库中调用dispose,最后在COM组件中终止公开.NET组件.
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.")
Run Code Online (Sandbox Code Playgroud)
我意识到这是一个有点老问题,但我确实得到了一些时间回来的实际请求.
它的作用是使用自定义实现替换创建对象的VTBL中的Release,该实现在释放所有引用时调用Dispose.请注意,无法保证始终有效.主要假设是标准CCW的所有接口上的所有Release方法都是相同的方法.
使用风险由您自己承担.:)
/// <summary>
/// I base class to provide a mechanism where <see cref="IDisposable.Dispose"/>
/// will be called when the last reference count is released.
///
/// </summary>
public abstract class DisposableComObject: IDisposable
{
#region Release Handler, ugly, do not look
//You were warned.
//This code is to enable us to call IDisposable.Dispose when the last ref count is released.
//It relies on one things being true:
// 1. That all COM Callable Wrappers use the same implementation of IUnknown.
//What Release() looks like with an explit "this".
private delegate int ReleaseDelegate(IntPtr unk);
//GetFunctionPointerForDelegate does NOT prevent GC ofthe Delegate object, so we'll keep a reference to it so it's not GC'd.
//That would be "bad".
private static ReleaseDelegate myRelease = new ReleaseDelegate(Release);
//This is the actual address of the Release function, so it can be called by unmanaged code.
private static IntPtr myReleaseAddress = Marshal.GetFunctionPointerForDelegate(myRelease);
//Get a Delegate that references IUnknown.Release in the CCW.
//This is where we assume that all CCWs use the same IUnknown (or at least the same Release), since
//we're getting the address of the Release method for a basic object.
private static ReleaseDelegate unkRelease = GetUnkRelease();
private static ReleaseDelegate GetUnkRelease()
{
object test = new object();
IntPtr unk = Marshal.GetIUnknownForObject(test);
try
{
IntPtr vtbl = Marshal.ReadIntPtr(unk);
IntPtr releaseAddress = Marshal.ReadIntPtr(vtbl, 2 * IntPtr.Size);
return (ReleaseDelegate)Marshal.GetDelegateForFunctionPointer(releaseAddress, typeof(ReleaseDelegate));
}
finally
{
Marshal.Release(unk);
}
}
//Given an interface pointer, this will replace the address of Release in the vtable
//with our own. Yes, I know.
private static void HookReleaseForPtr(IntPtr ptr)
{
IntPtr vtbl = Marshal.ReadIntPtr(ptr);
IntPtr releaseAddress = Marshal.ReadIntPtr(vtbl, 2 * IntPtr.Size);
Marshal.WriteIntPtr(vtbl, 2 * IntPtr.Size, myReleaseAddress);
}
//Go and replace the address of CCW Release with the address of our Release
//in all the COM visible vtables.
private static void AddDisposeHandler(object o)
{
//Only bother if it is actually useful to hook Release to call Dispose
if (Marshal.IsTypeVisibleFromCom(o.GetType()) && o is IDisposable)
{
//IUnknown has its very own vtable.
IntPtr comInterface = Marshal.GetIUnknownForObject(o);
try
{
HookReleaseForPtr(comInterface);
}
finally
{
Marshal.Release(comInterface);
}
//Walk the COM-Visible interfaces implemented
//Note that while these have their own vtables, the function address of Release
//is the same. At least in all observed cases it's the same, a check could be added here to
//make sure the function pointer we're replacing is the one we read from GetIUnknownForObject(object)
//during initialization
foreach (Type intf in o.GetType().GetInterfaces())
{
if (Marshal.IsTypeVisibleFromCom(intf))
{
comInterface = Marshal.GetComInterfaceForObject(o, intf);
try
{
HookReleaseForPtr(comInterface);
}
finally
{
Marshal.Release(comInterface);
}
}
}
}
}
//Our own release. We will call the CCW Release, and then if our refCount hits 0 we will call Dispose.
//Note that is really a method int IUnknown.Release. Our first parameter is our this pointer.
private static int Release(IntPtr unk)
{
int refCount = unkRelease(unk);
if (refCount == 0)
{
//This is us, so we know the interface is implemented
((IDisposable)Marshal.GetObjectForIUnknown(unk)).Dispose();
}
return refCount;
}
#endregion
/// <summary>
/// Creates a new <see cref="DisposableComObject"/>
/// </summary>
protected DisposableComObject()
{
AddDisposeHandler(this);
}
/// <summary>
/// Calls <see cref="Dispose"/> with false.
/// </summary>
~DisposableComObject()
{
Dispose(false);
}
/// <summary>
/// Override to dispose the object, called when ref count hits or during GC.
/// </summary>
/// <param name="disposing"><b>true</b> if called because of a 0 refcount</param>
protected virtual void Dispose(bool disposing)
{
}
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5130 次 |
| 最近记录: |