我正在尝试使用示例https://gist.github.com/sverrirs/d099b34b7f72bb4fb386中的 GetApplicationVolume 函数,如下所示:
public static float? GetApplicationVolume(int pid)
{
ISimpleAudioVolume volume = GetVolumeObject(pid);
if (volume == null)
return null;
float level;
volume.GetMasterVolume(out level);
Marshal.ReleaseComObject(volume);
return level * 100;
}
private static ISimpleAudioVolume GetVolumeObject(int pid)
{
IMMDeviceEnumerator deviceEnumerator = null;
IAudioSessionEnumerator sessionEnumerator = null;
IAudioSessionManager2 mgr = null;
IMMDevice speakers = null;
try
{
// get the speakers (1st render + multimedia) device
deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers);
// activate the session manager. we need the enumerator
Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID;
object o;
speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out o);
mgr = (IAudioSessionManager2)o;
// enumerate sessions for on this device
mgr.GetSessionEnumerator(out sessionEnumerator);
int count;
sessionEnumerator.GetCount(out count);
// search for an audio session with the required process-id
ISimpleAudioVolume volumeControl = null;
for (int i = 0; i < count; ++i)
{
IAudioSessionControl2 ctl = null;
try
{
sessionEnumerator.GetSession(i, out ctl);
// NOTE: we could also use the app name from ctl.GetDisplayName()
int cpid;
ctl.GetProcessId(out cpid);
if (cpid == pid)
{
volumeControl = ctl as ISimpleAudioVolume;
break;
}
}
finally
{
if (ctl != null) Marshal.ReleaseComObject(ctl);
}
}
return volumeControl;
}
finally
{
if (sessionEnumerator != null) Marshal.ReleaseComObject(sessionEnumerator);
if (mgr != null) Marshal.ReleaseComObject(mgr);
if (speakers != null) Marshal.ReleaseComObject(speakers);
if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator);
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(AudioManager.GetApplicationVolume(5992).ToString());
}
}
并得到“未处理的异常:System.Runtime.InteropServices.InvalidComObjectException: Объект COM, который был отделен от своего базового RCW, использоваться не может.
谷歌搜索时,我进入了一些关于流量的丛林,完全糊涂了。帮助!CC# 和 .NET 以前不工作,所以不要扔拖鞋)
看,C# 中有
IDisposable对象。此接口的目的是帮助开发人员正确清理非托管资源,即使在托管代码中抛出异常也是如此。或者将非托管资源的控制权交给垃圾收集器,但这是关于终结器的另一个故事。为了保证即使发生异常也能调用某些代码,使用该构造
try-finally或专门IDisposable为其简化语法using。您可以在此处阅读更多内容:使用实现 IDisposable 的对象。那我为什么要这样做。COM 对象本质上是对非托管代码的调用,例如,用 C++ 编写的代码,但它可以通过称为 COM(组件对象模型)的特殊操作系统 API 进行交互。所以,如果需要调用实现
IDisposable类,那么就需要调用Dispose()COM对象Marshal.ReleaseComObject(),并且使用相同的构造try-finally,以免在托管代码中发生异常并且代码执行不进一步时导致内存泄漏.而这个类的开发者
AudioManager非常严格地遵循这些原则,而且他做得过火了。事实是方法中的代码行
GetVolumeObject按以下顺序执行(当它们达到正确的 PID 时)当您返回该方法时,您正在尝试访问一个 COM 对象,结果证明
Marshal.ReleaseComObject.用 dotnet 语言说,您正在尝试使用一个之前已经被销毁的对象。
该怎么办?最简单的方法是欺骗块设计
finally像这样
在这种情况下,它将以工作状态
volumeControl从一个方法返回GetVolumeObject到另一个方法GetApplicationVolume。我通过替换我的一个进程的 PID 来检查,为了清楚起见,我稍微减少了我从中读取它的应用程序的体积。
控制台输出:
好吧,这也有效。