Excel вызывает сервер автоматизации .NET из двух разных доменов приложений?

У меня есть подключаемый модуль Excel (написанный на C #) со статической переменной, которая лежит в основе кэша одноэлементных данных:

static DataCache _instance;

Доступ к нему осуществляется через три разных пути кода:

  1. Обработчики событий на ленточной панели VSTO инициализируют экземпляр, а также читают его для отображения во вспомогательных диалоговых окнах.
  2. Сервер RTD (класс, который объявлен [ComVisible] и реализует интерфейс IRtdServer) использует данные для формул RTD.
  3. Набор вызовов автоматизации (реализованных в другом классе, объявленном [ComVisible]) также работает с данными. Они вызываются с помощью кода VBA, который вызывается при нажатии кнопок на листе Excel.

РЕДАКТИРОВАТЬ (# 3):

В зависимости от порядка, в котором эти пути кода сначала вызываются, я обнаружил, что мой код выполняется в двух отдельных доменах приложений.

Весь доступ из обработчиков событий ленты происходит в домене приложений под названием «MyPlugIn.vsto». Если это ПЕРВЫЙ доступ к моему COM-объекту, то все последующие вызовы (включая вызовы RTD) происходят в том же домене приложений.

Однако если ПЕРВЫЙ доступ осуществляется через интерфейс RTD, то этот вызов и все последующие вызовы RTD происходят в домене приложения, называемом «DefaultDomain». (Это происходит при загрузке сохраненного документа со встроенными формулами RTD.) Последующие вызовы для инициализации и управления DataCache через панель инструментов по-прежнему происходят в домене приложения «MyPlugIn.vsto». Это означает, что формулы RTD всегда выполняются так, как если бы DataCache не был инициализирован (поскольку статическая переменная, установленная в одном домене приложения, остается неинициализированной в другом).

Похоже, что Excel или VSTO создают домен приложения при инициализации VSTO. Объекты, созданные через COM-взаимодействие до этой инициализации, попадают в домен приложений по умолчанию, а объекты, созданные после этого, попадают в домен приложений VSTO.

Как я могу гарантировать, что используется один и тот же экземпляр DataCache, независимо от того, в каком домене приложения создается мой объект сервера RTD?


person Eric    schedule 17.10.2010    source источник
comment
Что вы имеете в виду, что мой одноэлементный объект не используется должным образом? Это просто инициализация объекта, как предлагает @mhttk, или вы утверждаете, что разные потоки видят разное состояние в этой переменной (что кажется очень странным), или что-то еще?   -  person Rory    schedule 24.10.2010
comment
@Rory - в одном потоке инициализируется _instance. При последующих вызовах из того же потока он по-прежнему инициализируется, как ожидалось. Однако, когда другой поток пытается получить к нему доступ (несколько минут спустя - это не проблема времени), он принимает значение NULL и должен быть повторно инициализирован для использования этим потоком.   -  person Eric    schedule 24.10.2010
comment
Это довольно странно, не правда ли? По моему опыту взаимодействия .NET COM (с Internet Explorer, который похож, но явно отличается), этого не происходит. Это нормально с COM-квартирами? Вы уверены, что звонки относятся к одному и тому же процессу?   -  person Rory    schedule 24.10.2010
comment
@Rory - да, это очень странно и, похоже, не соответствует тому, что я нахожу в документации по COM-взаимодействию. Я думаю, что у меня работает только один процесс, но я начинаю задаваться вопросом, может ли Excel каким-то образом получить мне два AppDomains или, возможно, две параллельные версии среды выполнения. Я буду искать ...   -  person Eric    schedule 25.10.2010
comment
Как насчет: попробуйте использовать Process.GetCurrentProcess().Id и Thread.GetDomainId() в отладчике, чтобы увидеть, поступают ли вызовы в один и тот же процесс и домен приложения. И напишите сообщение в DataCache.Dispose (), чтобы вы могли видеть, когда оно удалено.   -  person Rory    schedule 25.10.2010
comment
Еще одна мысль - можете ли вы заморозить один поток в отладчике, а затем сделать еще один вызов своего плагина, чтобы у вас было два потока, проходящих через ваш код одновременно? Это может позволить вам увидеть в отладчике немного больше о том, что происходит. Также я считаю, что для статического поля всегда есть только одно значение, независимо от доменов приложений. Возможно, сборка будет выгружена, и в этом случае значение будет потеряно, но вы говорите, что видите два независимых ненулевых значения для поля, что я не думаю, что это возможно. Если не загружены отдельные версии CLR?   -  person Rory    schedule 25.10.2010
comment
Я подтвердил, что в игре задействованы два разных домена приложений; Я дополню вопрос подробностями.   -  person Eric    schedule 25.10.2010
comment
Это был отличный пост В проводнике процессов я вижу, что моя надстройка VSTO делает то же самое: создает домен приложения по умолчанию и домен приложения myplugin.vsto. Есть ли способ узнать, в какой домен приложения загружаются определенные COM-объекты? Либо с помощью отладчика и исходного кода, либо с помощью такого инструмента, как проводник процессов?   -  person    schedule 09.07.2012


Ответы (2)


Ваша статическая переменная определенно не используется совместно между доменами приложений, поэтому то, что вы видите, соответствует ожиданиям, учитывая разные домены приложений.

Думаю, это работает так:

Надстройка VSTO работает в собственном домене приложений. Если фабрика классов COM для вашего объекта кэша (или сервера RTD) создается из этого домена приложений, она будет загружена в вызывающий домен приложений. Последующий доступ к этому COM-классу обнаружит, что он уже загружен в процесс, и будет использовать существующий экземпляр.

Однако, если первая активация запускается самим Excel, например при вызове RTD реализованный .NET COM-объект будет загружен в AppDomain по умолчанию для процесса. У вас нет контроля над этой частью процесса загрузки, если вы не создаете неуправляемую прокладку, поскольку «ваш код» не выполняется, когда происходит загрузка.

Некоторые предложения из моей головы:

  1. Сделайте несколько функций-оберток для вызовов RTD, которые предоставляются вашей надстройкой .NET. Таким образом, вы можете убедиться, что класс RTD загружен перед вызовом Excel Application.RTD для реальной настройки RTD.

  2. Сделайте доступ с RTD-сервера к фактическому кешу с помощью пользовательских функций - таким образом Excel вызовет AppDomain, который имеет реальный кеш, даже если это не текущий AppDomain, где находится RTD-сервер.

  3. Попробуйте получить объект надстройки через Application.AddIns .... есть способ получить фактический COM-объект надстройки и использовать какой-то интерфейс для доступа к кешу ...

  4. Сделайте неуправляемую прокладку (поищите в Интернете «Мастер прокладки COM») для вашего RTD-сервера. Как-нибудь выяснить, как загрузить свой VSTO AppDomain, а затем загрузить RTD-сервер в этот AppDomain.

  5. Посмотрите, есть ли способ загрузить надстройку VSTO в домен приложений по умолчанию. Я не знаю, но, возможно, есть флаг или переключатель, который сообщает «загрузчику систем Microsoft Office» (или как там сейчас называется эта часть) не создавать изолированный домен приложений.

  6. Правильный ответ: используйте Excel-Dna (отказ от ответственности: я разработчик). Он поддерживает ленты, RTD и UDF в вашей управляемой надстройке без регистрации, и все будет помещено в ваш домен приложений надстройки. Это бесплатно, но для переноса ваших материалов потребуется некоторое время и усилия - RTD - это тривиально, но если вы используете много вспомогательных объектов VSTO для доступа к ленте и листам (таблицы и т. Д.), Вам нужно подумать о это немного.

Надеюсь, это даст вам некоторые идеи.

--Говерт--

person Govert    schedule 29.10.2010
comment
У меня та же проблема, что и у исходного плаката: мой надстройка загружается в AppDomain1, а затем, когда Excel встречает ячейку с =RTD и моим прогидом, он создает экземпляр другого AppDomain2, где, конечно, ни одно из состояний, которые я можно найти. Я попытался создать экземпляр (с помощью .NET Activator) моего RTD-сервера при запуске, который работает, и тип создается в моей сборке, но Excel все еще создает другую сборку. Я не уверен, как бы я отнесся к вашим предложениям 1/2/3 - может ли ваш проект Excel-Dna помочь каким-либо образом (где мне искать)? Спасибо! - person rawpower; 01.10.2018

во-первых, вы можете объявить свой экземпляр как:

static DataCache _instance = new DataCache();

таким образом (не только точно) вы знаете, что _instance генерируется потокобезопасным способом. Тема безопасных для потоков синглтонов широко освещается, но это кажется одним из самых простых решений.

Во-вторых, вы можете попробовать использовать такую ​​структуру, как

Lock (_lockObject)
{
...
}

как для чтения, так и для записи. Это сделает ваши чтения и записи безопасными из разных потоков.

Наконец, но это чистая догадка, вы можете попробовать создать отдельный объект для ваших COM-вызовов, который находится в STA и обращается к вашей библиотеке.

Удачи!

person mhttk    schedule 24.10.2010
comment
Извините, это не решает мою проблему. Я отредактировал вопрос, чтобы уточнить, что проходит несколько минут между тем, когда один поток инициализирует экземпляр, а другой поток пытается его использовать. Это не проблема синхронизации потоков, это связано с тем, как работает реализация .NET / COM. - person Eric; 24.10.2010