Робота з com-об'єктами з використанням збірок взаємодії, або «чому в пакеті office

Ця документація переміщена в архів і не підтримується.

На цій сторінці…

В .NET Framework є кілька механізмів для обміну даними між додатками .NET Framework і COM-об'єктами. Однак для запобігання витоку пам'яті при роботі з COM-об'єктами або в разі раптового припинення роботи COM-об'єкта ці функції слід використовувати досить вміло.

Найзручніший спосіб роботи з COM-об'єктами - збірки взаємодії (interop assemblies). В цьому випадку кероване додаток .NET взаємодіє з модулем RCW (Runtime Callable Wrapper - обгортка .NET-викликів), який управляє обміном з COM-об'єктом. Модулі RCW виконують багато корисних функцій при роботі з COM-об'єктами, включаючи активацію компонентів і маршалірованіе параметрів (наприклад, перетворення строкових даних в COM-рядки BSTR). Корпорація Microsoft надає спеціальні збірки взаємодії для роботи з системою Microsoft Office System, які називаються основними збірками взаємодії (Primary Interop Assemblies, PIA). Однак ці кошти не дозволяють вирішити проблеми, пов'язані з обробкою життєвих циклів COM-об'єктів, і тому потрібно знайти можливість розпізнавання і вирішення цих проблем за допомогою інших засобів.

Перестала працювати кнопка

Раптово, без всяких видимих ​​причин, при натисканні кнопки процедура обробки події Click не запускається. Подія Click для кнопки перестає працювати.

Більшість розробників швидко виявили б проблему, якби програма події Click перестала запускатися відразу після завершення процедури і виходу змінної з області видимості. Це саме той момент, коли в COM-додатку, яке управляє додатком Word, виникає проблема. Однак в .NET Framework об'єкт не обов'язково знищується відразу після того, як перестає бути видно. Замість цього він буде існувати до тих пір, поки .NET Framework не знищить його в довільний момент. Весь цей час програма події Click при натисканні користувачем кнопки меню успішно виконується, а потім раптово перестає правильно працювати, коли .NET Framework нарешті знищує об'єкт. Це фундаментальна відмінність в способі обробки життєвих циклів об'єктів, а, отже, і пам'яті, в середовищі COM і .NET Framework.

Управління пам'яттю в моделях COM і .NET

В .NET Framework використовується. якщо можна так висловитися, «протилежний», нестрогий підхід до звільнення пам'яті. Об'єкти будуть видалені з пам'яті в процесі складання сміття, при цьому виконується перевірка того, що об'єкти більше не використовуються. Процес складання сміття досить пасивний. Він виконується тільки в міру необхідності; зазвичай коли не вистачає пам'яті. В .NET Framework в разі знищення змінної об'єкт, на який вона посилається, просто стає кандидатом на видалення коли-небудь в майбутньому. Додаткові відомості про цю концепцію і збірці сміття в .NET Framework див. Статтю «Програмування збірки сміття» в Посібнику розробника .NET Framework.

Такий спосіб звільнення пам'яті створює проблему для роботи події Click. В .NET-додатку змінна, яка посилається на модуль RCW, керуючий додатком Word, в кінці процедури виходить за межі видимості, але значить лише, що модуль RCW, керуючий COM-об'єктом, позначається для збору сміття. В кінцевому рахунку, модуль RCW може бути видалений з пам'яті при видаленні сміття, і в цей час COM-об'єкт, яким керував модуль RCW, буде знищений. Тільки після цього .NET-додаток втрачає зв'язок з подією Click і кнопка перестає працювати.

Управління COM-пам'яттю

Однак, якщо необхідно звільнити COM-об'єкт до завершення програми, виникає інша проблема: пам'ять не відновлюється належним чином. У додатку .NET можна знищити все посилання на модуль RCW для відповідного COM-об'єкта, але сам COM-об'єкт необов'язково буде видалений. Наприклад, з додатком, яке постійно завантажує і вивантажує Word для обробки серії документів, може не вистачити пам'яті, оскільки раніше використовувалися екземпляри додатку Word як і раніше в ній залишаються. Програміст самостійно повинен управляти COM-об'єктами, щоб зменшити навантаження на пам'ять.

Отже, COM-об'єкти з Office ілюструють можливі проблеми. Як і багато великі COM-сервери, різні додатки Office містять в буквальному сенсі сотні об'єктів, і для звичайних завдань потрібне створення екземплярів декількох об'єктів. Наприклад, наступна програма запускає додаток Word, використовуючи для цього його основну збірку взаємодії (PIA), а потім використовує його модуль RCW, в результаті чого створюється документ:

Ця програма завантажує в пам'ять три об'єкти: модуль RCW, який управляє об'єктом Application додатки Word, його колекцію Documents і об'єкт Document. Якщо на наступному етапі в документ завантажується велике зображення, обсяг пам'яті, займаний COM-об'єктом, може стати дуже великим.

Після завершення роботи з об'єктами їх можна видалити, для цього можна знищити змінні, що посилаються на об'єкти Word:

На жаль, при складанні сміття пам'ять, яку займає COM-об'єктами, може не відновлюватися, навіть якщо процес збірки сміття запускається через брак пам'яті. При складанні сміття виконується спроба вибіркового видалення об'єктів з пам'яті: об'єктів, які використовувалися не довго, призначається вищий пріоритет під час збирання сміття, ніж об'єктів, які використовувалися протягом тривалого часу. Це означає, що звільнення COM-об'єкта відразу після завершення роботи з ним підвищує ймовірність того, що при складанні сміття він буде вилучений швидше.

Примітка. Більш докладно про збірку сміття і його вплив на продуктивність див. Статтю Garbage Collector Basics and Performance Hints в Посібнику розробника .NET Framework (англійською мовою).

При роботі з COM-об'єктом з додатки .NET використовуються два об'єкти: обгортка RCW і COM-об'єкт (або об'єкти). У процесі складання сміття відомий розмір тільки модуля RCW (який може бути невеликим), а розмір COM-об'єкта (який може бути більшим) невідомий. Тому, незважаючи на те що додаток .NET може звільнити модуль RCW, в процесі збору сміття модуль RCW може не піти з пам'яті, навіть в разі її нестачі. Поки модуль RCW залишається в пам'яті, керований ним COM-об'єкт також залишається в пам'яті.

Може здатися, що проблему вдасться вирішити, якщо примусово запустити збір сміття для RCW. Однак примусовий запуск збирача сміття практично завжди є невдалим рішенням в середовищі .NET Framework. Крім того, він може виявитися марним, оскільки не гарантує видалення COM-об'єкта. Навіть в разі явного виклику збір сміття завжди залишається довільним в середовищі .NET Framework.

Існують два механізми, що забезпечують видалення COM-об'єктів з пам'яті: об'єкт AppDomain і метод ReleaseComObject. Використання об'єкта AppDomain є найпростішим рішенням з управління COM-об'єктами, але виникає істотний ризик зниження продуктивності. При використанні методу ReleaseComObject такої проблеми не існує, проте в цьому випадку потрібно більш уважне планування і кодування.

Управління COM-об'єктами за допомогою класу AppDomain

У середовищі .NET Framework об'єкт AppDomain надає окрему середу для виконання програми. З точки зору управління COM-об'єктами, об'єкти AppDomain хороші через те, що при їх вивантаженні вивантажуються і всі ресурси, які вони використовували. Стратегія проста: для кожного створюваного COM-об'єкта створіть об'єкт AppDomain і завантажте в нього COM-об'єкт. До того ж такий спосіб спростить роботу з COM-об'єктами, оскільки можна завантажити кілька COM-об'єктів в один домен і видалити їх все одночасно.

Однак створення об'єкта AppDomain може вимагати значних ресурсів, тому його не слід використовувати, коли важливо зберегти високу швидкодію. Крім того, якщо з'являється можливість зовнішнього доступу до об'єкта AppDomain, порушується безпека доступу коду (якщо зовнішнього доступу немає, об'єкт AppDomain є безпечним).

У наступному прикладі передбачається, що існує COM-об'єкт типу MyDLL.MyObject в файлі з ім'ям MyDLL.DLL. Щоб додати в додатку посилання на цю COM-бібліотеку, необхідно в меню Project вибрати команду Add Reference, а потім натиснути кнопку Browse і вибрати файл MyDLL.DLL.

Після додавання посилання на COM-об'єкт, його можна завантажити в AppDomain, використовуючи метод CreateInstanceFromAndUnWrap. Метод отримує два параметри: повний шлях до збірки взаємодії і точку входу для модуля класу. Місцезнаходження збірки взаємодії можна визначити за допомогою об'єкта Type. Cвязана ним об'єкт Assembly має властивість Location, яке вказує, де можна знайти збірку взаємодії. Спочатку модуль RCW полягає в оболонку для запобігання непотрібної завантаження даних бібліотеки типів, але метод CreateInstanceFromAndUnWrap також видаляє цю оболонку.

У наступному прикладі визначається об'єкт типу AppDomain з ім'ям MyDomain, а потім в нього завантажує COM-об'єкт. Після роботи з COM-об'єктом AppDomain вивантажується, звільняючи і COM-об'єкт:

Управління COM-об'єктом за допомогою методу ReleaseComObject

В якості альтернативи використанню об'єктів AppDomain можна примусово видворити COM-об'єкт з пам'яті, зменшивши лічильник посилань COM-об'єкта до нуля. Після видалення COM-об'єкта з пам'яті можна потім звільнити модуль RCW (для подальшої збірки сміття). Цей спосіб складніший, ніж попередній, однак дозволяє уникнути зниження продуктивності, пов'язаної зі створенням об'єкта AppDomain.

При створенні в модулі RCW COM-об'єкта в його лічильнику посилань встановлюється одиниця. Незалежно від того, скільки клієнтів .NET посилаються на модуль RCW в одному процесі, значення лічильника посилань COM-об'єкта як і раніше залишається рівним одному. Однак, якщо перенести модуль RCW за межі кордонів процесу, значення лічильника посилань COM-об'єкта може збільшуватися, так що лічильник посилань не обов'язково дорівнює одиниці.

Платформа .NET Framework надає спосіб зменшення значення лічильника посилань COM-об'єкта безпосередньо, шляхом передачі модуля RCW в метод ReleaseComObject класу System.Marshall. Цей метод повертає значення лічильника посилань COM-об'єкта після його зменшення. У цьому прикладі завантажується додаток Word, зменшується значення лічильника посилань, а потім звільняється модуль RCW:

Щоб обробити випадки, коли значення, що повертається методом ReleaseComObject, більше нуля, можна викликати метод в циклі, який буде викликати об'єкт ReleaseComObject до тих пір, поки значення, що повертається не дорівнюватиме нулю:

Використання методу ReleaseComObject дозволяє звільнити COM-об'єкт, в той час як інші об'єкти як і раніше залежать від нього. Після цього спроба роботи з модулем RCW з додатки .NET призводить до виключення System.Runtime.InteropServices.InvalidComObjectException з додатковою інформацією «COM object that has been separated from its underlying RCW can not be used» ( «COM-об'єкт, який відключений від RCW , використовувати не можна »). Таким чином, потрібно точно знати, коли додаток завершує роботу з об'єктом, щоб можна було зменшити значення лічильника. Один із способів забезпечити узгодженість і ясність полягає в тому, щоб помістити виклики методу ReleaseComObject в загальний метод finalizer / dispose об'єкта. Це забезпечує однозначне відповідність між виконанням методу finalize об'єкта .NET і звільненням відповідного COM-об'єкта.

висновок

У статті описані типові проблеми, що виникають при розробці програм .NET Framework, що працюють з COM-об'єктами, і наведені рекомендації, як можна змінити програмне рішення, щоб усунути ці проблеми. Особливо бажано уникати виходу посилань на COM-об'єкт з області видимості до того, як COM-об'єкт знищений. Тобто, коли додаток буде готове звільнити COM-об'єкт, необхідно переконатися, що COM-об'єкт видалений з пам'яті. З двох описаних механізмів клас AppDomain є найпростішим механізмом управління життєвими циклами COM-об'єкта, а використання методу ReleaseComObject забезпечує кращу продуктивність.

Пітер Фогель (магістр бізнесу, сертифікований розробник Microsoft) - глава PHV Information services. PHV спеціалізується на розробках за допомогою .NET і XML. Пітер проектував, створював і встановлював интрасети і компонентні системи для Bayer AG, Exxon, Christie Digital і канадського банку Canadian Imperial Bank of Commerce.