В предыдущих уроках по API КОМПАС (Напоминаем, что в качестве среды используется C++ Builder) Основы и Оформление чертежа мы исходили из того, что КОМПАС не запущен, и запускали его сами методом CreateInstance. Но что если в момент вызова этого метода КОМПАС уже запущен? Тогда будет создан еще один экземпляр данной программы. В принципе ничего страшного, но неаккуратно. Зачем плодить копии программы, когда можно обойтись одним экземпляром?
Сегодня мы поговорим о том, как подключаться к уже запущенному КОМПАС, чтобы не создавать картину, похожую на ту, которая изображена на рисунке ниже.
Запущено несколько экземпляров КОМПАС-3D
Подключение к КОМПАС
Для подключения к программе КОМПАС используется метод ActiveInstance. Ниже приводится пример программы, подключающейся к КОМПАС.
KompasObjectPtr kompas;
kompas.ActiveInstance(L"KOMPAS.Application.5");
//Делаем КОМПАС видимым
kompas->Visible = true;
kompas.Unbind();
Единственный параметр метода ActiveInstance – строковое наименование интерфейса, к которому мы подключаемся, в кодировке Unicode.
А что будет, если в момент вызова этого метода КОМПАС не запущен? Произойдет ошибка. Причем пользователю будет показано сразу два окна с ругательствами.
Первое окно с ошибкой
Второе окно с ошибкой
Формат этих окон зависит от используемой среды разработки и может отличаться от приведенных выше. Оборачивание вызова метода ActiveInstance в блок try/catch, к сожалению, не решает проблему.
try{
KompasObjectPtr kompas;
kompas.ActiveInstance(L"KOMPAS.Application.5");
//Делаем КОМПАС видимым
kompas->Visible = true;
kompas.Unbind();
}catch(...){}
Мы избавляемся только от одного окна с ошибкой. Получается, что вызывать метод ActiveInstance можно только тогда, когда КОМПАС запущен. И здесь мы приходим к главному вопросу: как определить, запущен КОМПАС или нет? Для этого существует несколько методов.
По главному окну
Самый распространенный способ – с помощью функции FindWindow найти главное окно программы. Функция FindWindow ищет окно по его заголовку или по наименованию его оконного класса. К сожалению, она требует точного совпадения строк. Искать по части строки она не умеет.
Заголовок окна мы не можем использовать, так как он включает в себя версию КОМПАС, которая заранее нам неизвестна, и дополнительную информацию, например, наименование открытого документа. Поэтому мы не можем знать наверняка точную строку в заголовке окна. Попробуем по наименованию оконного класса.
Узнать его можно с помощью программы Spy++. На рисунке ниже приведена примерная информация об оконном классе (наименование обведено в красный прямоугольник).
Окно «Свойства окна»
Наименования оконного класса мало того, что не отличаются наглядностью, так еще и различаются в разных версиях КОМПАС. Поэтому поиск по наименованию оконного класса нам также не подходит.
Единственное, на что мы хоть как-то можем положиться, так это на наличие подстроки «КОМПАС-3D» в заголовке главного окна. Но функция FindWindow не умеет искать по подстроке. Единственное, что нам остается, так это перебрать все окна верхнего уровня и проверить, содержит ли заголовок какого-либо из них подстроку «КОМПАС-3D». Ниже приводится исходный код процедуры, осуществляющей эту проверку.
//Функция обратного вызова для поиска окна
bool CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
//Получаем размер заголовка окна
unsigned int size;
size = GetWindowTextLength(hwnd);
if(!size) return true;
//Подготавливаем буфер под заголовок окна
wchar_t* pbuffer;
pbuffer = (wchar_t*)malloc(sizeof(wchar_t)*(size+1));
//Читаем заголовок окна
GetWindowTextW(hwnd, pbuffer, size-1);
//Ищем подстроку
wchar_t *p;
p = wcsstr(pbuffer, L"КОМПАС-3D");
//освобождаем память
free(pbuffer);
if(!p) return true;
//Окно найдено, сохраняем результат и останавливаем поиск
bool *pres;
pres = (bool*)lParam;
*pres = true;
return false;
}
//Функция проверки запущен ли КОМПАС?
bool IsKOMPASRun()
{
bool res = false;
EnumWindows((WNDENUMPROC)EnumWindowsProc, (LPARAM)(&res));
return res;
}
Для перечисления окон используется функция EnumWindows, входящая в состав Windows API. Данная функция подготавливает список окон верхнего уровня и для каждого найденного окна вызывает пользовательскую функцию EnumWindowsProc. В данной функции мы читаем заголовок найденного окна и проверяем, содержит ли он подстроку «КОМПАС-3D». Если содержит, то окно найдено и останавливаем поиск, если нет – переходим к следующему окну. Ниже приводится пример использования данной процедуры.
if(IsKOMPASRun())
ShowMessage("КОМПАС запущен");
else
ShowMessage("КОМПАС не запущен");
К сожалению, данный метод очень ненадежен. Дело в том, что в системе могут быть другие окна, содержащие в своем заголовке подстроку «КОМПАС-3D». Пример такого окна приведен на рисунке 4. Это явно не то окно, которое мы хотим найти.
Окно справки
По процессу
Другим вариантом является поиск процесса КОМПАС. У программы КОМПАС исполняемый файл обычно именуется «kompas.exe» (в разных версиях может различаться регистр). Что если вместо окна искать процесс с таким наименованием исполняемого файла? Ниже приводится исходный текст программы, реализующей данный метод.
bool IsKOMPASRun()
{
//Имя исполняемого файла, который мы ищем
char ExeName[] = "kompas.exe";
size_t lenName = strlen(ExeName);
//Делаем снимок системы
HANDLE hSnapshot;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
//Перечисляем процессы
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
Process32First(hSnapshot, &entry);
size_t len;
bool res = false;
do{
//Сравниваем наименования исполняемых файлов
len = strlen(entry.szExeFile);
if(len != lenName) continue;
if(!strnicmp(entry.szExeFile, ExeName, len))
{
res = true;
break;
}
}while(Process32Next(hSnapshot, &entry));
CloseHandle(hSnapshot);
return res;
}
В данном примере для перечисления процессов используются функции CreateToolhelp32Snapshot, Process32First, Process32Next, входящие в состав Windows API. Для их использования необходимо подключить заголовочный файл tlhelp32.h. Это не единственный способ перечисления процессов, но один из самых простых. Для сравнения строк используется функция strnicmp, которая сравнивает строки без учета регистра символов.
Способ, основанный на поиске процессов, надежнее поиска окна. Он дает значительно меньше ложных срабатываний, но не исключает их. Что если на компьютере пользователя запущена программа с точно таким же наименованием исполняемого файла? Или в последующих версиях КОМПАС исполняемый файл будет называться по-другому? Во всех этих случаях данный метод не работает.
С использованием технологии COM
Все описанные выше методы громоздки и ненадежны. Поэтому их использование нежелательно. Существует более надежный способ проверки факта запуска программы КОМПАС. Он основан на функциях библиотеки ole32.dll, которая входит в состав Windows и реализует технологию COM. Ниже приводится исходный код процедуры, проверяющей с помощью этих функций, запущен ли КОМПАС.
bool IsKOMPASRun()
{
wchar_t ObjectName[] = L"KOMPAS.Application.5";
//Инициализируем библиотеку Ole32.dll
CoInitialize(NULL);
CLSID clsid;
//Получаем clsid объекта
CLSIDFromProgID(ObjectName, &clsid);
//Пытаемся подключиться
HRESULT res;
IUnknown *pIUnknown;
res = GetActiveObject(clsid, NULL, &pIUnknown);
if(res == S_OK)
{
pIUnknown->Release();
return true;
}
return false;
}
Основную работу выполняет функция GetActiveObject. С ее помощью мы пытаемся подключиться к КОМПАС. Если это удалось, значит, КОМПАС запущен. Функция CLSIDFromProgID используется для конвертирования строкового представления объекта в его CLSID (уникальный 128-битный идентификатор). Ее можно использовать для проверки того, установлен КОМПАС на компьютере пользователя или нет. Ниже приводится пример программы, реализующей такую проверку.
bool IsKOMPASInstalled()
{
wchar_t ObjectName[] = L"KOMPAS.Application.5";
//Инициализируем библиотеку Ole32.dll
CoInitialize(NULL);
CLSID clsid;
//Получаем clsid объекта
HRESULT res;
res = CLSIDFromProgID(ObjectName, &clsid);
return (res == S_OK);
}
Корректное подключение
Ниже приводится исходный текст программы, реализующей корректное подключение к КОМПАС.
wchar_t ObjectName[] = L"KOMPAS.Application.5";
……………………………………………………………
if(! IsKOMPASInstalled())
{
ShowMessage("КОМПАС не установлен");
return;
}
KompasObjectPtr kompas;
if(IsKOMPASRun())
kompas.ActiveInstance(ObjectName);
else
kompas.CreateInstance(ObjectName);
kompas->Visible = true;
kompas.Unbind();
Переменная ObjectName объявлена глобальной, чтобы избежать ее дублирования в разных функциях.
Заключение
В данной статье были рассмотрены различные методы определения того, запущен КОМПАС или нет. Вы можете использовать любой из этих методов или их комбинацию. А может быть, придумаете свой метод. Самым надежным является метод, основанный на функции GetActiveObject.
В конце статьи приведен пример программы, реализующей подключение к КОМПАС с проверкой на его наличие в системе и работу в момент запуска программы.
Продолжение следует, следите за новостями блога.
Сергей Норсеев, автор книги «Разработка приложений под КОМПАС в Delphi».
Автор: АСКОН