2016-05-09 12 views
2

いくつかのWebサービスに接続するWindows Formsアプリケーションがあります。システム内のドキュメントを一覧表示し、ユーザーがダブルクリックすると、ローカルコンピュータにファイルをダウンロードし、ドキュメントを開いて編集します。ユーザーが文書を閉じると、文書をシステムにアップロードし直します。IOException catchブロックにもかかわらずIOExceptionが発生しました

このプロセスでは、ドキュメントのファイルロックを監視しています。ファイルロックが解除されるとすぐにドキュメントをアップロードします。

​​方法は次のようになります。

private const int ErrorLockViolation = 33; 
private const int ErrorSharingViolation = 32; 

private static bool IsFileLocked(string fileName) 
{ 
    Debug.Assert(!string.IsNullOrEmpty(fileName)); 

    try 
    { 
     if (File.Exists(fileName)) 
     { 
      using (FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None)) 
      { 
       fs.ReadByte(); 
      } 
     } 

     return false; 
    } 
    catch (IOException ex) 
    { 
     // get the HRESULT for this exception 
     int errorCode = Marshal.GetHRForException(ex) & 0xFFFF; 

     return errorCode == ErrorSharingViolation || errorCode == ErrorLockViolation; 
    } 
} 

我々は試行間の5秒のスリープとループでこれを呼び出します。これは大部分の時間にはうまくいくように見えますが、時にはこのメソッドからIOExceptionが表示されることがあります。私はこの例外がスローされる可能性があるのか​​分かりません。

例外は次のとおりです。

IOException: The process cannot access the file 'C:\Users\redacted\AppData\Roaming\redacted\Jobs\09c39a4c-c1a3-4bb9-a5b5-54e00bb6c747\4b5c4642-8ede-4881-8fa9-a7944852d93e\CV abcde abcdef.docx' because it is being used by another process. 
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) 
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) 
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy) 
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) 
at redacted.Helpers.IsFileLocked(String fileName) 
at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object& ppunk) 
at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID) 
at redacted.OutlookHelper.GetOutlookInternal() 
at redacted.OutlookHelper.GetOutlook() 
... 

他の奇数部分はスタックトレースです。これはシステム全体の異なる部分であるGetOutlookを指します(文書処理とは無関係)。​​には2つのコードパスがあり、いずれもGetOutlookInternalメソッドでは到達できません。まるでスタックが壊れているかのようです。

なぜFileSystemWatcherを使用しないのですか?

FileSystemWatcherを使用してファイルの変更を監視することを検討しましたが、ユーザーがドキュメントを開いたままにしてさらに変更を行う可能性があるため、この方法を割り引いています。私たちのWebサービスは、アップロードするとすぐに文書のロックを解除します。ユーザーが作業を完了するまでは、その作業はできません。

私たちは、アプリケーションによってロックされているドキュメントのみに関係しています。ファイルをロックしないアプリケーションもありますが、ここではそれらを考慮する必要はありません。

Outlookの方法

以下

がスタックに表示されますGetOutlookInternal方法です - あなたが見ることができるように、それが唯一の展望相互運用を扱うと、文書の開口部には無関係です。それは​​に呼び出すことはありません:私は誤って私の問題の原因を見つけるのを助けたこの記事に出くわし

private static Application GetOutlookInternal() 
    { 
     Application outlook; 

     // Check whether there is an Outlook process running. 
     if (Process.GetProcessesByName("OUTLOOK").Length > 0) 
     { 
      try 
      { 
       // If so, use the GetActiveObject method to obtain the process and cast it to an Application object. 
       outlook = (Application)Marshal.GetActiveObject("Outlook.Application"); 
      } 
      catch (COMException ex) 
      { 
       if (ex.ErrorCode == -2147221021) // HRESULT: 0x800401E3 (MK_E_UNAVAILABLE) 
       { 
        // Outlook is running but not ready (not in Running Object Table (ROT) - http://support.microsoft.com/kb/238610) 
        outlook = CreateOutlookSingleton(); 
       } 
       else 
       { 
        throw; 
       } 
      } 
     } 
     else 
     { 
      // If not running, create a new instance of Outlook and log on to the default profile. 
      outlook = CreateOutlookSingleton(); 
     } 
     return outlook; 
    } 

    private static Application CreateOutlookSingleton() 
    { 
     Application outlook = new Application(); 

     NameSpace nameSpace = null; 
     Folder folder = null; 
     try 
     { 
      nameSpace = outlook.GetNamespace("MAPI"); 

      // Create an instance of the Inbox folder. If Outlook is not already running, this has the side 
      // effect of initializing MAPI. This is the approach recommended in http://msdn.microsoft.com/en-us/library/office/ff861594(v=office.15).aspx 
      folder = (Folder)nameSpace.GetDefaultFolder(OlDefaultFolders.olFolderInbox); 
     } 
     finally 
     { 
      Helpers.ReleaseComObject(ref folder); 
      Helpers.ReleaseComObject(ref nameSpace); 
     } 

     return outlook; 
    } 
+0

スタックトレースをデバッグしてウォークスルーできませんか?これはむしろ奇妙に思えますが、スタックトレースが壊れていることは疑いがあります(少なくともIOExceptionはあります)。あなたは 'GetOutlookInternal'のコードを投稿できますか? – Jcl

+0

Windowsでファイルを共有することは決してお勧めできません。あなたはOutlookを使ってファイルにアクセスしているようです。私はoledbがOutlookにファイルにアクセスするのに慣れていると思う。 Oledbは、マルチユーザー環境で動作するように設計されておらず、ファイルのロックを適切に処理しません。 c#でファイルをロックしていると思うかもしれませんが、同時に2つのプロセスが同時にファイルにアクセスすることはできません。別のWindowsプロセスがファイルにアクセスできないことを意味するわけではありません。 Asyncプロセスを使用していますか? – jdweng

+0

@ Jcl残念ながら、問題は一部のクライアントPCでのみ発生し、それでも散発的です。私はデバッガで再現できません。 Outlookメソッドを含めるように更新しました。 –

答えて

3

Marshal.GetHRForException does more than just Get-HR-For-Exception

それは、我々は、1がIOExceptionMarshal.GetHRForException(...)を二つのスレッド呼んでいたていたが判明しますファイルがロックされているかどうかを判断します(Win32エラーコード32または33)。別のスレッドは、Interopを使用してOutlookインスタンスに接続するようにMarshal.GetActiveObject(...)を呼び出していました。

GetHRForExceptionが最初に呼び出され、GetActiveObjectが2番目に呼び出されてCOMExceptionがスローされた場合、間違った例外とスタックトレースが発生します。これは、GetHRForExceptionが実際に例外を「設定」しており、GetActiveObjectが実際のCOMExceptionの代わりにその例外をスローするためです。再現する

例コード:

この問題は、次のコードを使用して再生することができます。新しいコンソールアプリケーションを作成し、Outlook COM参照をインポートしてコードに貼り付けます。アプリケーションを起動すると、Outlookが実行されていないことを確認してください:

public static void Main(string[] args) 
    { 
     bool isLocked = IsFileLocked(); 
     Console.WriteLine("IsLocked = " + isLocked); 
     ShowOutlookWindow(); 
    } 

    private static bool IsFileLocked() 
    { 
     try 
     { 
      using (FileStream fs = File.Open(@"C:\path\to\non_existant_file.docx", FileMode.Open, FileAccess.Read, FileShare.None)) 
      { 
       fs.ReadByte(); 
       return false; 
      } 
     } 
     catch (IOException ex) 
     { 
      int errorCode = Marshal.GetHRForException(ex) & 0xFFFF; 
      return errorCode == 32 || errorCode == 33; // lock or sharing violation 
     } 
    } 

    private static void ShowOutlookWindow() 
    { 
     try 
     { 
      Application outlook = (Application)Marshal.GetActiveObject("Outlook.Application"); 
      // ^^ causes COMException because Outlook is not running 
      MailItem mailItem = outlook.CreateItem(OlItemType.olMailItem); 
      mailItem.Display(); 
     } 
     catch (System.Exception ex) 
     { 
      Console.WriteLine(ex); 
      throw; 
     } 
    } 

あなたは、コンソールでCOMExceptionを見ることを期待するが、これはあなたが例外がDirectoryNotFoundExceptionあるか

IsLocked = False 
System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\path\to\non_existant_file.docx'. 
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) 
    at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) 
    at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) 
    at System.IO.File.Open(String path, FileMode mode, FileAccess access, FileShare share) 
    at MyProject.Program.IsFileLocked() 
    at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object& ppunk) 
    at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID) 
    at MyProject.Program.ShowOutlookWindow() 

ノートを見るもので、スタックは​​に呼び出されたGetActiveObjectを間違って示唆しています。

ソリューション:

この問題を解決するには、代わりにGetHRForExceptionException.HResultプロパティを使用するだけでした。以前は、このプロパティは保護されていたが、我々はこの変更により、.NET 4.5

private static bool IsFileLocked() 
{ 
    try 
    { 
     using (FileStream fs = File.Open(@"C:\path\to\non_existant_file.docx", FileMode.Open, FileAccess.Read, FileShare.None)) 
     { 
      fs.ReadByte(); 
      return false; 
     } 
    } 
    catch (IOException ex) 
    { 
     int errorCode = ex.HResult & 0xFFFF; 
     return errorCode == 32 || errorCode == 33; // lock or sharing violation 
    } 
} 

にプロジェクトをアップグレードしたので、それは動作が期待されているように、アクセスできるようになりました。コンソールは、今示しています

IsLocked = False 
System.Runtime.InteropServices.COMException (0x800401E3): Operation unavailable (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE)) 
    at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object& ppunk) 
    at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID) 
    at MyProject.Program.ShowOutlookWindow() 

TL; DR:あなたはまた、COMコンポーネントを使用している場合Marshal.GetHRForExceptionを使用しないでください。

+0

解決策をご返信いただきありがとうございます。私は似たようなことを見たので、私のコードは次のようになります: 'int errorCode =(int)typeof(IOException).InvokeMember(" HResult "、BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public、null、ex 、null); ' – adrianm

関連する問題