2010-05-04 29 views
11

Tất cả những câu hỏi sau:Sử dụng đối tượng Wrapper để đúng dọn dẹp excel interop đối tượng

đấu tranh với các vấn đề mà C# không nhả Excel COM đối tượng đúng cách sau khi sử dụng chúng. Có hai hướng chính làm việc xung quanh vấn đề này:

  1. Giết quy trình Excel khi Excel không còn được sử dụng nữa.
  2. Cẩn thận chỉ định rõ ràng từng đối tượng COM được sử dụng cho một biến đầu tiên và để đảm bảo rằng cuối cùng, Marshal.ReleaseComObject được thực hiện trên mỗi biến.

Một số đã tuyên bố rằng 2 quá tẻ nhạt và luôn có một số sự không chắc chắn cho dù bạn quên tuân theo quy tắc này tại một số vị trí trong mã. Vẫn còn 1 dường như bẩn và dễ bị lỗi, tôi đoán rằng trong một môi trường bị hạn chế cố gắng để giết một quá trình có thể làm tăng một lỗi bảo mật. Vì vậy, tôi đã suy nghĩ về việc giải quyết 2 bằng cách tạo ra một mô hình đối tượng proxy khác bắt chước mô hình đối tượng Excel (đối với tôi, nó sẽ đủ để thực hiện các đối tượng mà tôi thực sự cần). Nguyên tắc sẽ trông như sau:

  • Mỗi lớp Interop của Excel có proxy bao bọc đối tượng của lớp đó.
  • Proxy giải phóng đối tượng COM trong trình hoàn thiện của nó.
  • Proxy bắt chước giao diện của lớp Interop.
  • Bất kỳ phương thức nào ban đầu trả lại đối tượng COM được thay đổi để trả lại proxy thay thế. Các phương thức khác chỉ đơn giản là ủy nhiệm việc triển khai thực hiện đối tượng COM bên trong.

Ví dụ:

public class Application 
{ 
    private Microsoft.Office.Interop.Excel.Application innerApplication 
     = new Microsoft.Office.Interop.Excel.Application innerApplication(); 

    ~Application() 
    { 
     Marshal.ReleaseCOMObject(innerApplication); 
     innerApplication = null; 
    } 

    public Workbooks Workbooks 
    { 
     get { return new Workbooks(innerApplication.Workbooks); } 
    } 
} 

public class Workbooks 
{ 
    private Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks; 

    Workbooks(Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks) 
    { 
     this.innerWorkbooks = innerWorkbooks; 
    } 

    ~Workbooks() 
    { 
     Marshal.ReleaseCOMObject(innerWorkbooks); 
     innerWorkbooks = null; 
    } 
} 

Câu hỏi của tôi cho bạn là đặc biệt:

  • Ai này tìm thấy một ý tưởng tồi và tại sao?
  • Ai tìm thấy ý tưởng này? Nếu vậy, tại sao không ai thực hiện/xuất bản một mô hình như vậy chưa? Có phải chỉ vì nỗ lực đó, hay tôi đang thiếu một vấn đề giết người với ý tưởng đó?
  • Có thể không/xấu/dễ xảy ra lỗi khi ReleaseCOMObject trong trình hoàn tất không? (Tôi đã chỉ thấy các đề xuất để đưa nó vào một Dispose() thay vì trong một finalizer - tại sao?)
  • Nếu cách tiếp cận có ý nghĩa, bất kỳ đề xuất để cải thiện nó?

Trả lời

5

Không thể/xấu/nguy hiểm khi thực hiện ReleaseCOMObject trong trình phá hủy? (Tôi đã chỉ thấy các đề xuất để đưa nó vào một Dispose() chứ không phải trong một destructor - tại sao?)

Không nên đặt mã làm sạch của bạn trong trình hoàn thiện vì không giống hàm hủy trong C++, nó không được gọi là xác định. Nó có thể được gọi ngay sau khi đối tượng đi ra khỏi phạm vi. Có thể mất một giờ. Nó có thể không bao giờ được gọi. Nói chung, nếu bạn muốn vứt bỏ các đối tượng không được quản lý, bạn nên sử dụng mẫu IDisposable chứ không sử dụng finalizer.

Điều này solution mà bạn đã liên kết với các nỗ lực khắc phục sự cố đó bằng cách gọi điện cho bộ thu gom rác và đợi người hoàn thành hoàn thành. Điều này thực sự không được đề xuất nói chung nhưng đối với tình huống cụ thể này, một số người coi đó là một giải pháp có thể chấp nhận được do khó theo dõi tất cả các đối tượng không được quản lý tạm thời được tạo ra. Nhưng dọn sạch một cách rõ ràng là cách thích hợp để thực hiện nó. Tuy nhiên với những khó khăn khi làm như vậy, "hack" này có thể được chấp nhận. Lưu ý rằng giải pháp này có lẽ tốt hơn ý tưởng bạn đề xuất.

Nếu thay vào đó bạn muốn cố gắng dọn dẹp rõ ràng, hướng dẫn "không sử dụng hai dấu chấm với đối tượng COM" sẽ giúp bạn nhớ giữ tham chiếu đến mọi đối tượng bạn tạo để bạn có thể làm sạch chúng khi bạn đã hoàn tất.

+0

Bạn đang đúng với finalizer ở gốc, nhưng trong tình huống đặc biệt này, tôi có thể nói: Chúng ta hãy ủy thác trách nhiệm thu gom rác, vì do finalizer tôi có thể dựa vào GC cũng phát hành đối tượng COM ngay khi đối tượng bên trong bị phá hủy. Nếu tôi muốn phát hành rõ ràng tất cả các tài nguyên ở một số nơi trên mã, tôi có thể sử dụng GC.Collect(). – chiccodoro

+0

@chiccodoro: Vấn đề với phương pháp này là nó dựa vào chi tiết triển khai của GC. Nếu GC thay đổi một chút, nó có thể không hoạt động nữa. Nhưng tôi hiểu sự khó khăn của việc thực hiện nó một cách đúng đắn trong tình huống này, và rằng hack có thể là giải pháp tiết kiệm chi phí nhất trong một số tình huống. –

+0

Hmm. Tôi thấy điểm của bạn. Nhưng việc sử dụng() cho mỗi dấu chấm đơn giản là tẻ nhạt. Có lẽ chỉ định nghĩa một phương thức Dispose() trên Excel.Application chạy GC.Collect() để tất cả các đối tượng Excel khác cũng được giải phóng vào thời điểm này? Cho đến khi Excel.Application lá phạm vi "sử dụng", tôi không yêu cầu chương trình của tôi để phát hành tài nguyên COM xuất phát từ ứng dụng. – chiccodoro

2

Chúng tôi sử dụng lớp LifetimeScope được mô tả trong tạp chí MSDN. Sử dụng nó đúng cách dọn dẹp các đối tượng và đã làm việc tuyệt vời với xuất khẩu Excel của chúng tôi. Mã này có thể được tải về tại đây và cũng có thể chứa các bài báo tạp chí:

http://lifetimescope.codeplex.com/SourceControl/changeset/changes/1266

+0

Hi Codezy, cảm ơn vì liên kết thú vị. – chiccodoro

+0

cool linkz nhờ bạn đời. –

0

Những gì tôi muốn làm:

class ScopedCleanup<T> : IDisposable where T : class 
{ 
    readonly Action<T> cleanup; 

    public ScopedCleanup(T o, Action<T> cleanup) 
    { 
     this.Object = o; 
     this.cleanup = cleanup; 
    } 

    public T Object { get; private set; } 

    #region IDisposable Members 

    public void Dispose() 
    { 
     if (Object != null) 
     { 
      if(cleanup != null) 
       cleanup(Object); 
      Object = null; 
      GC.SuppressFinalize(this); 
     } 
    } 

    #endregion 

    ~ScopedCleanup() { Dispose(); } 
} 

static ScopedCleanup<T> CleanupObject<T>(T o, Action<T> cleanup) where T : class 
{ 
    return new ScopedCleanup<T>(o, cleanup); 
} 

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject, Action<ComType> actionBeforeRelease) where ComType : class 
{ 
    return 
     CleanupObject(
      comObject, 
      o => 
      { 
       if(actionBeforeRelease != null) 
        actionBeforeRelease(o); 
       Marshal.ReleaseComObject(o); 
      } 
     ); 
} 

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject) where ComType : class 
{ 
    return CleanupComObject(comObject, null); 
} 

trường hợp sử dụng. Lưu ý cuộc gọi tới Thoát, có vẻ như cần thiết để kết thúc quá trình:

using (var excel = CleanupComObject(new Excel.Application(), o => o.Quit())) 
using (var workbooks = CleanupComObject(excel.Object.Workbooks)) 
    { 
     ... 
    } 
+0

Xin chào Rotaerk. Điều này trông giống như một sự tăng cường thú vị của phương pháp tiếp cận IDisposable, nhưng nó không trả lời câu hỏi của tôi. (Hoặc sau đó tôi bỏ lỡ điểm.) – chiccodoro

+1

Như đã đề cập, sử dụng IDisposable là cách để đi, không phải là finalizer. Các finalizers sẽ đảm bảo tài nguyên được dọn sạch, nhưng không cho bạn kiểm soát khi nào. GC.Collect không đảm bảo rằng mọi thứ sẽ được thu thập ngay lập tức, vì vậy nó không phải là giải pháp. Một lần dùng một lần sẽ cho phép bạn kiểm soát chặt chẽ khi phát hành xảy ra. Hơn nữa, việc sử dụng-tuyên bố cho phép bạn làm điều này một cách ngoại lệ an toàn. (Chỉ cần nhớ khi gỡ lỗi không dừng ứng dụng của bạn sớm trước khi dọn dẹp, hoặc nó sẽ không được thực hiện, và bạn sẽ có một loạt các tiến trình Excel đang chạy.) – Rotaerk

+0

Lý do bạn nhìn thấy trái trên các đối tượng là vì bạn HAVENT gọi là .WaitForPendingFinalizers() ở giữa các cuộc gọi đến Collect() nó đơn giản. –

1

Nhìn vào dự án của tôi MS Office for .NET. Có vấn đề được giải quyết với các đối tượng bao bọc referencich và các đối tượng gốc thông qua khả năng ràng buộc muộn của VB.NET.

+0

Xin chào TcK. Bạn có thể mở rộng về điều đó? Trong trang dự án bạn viết: "MS Office Primary Interop Assemblies là không cần thiết." Điều đó có nghĩa là người ta không cần phải trực tiếp truyền đạt mô hình đối tượng của nó bởi vì các đối tượng bao bọc chăm sóc, hay nó có nghĩa là bạn đã thực hiện một mức độ khá thấp, thay thế hoàn thành lắp ráp interop? – chiccodoro

+0

Có, nó giải quyết cách tiêu chuẩn của interopassembly. Nó kiểm tra động lực mô hình đối tượng COM khi chạy. Nó chậm hơn PIA ít hơn nhưng độc lập với phiên bản (bao gồm cả phiên bản trong tương lai). – TcKs

+0

Điều đó nghe thật thú vị. - Lần cuối bạn thay đổi mã và trang web trong năm 2008? – chiccodoro

0

Đối với những gì nó có giá trị, các Excel Refresh Service on codeplex sử dụng logic này:

public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class 
    { 
     if (reference == null) return; 
     try 
     { 
      doThis(reference); 
     } 
     finally 
     { 
      Marshal.ReleaseComObject(reference); 
     } 
    } 
+0

Cảm ơn bạn đã thông báo! Điều này sẽ tạo ra rất nhiều callback lồng nhau mặc dù, do đó tôi không thấy một lợi thế so với cách tiếp cận using() – chiccodoro

Các vấn đề liên quan