TN058: Реализация состояния модулей MFC
Примечание |
---|
Следующая техническая заметка не была обновлена со времени сначала была включена в подключенной документации.В результате некоторые процедуры и разделы могут оказаться устаревшей или неверны.Последние новости, рекомендуется поиск раздела процента в подключенном индексу документации. |
Эта техническая примечание описывает реализацию «конструкций состояния модулей» MFC.Понимание реализации состояния модулей критическое для использования библиотек DLL общих MFC из библиотеки DLL (или сервера OLE в процессе).
Прежде чем при чтении эту заметку см. в разделе «управление данные состояния модулей MFC» в Создание новых документов, окон и представлений.Данная статья содержит важные данные и данные занимают обзор в этом разделе.
Общие сведения
3 Типа сведения о состоянии MFC: Состояние модуля, состояние процесса и состояние потока.Иногда эти типы состояния можно объединения.Например, сопоставления маркеров MFC и модуль является локальной и поток локальное.Это дает 2 различных модулей, чтобы иметь различные сопоставления в каждом из них потоков.
Состояние процесса и состояние потока похожи.Эти элементы данных, традиционно глобальные переменные, но есть необходимость, относящиеся к заданным процесс или поток для правильной поддержки Win32s или для правильной поддержки многопоточности.Категория, которой данный элемент данных с размером зависит от которых элемент и его нужную семантику относительно границ процесса и потока.
Состояние модуля уникально в том, что она может содержать по-настоящему глобальное состояние или заявлять, локальное процесса или потока локальное.Кроме того, его можно передать быстро.
Переключение состояния модулей
Каждый поток содержит указатель на «current» или «активное» состояние модуля (не удивительно, указатель составе MFC локального состояния потока).Этот указатель изменяется, когда поток выполнения передает границу вызывающего модуля, например приложение OLE в элемент управления или библиотеки DLL или ЯВЛЯЕТСЯ элемент управления вызывающий обратно в приложение.
Текущее состояние модуля переключено путем вызова AfxSetModuleState.В большинстве случаев никогда не будет работать непосредственно с API.MFC в большинстве случаев без доставки он автоматически (WinMain, на OLE точках входа, AfxWndProc и т д).Это делается в любом компоненте создании статического связывания в специальном WndProc и нерегламентированном WinMain (или DllMain), который знает, состояние модуля должно быть является текущим.Можно просмотреть этот код, выполняя DLLMODUL.CPP или APPMODUL.CPP в каталоге MFC\SRC.
Он редок, что требуется установить состояние, а затем набор модулей обратно.Большую часть времени «отправить» собственное состояние модуля, что и текущий а затем, после того как были внесены,» назад «извлечение исходного часть контекста.Это делается макросом AFX_MANAGE_STATE и специальным классом AFX_MAINTAIN_STATE.
CCmdTarget имеет специальные функции для поддержки переключения состояния модулей.В частности, CCmdTarget корневой класс, используемый для точки входа ole-автоматизации COM и OLE.Предоставляемая как любая другая точка входа в систему, эти точки входа необходимо задать верное состояние модуля.В данной CCmdTarget знает, что «правильное» состояние модуля должно быть?Ответ, что он «запоминает», «текущее состояние модуля» формируется, когда те, что он может задать текущее состояние модуля к этому вспомненное» значение «, когда он вызывается позднее.В результате состояние модуля, что данный объект CCmdTarget связанный с состоянием модуля, которое было запись, если объект был создан.Создание простой пример загрузки сервер INPROC, создать объект, и вызов его методов.
Библиотека DLL загружается с помощью LoadLibrary OLE.
RawDllMain вызывается раньше.Он устанавливает состояние модуля к известному статический состояние модуля для библиотеки DLL.По этой причине RawDllMain статически связана с библиотекой DLL.
Вызывается конструктор для фабрики класса нашим связанной с объектом.COleObjectFactory является производным от CCmdTarget и в результате ее запоминает, в котором состояние модуля него создается.Это важно, если — предложено создает фабрику класса объектов, он знает, что теперь состояние модуля, чтобы сделать текущим.
DllGetClassObject вызывается, чтобы получить фабрику класса.MFC просматривает список фабрики классов, связанный с этим модулем и возвращает его.
COleObjectFactory::XClassFactory2::CreateInstance вызывается.Перед созданием объекта и возвращения ее эта функция устанавливает состояние модуля в состояние модуля, которое было запись в шаге 3 (один, если запись еще не был создан экземпляр COleObjectFactory ).Это делается в METHOD_PROLOGUE.
При создании объекта, это слишком производный CCmdTarget и тем же способом, вспомненное COleObjectFactory, состояние модуля был активен, таким образом делает этот новый объект.Теперь объект знает, состояние модуля для передачи если он вызывается.
Клиент вызывает функцию OLE COM-объект на полученный из вызова CoCreateInstance.Вызывается, когда объект используется METHOD_PROLOGUE для переключения состояния модулей как COleObjectFactory.
Как можно видеть, состояние модуля распространяется от объекта к объекту по мере их создания.Важно иметь состояния модулей.Если он не задан, то существующие библиотеки DLL или плохо COM-объект могут взаимодействовать с приложением MFC, которое вызывает его или невозможно найти собственные ресурсы или могут завершиться ошибкой в других горемычных способами.
Обратите внимание, что некоторые виды библиотек DLL, в частности «библиотека DLL расширения MFC» не переключают состояние модуля в их RawDllMain (фактически, они, как правило, даже не имеет RawDllMain).Это происходит потому, что они предназначены вести себя «если» они фактически присутствовали в приложении, которое использует их.Они много часть приложения, которое выполняется и их на намерение изменить состояние приложения глобальное.
ЯВЛЯЕТСЯ элементы управления и другие библиотеки DLL очень различаются.Они не хотят изменения состояния вызывающего приложения; приложение, вызывающее они не даже может быть приложением MFC и поэтому не может быть ни состоянием, который требуется изменить.Это причина в том, что переключение состояния модулей было изобретен.
Для экспортированных функций из библиотеки DLL, как одно, которое запускает диалоговое окно в библиотеке DLL, необходимо добавить следующий код в начало функции:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
Это не будет передавать текущее состояние модуля с состояние, возвращенное AfxGetStaticModuleState до конца текущей области.
Проблем с ресурсами в библиотеке DLL будет выполняться, если макрос AFX_MODULE_STATE не используется.По умолчанию MFC использует дескриптор ресурса основного приложения, чтобы загрузить шаблон ресурса.Этот шаблон фактически хранится в библиотеке DLL.Исходной причиной исключения, что сведения о состоянии модуля MFC не было переключено макросом AFX_MODULE_STATE.Дескриптор ресурса взят из состояния модулей MFC.Переключение состояния модуля не приводит к тому, что неверный дескриптор ресурса.
AFX_MODULE_STATE не требуется, помещаемая в каждую функцию в dll-библиотеке.Например, InitInstance может быть вызван кодом MFC в приложении без AFX_MODULE_STATE поскольку MFC автоматически сдвигает состояние модуля перед InitInstance а затем переключателями обратно после InitInstance.Это также верно для всех обработчиков сопоставления сообщения.Обычные библиотеки DLL фактически имеют особый главной процедуре окна, которая автоматически переключается состояние модуля перед маршрутизация сообщения.
Процесс локальные данные
Процесс локальные данные не были бы одного огромного беспокойства, которые не будут иметь их сложности модели для библиотеки DLL, Win32s.Во всех библиотеках DLL Win32s совместно использовать их глобальные данные, даже когда загружается несколькими приложениями.Это очень «отличается от реальной» модель данных Win32 DLL, где каждое библиотеки DLL получает отдельную копию пространство данных в любом процессе, вложение на библиотеку DLL.Чтобы добавить к сложности данные, выбранные в куче в библиотеке DLL Win32s в действительности процесса, относящийся (по крайней мере, сколько владение проходит).Рассмотрим следующие данные и код:
static CString strGlobal; // at file scope
__declspec(dllexport)
void SetGlobalString(LPCTSTR lpsz)
{
strGlobal = lpsz;
}
__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
StringCbCopy(lpsz, cb, strGlobal);
}
Рассмотрим, что произойдет, если приведенный выше код внутри найден в библиотеке DLL и библиотека DLL загружается 2 процессами a и B (в действительности может быть 2 экземпляров одного и того же приложения).Вызовы SetGlobalString("Hello from A").В результате память выделяется для данных CString в контексте а. процесса.Имейте в виду, что CString является глобально и отображается, как a, так и B.Теперь вызовы GetGlobalString(sz, sizeof(sz)) б.Б будет просмотреть данные, а.Это происходит потому, что Win32s не обеспечивает никакой защиты между процессами как Win32.Первая проблема; в большинстве случаев не требуется иметь один глобальный влияют на заданный по приложения, считается, что принадлежат другим приложением.
Дополнительные проблемы.Пусть теперь a ".При a ", память, используемая строкой «strGlobal» становится доступным для системы - то есть, вся память, выделенная by обрабатывает a освобождается автоматически операционной системой.Она не освобождается, так как вызывать деструктор CString; она еще не была вызвана.Он освобождается просто, поскольку приложение, оно остается выделенной имеет сцену.Теперь, если б вызвало GetGlobalString(sz, sizeof(sz)), он не может получить допустимые данные.Другое приложение может использовать эту память для действия else.
Очистить проблема существует.MFC 3.x использовал метод с именем поток-местный хранением (TLS).MFC 3.x выделитьTm бы индекс TLS, фактически выступает в качестве процесс-местный под Win32s индекс расположения, даже если не вызывается, то а затем реализованный бы на все данные на основе этого индекса TLS.Это похоже на индекс TLS, который использовался для хранения поток-местные данные в Win32 (дополнительные сведения см. далее в этой теме).Это вызвало каждое библиотеки DLL MFC использовать хотя бы 2 индекса TLS в процесс.При указании загрузка множества ЯВЛЯЕТСЯ библиотека DLL элемента управления (OCXs), можно быстро запустить из индексов TLS (доступные только 64).Кроме того, MFC должен задать все эти данные в одном месте в одной структуре.Он не был очень является расширяемым и не был в идеале, касающиеся использования индексов TLS.
Это устраняет MFC 4.x с использованием шаблонов класса «можно создавать программу-оболочку» вокруг данных, которые должны быть процессом локальным.Например, проблема упомянутая выше может быть исправлена написать:
struct CMyGlobalData : public CNoTrackObject
{
CString strGlobal;
};
CProcessLocal<CMyGlobalData> globalData;
__declspec(dllexport)
void SetGlobalString(LPCTSTR lpsz)
{
globalData->strGlobal = lpsz;
}
__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, size_t cb)
{
StringCbCopy(lpsz, cb, globalData->strGlobal);
}
MFC реализует это в шагах 2.Во-первых, уровень поверх API Win32 Tls* (TlsAlloc, TlsSetValue, TlsGetValue и т д), которые используют только 2 индекса TLS в процесс, независимо от того, как много библиотеки DLL имеется.Во-вторых, этот доступа к шаблон CProcessLocal эти данные.Он переопределяет operator->, что позволяет интуитивный синтаксис отображается выше.Все объекты, которые создает программу-оболочкуы CProcessLocal должен быть производным от CNoTrackObject.CNoTrackObject предоставляет механизм распределения низк-уровня (LocalAlloc/LocalFree) и виртуальный деструктор те, что MFC может автоматически удалить локальные процессов объекты, когда процесс завершен.Такие объекты могут иметь пользовательский деструктор если дополнительная очистка не требуется.В предыдущем примере не требует одного, так как компилятор создаст по умолчанию деструктор, чтобы удалить внедренный объект CString.
Другие интересные преимущества к этому подходу.Не только не создаются все объекты уничтожаются CProcessLocal автоматически, они до тех пор, пока они не являются обязательными.CProcessLocal::operator-> равна создать связанный объект при первом вызове и нет быстрее.В приведенном выше примере, означает, что строка «strGlobal» не будет построена до впервые SetGlobalString или GetGlobalString вызывается.В некоторых случаях это может помочь сократить время запуска DLL.
Сведения о потоке локальные
Аналогично обработке локальных данных, поток локальные данные используются, когда данные должны находиться в данный поток.Иными словами, необходим отдельный экземпляр данных для каждого потока, который обращается к этим данным.Это можно несколько раз использовать вместо основные механизмы синхронизации.Если данные не нужно совместно использоваться несколькими потоками, то эти механизмы могут быть дорогими и ненужными.Допустим, мы имело объект CString (подобно пример выше).Мы можем сделать ее действиях потоков локальное путем создания программу-оболочку его с шаблоном CThreadLocal:
struct CMyThreadData : public CNoTrackObject
{
CString strThread;
};
CThreadLocal<CMyThreadData> threadData;
void MakeRandomString()
{
// a kind of card shuffle (not a great one)
CString& str = threadData->strThread;
str.Empty();
while (str.GetLength() != 52)
{
unsigned int randomNumber;
errno_t randErr;
randErr = rand_s( &randomNumber );
if ( randErr == 0 )
{
TCHAR ch = randomNumber % 52 + 1;
if (str.Find(ch) < 0)
str += ch; // not found, add it
}
}
}
Если MakeRandomString был вызван из 2 различных потоков, то каждое зашаркало «» строка в другой способ без мешать с другим.Это происходит потому, что виртуальный экземпляр strThread в поток вместо глобального только одного экземпляра.
Обратите внимание, что ссылка используется для получения адреса CString раз, а не один раз в итерацию цикла.Используется код цикла может быть записан с threadData->strThread в любом месте «str», однако код будет гораздо медленнее при выполнении.Лучше поместить ссылку в кэше к данным при возникновении подобных ссылок в циклах.
Шаблон класса CThreadLocal использует те же механизмы, что CProcessLocal и те же методы реализации.