21

在我的程序中,我使用运行对象表 (ROT) 来确保我的程序只有一个实例正在运行。由于我从不幸离开公司的开发人员那里“继承”了该代码,因此我是解决问题的可怜人。代码运行良好,但我们有 3 个客户(共 39,000 名)将获得AccessDeniedException. 每个客户都以用户模式运行软件。

有什么建议可能是错的吗?

bool retVal = false;
IMoniker[] arrMoniker = new IMoniker[1];
IBindCtx bindCtx = null;
string displayName;
int hResult;
int mkSys;
Guid clsidRot;
bool guidCompare = false;

IntPtr number = IntPtr.Zero;
moreObjectsListed = false;
objectFromRot = null;

try
{
    // check the objects in the running object table for fitting the specified class id
    while ((retVal == false) && (0 == enumMoniker.Next(1, arrMoniker, number)))
    {
        hResult = CreateBindCtx(0, out bindCtx);
        if (hResult == 0)
        {
            arrMoniker[0].IsSystemMoniker(out mkSys);

            if (mkSys == 4)
            {
                try
                {
                    // the display name is the class id of the object in the table
                    // --> AccessDeniedException raises here <--
                    arrMoniker[0].GetDisplayName(bindCtx, null, out displayName);
                    clsidRot = new Guid(displayName.Substring(1));  
                    guidCompare = clsidRot.Equals(clsid);
                }
                catch(Exception) {}

                // an object with fitting class id was found
                if (guidCompare == true)
                {
                    rot.IsRunning(arrMoniker[0]);
                    rot.GetObject(arrMoniker[0], out objectFromRot);
                    retVal = true;
                }
            }
        }
    }
}
finally
{
    if (arrMoniker[0] != null)
    {
        moreObjectsListed = true;
        Marshal.ReleaseComObject(arrMoniker[0]);
    }
    if (bindCtx != null)
    {
        Marshal.ReleaseComObject(bindCtx);
    }
}

编辑:这是在 ROT 中注册对象的请求代码:

internal static extern uint RegisterActiveObject([MarshalAs(UnmanagedType.IUnknown)]object pIUnknown, ref Guid refclsid, uint flags, out uint pdwRegister);
internal const uint ActiveObjectStrong = 0;

...

NativeMethods.RegisterActiveObject(this, ref guid, NativeMethods.ActiveObjectStrong, out this.runningObjectTableRegisteredId);

编辑2:

首先对所有调查人员来说是一个很大的借口,我们没有得到 AccessDeniedException 它是一个 System.UnauthorizedAccessException (HRESULT: 0x80070005 (E_ACCESSDENIED))。

其次,“调查员”Ken Brittain 的问题的答案:-SharePoint不在其中-我愿意向 ROT 请求正确的对象-另一个提示可能是 3 个问题中的 1 个(除了 39,000 个正常工作)正在运行WTS(Windows 终端服务器)上的应用程序

编辑3:

这是其中一个异常的堆栈跟踪:(我已经翻译了堆栈跟踪,因为它在德国机器上)

System.UnauthorizedAccessException: Access denied (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
at System.Runtime.InteropServices.ComTypes.IRunningObjectTable.EnumRunning(IEnumMoniker& ppenumMoniker)
at Datev.Framework.DirectStart.RunningObjectTableClientManager..ctor()

堆栈跟踪的其余部分在我们的代码中。在这种情况下,可标记的是在我们的 RunningObjectTableClientManager 的构造函数中引发了异常。这是该构造函数的代码:

private IRunningObjectTable rot;
private IEnumMoniker enumMoniker;

public RunningObjectTableClientManager()
{
    int retVal = GetRunningObjectTable(0, out this.rot);

    if (retVal == 0)
    {
        rot.EnumRunning(out this.enumMoniker);
    }
}
4

3 回答 3

5

根据我的经验,GUID 冲突的可能性虽然看起来不太可能,但没有进行调查。我采取的第一条路线是寻找可能导致AccessDeniedException. 从那里向后工作,您可以看到GetDisplayName没有显式抛出此异常(或返回任何类似的东西)。

那么有什么作用呢?您的代码似乎在 C# 中。除非我错误地使用 C# 中的 COM,否则将通过主互操作。只有两 (2) 个互操作公开了IMoniker我能找到的接口:

  • System.Runtime.InteropServices.ComTypes包含IMoniker
  • Microsoft.VisualStudio.OLE.Interop 也包含一个IMoniker

您正在谈论一个应用程序,所以我的直觉告诉我您正在使用运行时版本。查看电话,我找不到返回任何形式的拒绝访问 HRESULT或类似情况的电话。VisualStudio互操作确实提到了以下有关访问和信任的内容:Using Libraries from Partially Trusted Code。这听起来像是一条可以遵循的路径,如果您使用的是 Visual Studio 互操作,它将适用。

如果您使用的是包含在mscorlib.dll程序集中的运行时服务命名空间(根据此页面.NET Framework Assemblies Callable by Partially Trusted Code被标记为可调用的部分受信任代码),则该解释似乎不适用。

那么现在怎么办?我进行了搜索,发现除了在 MSDN中标记为过时的Microsoft.Office.Server.ApplicationRegistry.Infrastructure.AccessDeniedExceptionAccessDeniedException类之外没有支持的实现。该类在SharePoint 2010类库下归档。

所以这是我的问题:您使用的是哪个互操作?SharePoint 完全在其中吗?我说之前没有怀疑 GUID 碰撞,但现在我质疑这个假设。您是否正在向 ROT 请求正确的对象?这个对象是否在另一个进程下运行(意思是不是你的)?

于 2011-11-22T13:30:44.937 回答
1

这个站点看来,这可能是由于注册表设置或表中注册的对象的安全设置:

           Check "HKLM\Software\Network OLE\Enabled". Fail the    
           request if zero.                                       

           Check "HKCU\Software\Network OLE\Enabled". Fail the        
           request if zero.                                           
           Before performing any operation against a ROT entry        
           (i.e., IRunningObjectTable::Revoke,                        
           IRunningObjectTable::IsRunning,                            
           IRunningObjectTable::GetObject,                            
           IRunningObjectTable::NoteTimeChange,                       
           IRunningObjectTable::GetTimeOfLastChange, or when          
           including an entry in an IEnumMoniker::Next of an          
           IEnumMoniker returned from                                 
           IRunningObjectTable::EnumRunning), check the call against  
           the SECURITY_DESCRIPTOR available from                     
           IRunningObjectTable::Register. This will be either the     
           value returned by the object's                             
           IActivationSecurity::GetSecurityDescriptor at the time of  
           IRunningObjectTable::Register or will have been taken      
           from "HKCU\Software\Network OLE\DefaultROTSecurity" or     
           "HKLM\Software\Network OLE\DefaultROTSecurity" at the      
           time of IRunningObjectTable::Register if the object did    
           not support IActivationSecurity.
于 2011-12-01T02:46:04.377 回答
1

也许这不是您正在寻找的答案,但是在继承了这段代码之后,您是否停下来质疑这是否甚至是适合您的用例的正确技术?这是我第一次看到 C# 应用程序使用 Com Interop 来防止多个应用程序实例。我从来没有在 Com 方面有过很好的经验,并且发现了类似的无法解释或未记录的异常。

为什么不看一下防止多个应用程序实例的替代技术呢?我在过去的解决方案中使用了互斥锁,从来没有遇到过问题。虽然我手头没有我过去的代码,但这个问题之前已经在 stackoverflow 上多次讨论过,其中一些很好的答案已经过同行评审和社区编辑。

例如'在 C# 中使用全局互斥体的好模式是什么?' 有一个很好的社区编辑答案,似乎考虑了各种奇怪的球竞赛条件和线程/进程终止以及潜在的安全问题。

所以我的建议是远离 Com Interop 并改用 Mutex 实现。

于 2011-12-01T07:25:34.770 回答