Показаны сообщения с ярлыком COM. Показать все сообщения
Показаны сообщения с ярлыком COM. Показать все сообщения

понедельник, 27 октября 2008 г.

Про создание COM-объектов без регистрации DLL в системе

Как известно, чтобы создать некий COM-объект, надо прежде всего зарегистрировать DLL в которой этот объект реализован. И делается это примерно так

regsvr32.exe SomeCOMObjects.dll

Дальше можно вызывать CoCreateInstance, получать экземпляр объекта и работать с ним.

Однако, иногда возникает желание, пользовать объекты не регистрируя библиотеку, например, когда хочется получить portable версию программы. Один способ я описывал вот здесь. Но к сожалению на Vista этот вариант у меня не прошел или я просто не слишком активно пытался.

Второй вариант создавать объект, загружая соответствующую длл-ку при помощи функции LoadLibrary. Выглядит это следующим образом:

показать код

class CCOMDll
{
typedef HRESULT (__stdcall *DllGetClassObjectProc)(REFCLSID rclsid, REFIID riid, LPVOID * ppvObj);
public :
CCOMDll()
: m_sDLLFilePath(_T(""))
, m_hLib(NULL)
, m_pDllGetClassObjectProc(NULL)
, m_pDllCanUnloadNowProc(NULL)
{
}
virtual ~CCOMDll()
{
if (NULL!=m_hLib)
::FreeLibrary(m_hLib);
}
BOOL LoadLibrary(const CString &sDLLFilePath)
{
m_sDLLFilePath = sDLLFilePath;
m_hLib = ::LoadLibrary(m_sDLLFilePath);

m_pDllGetClassObjectProc = (DllGetClassObjectProc)::GetProcAddress(m_hLib, "DllGetClassObject");
return (NULL!=m_hLib);
}
BOOL IsValid() const
{
return (NULL!=m_hLib);
}
//
HRESULT CoCreateInstance(REFCLSID rclsid, REFIID riid, LPVOID* ppvObj)
{
*ppvObj = NULL;
if (NULL==m_pDllGetClassObjectProc)
return S_FALSE;

IClassFactory *pFactory = NULL;
HRESULT hr = (m_pDllGetClassObjectProc)(rclsid, IID_IClassFactory, (void**)&pFactory);
if (S_OK!=hr)
return hr;

hr = pFactory->CreateInstance(NULL, riid, ppvObj);
if (NULL!=pFactory)
pFactory->Release();
return hr;
}
protected :
CString m_sDLLFilePath;
HINSTANCE m_hLib;

DllGetClassObjectProc m_pDllGetClassObjectProc;
};



Понятно, что если делать все по уму надо кой-какую дополнительную обвязку дописать, и выгружать длл-ку тоже следует с осторожностью. Так же не шибко хорошо, когда внутри одного объекта используются объекты из другой библиотеки, которая тоже не зарегистрирована. Т.е. вариант не особо практичный, но в принципе, может кому и пригодится.

воскресенье, 1 июня 2008 г.

Про регистрацию COM объектов при помощи манифестов

Одна из проблем возникающая при попытке сделать "portable" версию некоторой программы, это использование основным exe-файлом COM-объектов.

COM-объекты обычно реализуются в виде dll-файлов, которые регистрируются в системе примерно так:

regsvr32.exe SomeCOMObjects.dll

На самом деле вся регистрация сводится к прописыванию в реестре ключей со списком GUID-ов и ссылками на путь к SomeCOMObjects.dll.

Понятно, что теперь, чтобы программа заработала на компьютере отличном от того, где она установлена, не достаточно утащить туда только exe-файл. В рамках нашей задачи создания portable версии программы, которую можно будет носить на внешнем винчестере или Flash Drive, неплохим вариантом решения будет следующие действия:

1. На внешнем носителе создаем папку для программы и копируем туда, непосредственно exe-файл и все используемые им dll-файлы с COM-объектами.

2. Создаем два bat-файла (или, например, два vbs-файла): COMDllRegister.bat и COMDllUnregister.bat в первом для каждой dll прописываем

regsvr32.exe SomeCOMObjects.dll

во втором

regsvr32.exe /u SomeCOMObjects.dll

Приходя на новый компьютер, запускаем вначале COMDllRegister.bat, потом саму программу, работаем, а перед уходом запускаем COMDllUnregister.bat. Если кроме COM-объектов никаких других привязок в системе у программы нет, то все работает замечательно.

Какие минусы у данного способа?

Минусы очевидны. Если в системе уже были зарегистрированы COM-объекты используемые программой, с которой Вы работаете (известно, что COM это в том числе и способ разделения кода и многие программы используют одни и те же COM-объекты, например, DirectX это исключительно набор COM-компонентов). То при запуске первого bat-файла Вы перенаправите пути с Dll-файлов, расположенных на компьютере, на Dll-файлы на вашем внешнем диске, а после запуска второго, эти COM-объекты из системы исчезнут, что может отрицательно сказаться на работоспособности программ на этом компьютере.

Понятно, что есть варианты решения данной проблемы, например, осуществлять проверку, не зарегистрированы ли уже GUID-ы в реестре, и если да то не регистрировать их, и не разрегистрировать те Dll, которые регистрировали не Вы. Все это понятно, и в принципе, реализуемо.

Но есть способ лучше. Он подробно, с примерами, расписан в статье Registration-Free Activation of COM Components: A Walkthrough.

Суть этого способа сводится к тому, чтобы вместо регистрации COM-объектов в реестре системы, использовать manifest-файлы. Таким образом, в том числе можно использовать в программе вместо dll-зарегистрированной в системе свою.

Итак, что надо сделать.

1. Для каждой dll, содержащей COM-объекты надо создать manifest-файл. Можно это сделать вручную (предварительно придется зарегистрировать dll, а затем воспользоваться утилитой OLE/COM Object viewer), можно воспользоваться утилитой mt.exe из Visual Studio 2005. В результате должен получится набор manifest-файлов примерно такого содержания


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="STDUCore.X" version="1.0.0.0"/>
<file name="STDUCore.dll">
<comClass
clsid="{2BB2E135-4B81-4840-B7CD-A744DD236AB0}"
threadingModel = "Apartment"/>
<comClass
clsid="{1740E2A8-ACF7-4930-B6DF-5D0C85E874DA}"
threadingModel = "Apartment"/>
<typelib tlbid="{A11D2AA5-3D39-448E-B9D0-177A73707C98}" version="1.0" helpdir=""/>
</file>
<comInterfaceExternalProxyStub
name="ISTDUImage"
iid="{3905360D-3C87-498C-93D2-27E002B941B1}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid="{A11D2AA5-3D39-448E-B9D0-177A73707C98}"/>
<comInterfaceExternalProxyStub
name="ISTDUTransform"
iid="{721B21ED-7BCD-4127-9EFF-6C06AA95FE31}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid="{A11D2AA5-3D39-448E-B9D0-177A73707C98}"/>
</assembly>



2. Необходимо создать manifest для основной программы, он будет выглядеть как-то так


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type = "win32" name = "client" version = "1.0.0.0" />
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUCore.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUDjVuFile.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUPDFFile.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUTiffFile.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="STDUViewer.X" version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>


STDUCore.X, STDUViewer.X и т.п. это имена файлов manifest для соответствующих dll.

3. Теперь собираем все dll-файлы, основную программу, и manifest-файлы в одну директорию - "portable" версия программы готова.

Выше в качестве примера используются куски manifest-файлов для "portable" версии STDU Viewer



Какие минусы у данного способа?

Во-первых, не всегда возможно определить, какие COM-объекты из каких dll-файлов использует конкретная программа. Во-вторых, мне так и не удалось заставить работать этот способ под Windows Vista, что скорее всего это связано с UAC (но нельзя сказать, что я сильно старался). И, наконец, создание manifest-файлов в ручном режиме, для серьезной программы, мягко говоря, работа не простая, и не шибко веселая.