C# 如何正确释放暴露于COM的.NET对象?
因此,我试图向COM公开一个.NET对象,以便在我们的一个旧版VB6应用程序中使用。我相信我已经正确设置了.NET类,因为我现在可以在VB6中使用它,但是我不认为当我的VB6应用程序关闭/崩溃时我会释放对COM对象的所有引用。这是一个非常受事件驱动的.NET对象,我们已经看到了一些奇怪的问题,在这些问题中,.NET对象的前一个实例化接收到一个用于该对象的新实例化的事件 为了测试这一点,我在.NET对象中添加了跟踪编写器。这些跟踪打印出本地秒表的C# 如何正确释放暴露于COM的.NET对象?,c#,.net,com,vb6,com-interop,C#,.net,Com,Vb6,Com Interop,因此,我试图向COM公开一个.NET对象,以便在我们的一个旧版VB6应用程序中使用。我相信我已经正确设置了.NET类,因为我现在可以在VB6中使用它,但是我不认为当我的VB6应用程序关闭/崩溃时我会释放对COM对象的所有引用。这是一个非常受事件驱动的.NET对象,我们已经看到了一些奇怪的问题,在这些问题中,.NET对象的前一个实例化接收到一个用于该对象的新实例化的事件 为了测试这一点,我在.NET对象中添加了跟踪编写器。这些跟踪打印出本地秒表的Stopwatch.elapsedmillesons
Stopwatch.elapsedmillesons
属性,我使用该属性跟踪.NET对象的生存期,以及在.NET对象的每个实例化上创建的新GUID
此对象的第一个实例化(通过VB6)如下所示:
0>>>实例化手机>进入代理登录>创建CTISoftphone>订阅PhoneEvent>订阅OnCreateCitconf>等待任务结果。>接收到的事件:onCreateTicConf:无消息>onCreateTicConf不为空,正在触发事件。>调用LoginAgent>Unsubscribing from onCreateTicConf>Entered AgentLogin>Creating CTISoftphone>Subscribing to PhoneEvent>Subscribing to onCreateTicConf>等待任务结果。>接收到的事件:OnCreateTicConf:无消息>OnCreateTicConf为空,无法触发事件。如果我理解,您正在.NET对象中接收事件。在某些时候,您必须订阅活动才能接收它们。你退订了吗?订阅添加了一个引用——无论是COM类型还是.NET类型。您仍在接收活动的事实向我表明,您仍然订阅了这些活动。我也建议取消订阅活动。我怀疑@JoeWillcoxson关于活动订阅的说法是正确的。您是否尝试过通过Visual Studio运行VB6应用程序来调试.Net类?i、 e.在项目属性->调试选项卡->启动操作->启动外部程序(VB6可执行文件)下。您提到了GC,您是否实现了强制收集?作为调试测试,您可以在类中实现析构函数,并验证它是否在强制GC集合上运行;这将允许您确定假定未引用的实例是否已发布。VB6早在.NET之前就已“管理”。将VB6对象的引用设置为nothing是无用的,除非底层COM对象引用系统有很多缺陷。因为它是一个.NET对象,所以这是无用的。事实上,由于这两个世界都是可以管理的,所以在正常的操作过程中,您不必做任何事情。问题(来自另一个实例的调用)可能是由您的代码造成的。有什么不受管理的吗?一切都是VB6和.NET吗?有自定义线程启动吗?COM对象线程模型?没有一些复制代码很难帮助更多人。好吧,所以我在
Dispose
中实现了IDisposable
并取消订阅所有事件。这似乎是可行的,但我认为这更多的是一个副产品,而不是因为GC实际上正在拾取这些对象(例如,如果没有任何孤立对象具有实时事件,它们就不能错误地捕获任何内容)。这感觉更像是一种黑客行为,我仍然很想知道“正确”的方法来做到这一点。@SimonMourier,唯一使用的非托管资源是一个日志文件,我在记录时只掌握了它的句柄。关于线程,是的,.NET对象中还有其他线程-我们有一个线程正在使用来自电话系统的事件。我自己还没有设置线程模型,所以.NET对象仍然是MTA,VB6是STA(我不完全理解这些,所以可能我没有正确设置?)。我同意你的观点,这个问题可能在我的代码中,这是我第一次使用COM和.NET,而且我肯定我不是第一个遇到这个问题的人。
0:003> !dumpheap -stat -type MyApp.ClickToDial.Phone
Statistics:
MT Count TotalSize Class Name
038492c4 1 24 MyApp.ClickToDial.Phone+<>c__DisplayClass77_0
03848b8c 1 32 MyApp.ClickToDial.Phone+OnCallReleasedHandler
03848aa8 1 32 MyApp.ClickToDial.Phone+OnCallTransferredHandler
038488e0 1 32 MyApp.ClickToDial.Phone+OnLoginAgentConfHandler
038487fc 1 32 MyApp.ClickToDial.Phone+OnLogoutAgentConfHandler
03848718 1 32 MyApp.ClickToDial.Phone+OnSendDtmfConfHandler
03848634 1 32 MyApp.ClickToDial.Phone+OnGetCtiDataConfHandler
03848550 1 32 MyApp.ClickToDial.Phone+OnInitiateConferenceConfHandler
0384846c 1 32 MyApp.ClickToDial.Phone+OnCancelConferenceConfHandler
03848388 1 32 MyApp.ClickToDial.Phone+OnMakeCallConfHandler
038482a4 1 32 MyApp.ClickToDial.Phone+OnCompleteConferenceConfHandler
038481c0 1 32 MyApp.ClickToDial.Phone+OnCallConferencedHandler
038480dc 1 32 MyApp.ClickToDial.Phone+OnErrorHandler
03847ff8 1 32 MyApp.ClickToDial.Phone+OnCallDroppedHandler
03847f14 1 32 MyApp.ClickToDial.Phone+OnCallEstablishedHandler
038489c4 3 96 MyApp.ClickToDial.Phone+OnCreateCtiConfHandler
03844ac4 1 116 MyApp.ClickToDial.Phone
Total 19 objects
public bool AgentLogin(string extension, string agentId, string password)
{
writeTrace($"Entered {nameof(AgentLogin)}");
_extension = extension;
_agentId = agentId;
try
{
writeTrace($"Creating {nameof(CTISoftphone)}");
_phone = new CTISoftphone($"ext={extension},logFile={Settings.LogLocation}");
}
catch (Exception ex)
{
writeTrace($"Caught exception: {ex.GetType()} | {ex.Message}");
//this one might get lost since we haven't subscribed to CTISoftphone.PhoneEvent yet.
OnError?.Invoke($"{_wrapperLogPrefix} Unable to create CTISoftPhone. The error was: {ex.Message}");
return false;
}
writeTrace($"Subscribing to {nameof(CTISoftphone.PhoneEvent)}");
_phoneEventHandler = new CTISoftphone.PhoneEventHandler(cti_phone_event);
_phone.PhoneEvent += _phoneEventHandler;
var are = new AutoResetEvent(false);
var task = new Task(() =>
{
try
{
writeTrace("Calling LoginAgent");
_phone.LoginAgent(agentId, password);
}
catch (Exception)
{
}
});
task.ContinueWith((t) => are.Set());
var task_handler = new OnCreateCtiConfHandler(task.Start);
writeTrace("Subscribing to OnCreateCtiConf");
OnCreateCtiConf += task_handler;
writeTrace("Waiting for task result");
are.WaitOne();
writeTrace("Unsubscribing from OnCreateCtiConf");
OnCreateCtiConf -= task_handler;
return true;
}
private void cti_phone_event(CTISoftphone.CTIEvent type, string info)
{
writeTrace($"Received event: {type} : {(string.IsNullOrEmpty(info) ? "No message" : info)}");
switch (type)
{
case CTISoftphone.CTIEvent.OnMakeCallConf:
OnMakeCallConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnCompleteConferenceConf:
OnCompleteConferenceConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnCancelConferenceConf:
OnCancelConferenceConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnCallEstablished:
OnCallEstablished?.Invoke();
break;
case CTISoftphone.CTIEvent.OnCallDropped:
OnCallDropped?.Invoke();
break;
case CTISoftphone.CTIEvent.OnCallConferenced:
OnCallConferenced?.Invoke();
break;
case CTISoftphone.CTIEvent.OnError:
OnError?.Invoke(info);
break;
case CTISoftphone.CTIEvent.OnInitiateConferenceConf:
OnInitiateConferenceConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnGetCTIDataConf:
OnGetCtiDataConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnSendDTMFConf:
OnSendDtmfConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnLogoutAgentConf:
OnLogoutAgentConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnLoginAgentConf:
OnLoginAgentConf?.Invoke();
break;
case CTISoftphone.CTIEvent.OnCreateCTIConf:
if (OnCreateCtiConf != null)
{
writeTrace("OnCreateCtiConf was not null, firing event");
OnCreateCtiConf();
}
else
{
writeTrace("OnCreateCtiConf was null, CANNOT fire event");
}
break;
case CTISoftphone.CTIEvent.OnReleaseCallConf:
OnCallReleased?.Invoke();
break;
case CTISoftphone.CTIEvent.OnCallTransferred:
OnCallTransferred?.Invoke();
break;
default:
OnError?.Invoke($"{_wrapperLogPrefix} Received unknown CTI event type: {type}");
break;
}
}
public void Dispose()
{
writeTrace("Dispose called");
if (_phone != null)
_phone.PhoneEvent -= _phoneEventHandler;
_phone = null;
_ctiData = null;
OnCallEstablished = null;
OnCallDropped = null;
OnError = null;
OnCallConferenced = null;
OnCompleteConferenceConf = null;
OnMakeCallConf = null;
OnCancelConferenceConf = null;
OnInitiateConferenceConf = null;
OnGetCtiDataConf = null;
OnSendDtmfConf = null;
OnLogoutAgentConf = null;
OnLoginAgentConf = null;
OnCreateCtiConf = null;
OnCallTransferred = null;
OnCallReleased = null;
}
~Phone()
{
writeTrace("Destructor called");
Dispose();
}
0:003> !dumpheap -stat -type Avaya.APS.CTD.
Statistics:
MT Count TotalSize Class Name
03846d64 1 32 Avaya.APS.CTD.CTISoftphone+PhoneEventHandler
03846bf0 1 108 Avaya.APS.CTD.CTISoftphone
Total 2 objects