СОМ-объект без регистрации в реестре
Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain
Всем привет!
У меня такой вопрос: как мне использовать DLL-модуль, в котором реализован некий СОМ-объект, но таким образом, чтобы этот модуль не регистрировать в системе, а непосредственно самому его загружать из указанного каталога и обращаться к его интерфейсам?
Для чего мне это нужно: чтобы реализовать набор плагинов для основного приложения. Каждый DLL-модуль реализует однотипные функции (к примеру: формирование и печать того или иного документа или получение исходных данных из различных источников).
У меня такой вопрос: как мне использовать DLL-модуль, в котором реализован некий СОМ-объект, но таким образом, чтобы этот модуль не регистрировать в системе, а непосредственно самому его загружать из указанного каталога и обращаться к его интерфейсам?
Для чего мне это нужно: чтобы реализовать набор плагинов для основного приложения. Каждый DLL-модуль реализует однотипные функции (к примеру: формирование и печать того или иного документа или получение исходных данных из различных источников).
- Romeo
- Сообщения: 3126
- Зарегистрирован: 02 мар 2004, 17:25
- Откуда: Крым, Севастополь
- Контактная информация:
Если COM, то только регистрация, иначе никак.
Для того, чтобы реализовать поддержку плагинов, кстати, чаще всего не используют COM, а просто стандартизируют интерфейс DLL строгим набором функций, доступных в export секции. Каждый из плагинов обязуется эти функции отимплементить. Приложение загружает плагин динамически (LoadLibrary), затем получает указатели на функции этого плагина (GetProcAddress), и вызывает эти функции.
На COM тоже можно реализовать поддержку плагинов, но это сложнее. Для COM используется такой подход. Каждая DLL, во время регистрации дополнительно производит запись в какое-нибудь место в реестре, создаёт там новый entry и заносит в него своё имя и CLSID кокласса, который реализует функциональность. Все коклассы должны имплементировать один и тот же интерфейс. Приложение, зная место, где зарегистрировались все плагины, открывает entry, читает все его subentry, вызывая для каждого каждого вычитанного CLSID функцию CoCreateInstance (либо ёё аналог-враппер) и затем делая QueryInterface общего для всех интерфейса.
Для того, чтобы реализовать поддержку плагинов, кстати, чаще всего не используют COM, а просто стандартизируют интерфейс DLL строгим набором функций, доступных в export секции. Каждый из плагинов обязуется эти функции отимплементить. Приложение загружает плагин динамически (LoadLibrary), затем получает указатели на функции этого плагина (GetProcAddress), и вызывает эти функции.
На COM тоже можно реализовать поддержку плагинов, но это сложнее. Для COM используется такой подход. Каждая DLL, во время регистрации дополнительно производит запись в какое-нибудь место в реестре, создаёт там новый entry и заносит в него своё имя и CLSID кокласса, который реализует функциональность. Все коклассы должны имплементировать один и тот же интерфейс. Приложение, зная место, где зарегистрировались все плагины, открывает entry, читает все его subentry, вызывая для каждого каждого вычитанного CLSID функцию CoCreateInstance (либо ёё аналог-враппер) и затем делая QueryInterface общего для всех интерфейса.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Модуль с СОМ-объектом можно и не регистрировать в системе.
Тогда для создания экземпляра объекта вместо вызова функции CoCreateInstance() придётся сделать следующее:
1. Загрузить модуль с помощью функции LoadLibrary()
2. С помощью функции GetProcAddress() получить указатель на функцию DllGetClassObject
3. Через функцию DllGetClassObject получить указатель на интерфейс фабрики классов IClassFactory
4. В интерфейсе IClassFactory вызвать метод CreateInstance
Далее всё как в обычном СОМ-объекте...
Но дело в том, что при создании экземпляра объекта потребуется GUID ко-класса.
А поскольку у каждого модуля будет свой GUID и его значение тебе заранее неизвестно, то тебе в каждой DLL-ке нужно будет дополнительно экспортировать функцию, возвращающую GUID ко-класса. Например: DllGetClassID().
Ещё потребуется GUID самого интерфейса, методы которого тебе придётся вызывать.
Так-как каждый модуль будет имплементировать один и тот же интерфейс, то его GUID во всех модулях должен быть одинаковым. Можешь просто фрагмент файла .idl с описанием интерфейса копировать из одного DLL-проекта и добавлять его в другой DLL-проект.
Ещё лучше реализовать двойственный интерфейс, тогда можно будет вызывать методы через IDispatch. В этом случае GUID самого интерфейса уже не будет иметь значения, но потребуется соответствующий класс-обёртка для вызова методов через IDispatch. В проектах MFC такие классы-обёртки генерирует визард из библиотеки типов СОМ-объекта.
Тогда для создания экземпляра объекта вместо вызова функции CoCreateInstance() придётся сделать следующее:
1. Загрузить модуль с помощью функции LoadLibrary()
2. С помощью функции GetProcAddress() получить указатель на функцию DllGetClassObject
3. Через функцию DllGetClassObject получить указатель на интерфейс фабрики классов IClassFactory
4. В интерфейсе IClassFactory вызвать метод CreateInstance
Далее всё как в обычном СОМ-объекте...
Но дело в том, что при создании экземпляра объекта потребуется GUID ко-класса.
А поскольку у каждого модуля будет свой GUID и его значение тебе заранее неизвестно, то тебе в каждой DLL-ке нужно будет дополнительно экспортировать функцию, возвращающую GUID ко-класса. Например: DllGetClassID().
Ещё потребуется GUID самого интерфейса, методы которого тебе придётся вызывать.
Так-как каждый модуль будет имплементировать один и тот же интерфейс, то его GUID во всех модулях должен быть одинаковым. Можешь просто фрагмент файла .idl с описанием интерфейса копировать из одного DLL-проекта и добавлять его в другой DLL-проект.
Ещё лучше реализовать двойственный интерфейс, тогда можно будет вызывать методы через IDispatch. В этом случае GUID самого интерфейса уже не будет иметь значения, но потребуется соответствующий класс-обёртка для вызова методов через IDispatch. В проектах MFC такие классы-обёртки генерирует визард из библиотеки типов СОМ-объекта.
Спасибо за информацию. Но как мне на практике это применить?
Как генерировать классы-обёртки для IDispatch и затем использовать их?
Как на примере самому загрузить какой-нибудь СОМ-объект без использования CoCreateInstance()?
Буду благодарен за помощь.
Как генерировать классы-обёртки для IDispatch и затем использовать их?
Как на примере самому загрузить какой-нибудь СОМ-объект без использования CoCreateInstance()?
Буду благодарен за помощь.
Если желаешь потренироваться в создании классов-обёрток, то можешь выполнить следующее упражнение...
1. Создай проект консольного приложения Win32 с включением библиотек MFC и ATL. Назови его для примера ShellApp.
2. Выбери пункт меню "Project -> Add class..." и в появившемся окне диалога выбери пункт "MFC Class from TypeLib".
3. В следующем диалоге переключись на "File" и выбери из каталога Windows\System32 модуль "Shell32.dll"
4. В левом списке объектов выбери интерфейс IShellDispatch и добавь его в правый список. Потом нажми кнопку "Finish".
5. Открой созданный визардом файл CShellDispatch.h и закомментируй в нём (или удали) строку с директивой #import, которая будет находиться в самом начале файла.
6. Далее в исходный код проекта добавь такие строки...
После этого скомпилируй проект и запусти приложение. В результате на экране должны свернуться все окна и запуститься редактор "Блокнот".
Желаю удачи...
1. Создай проект консольного приложения Win32 с включением библиотек MFC и ATL. Назови его для примера ShellApp.
2. Выбери пункт меню "Project -> Add class..." и в появившемся окне диалога выбери пункт "MFC Class from TypeLib".
3. В следующем диалоге переключись на "File" и выбери из каталога Windows\System32 модуль "Shell32.dll"
4. В левом списке объектов выбери интерфейс IShellDispatch и добавь его в правый список. Потом нажми кнопку "Finish".
5. Открой созданный визардом файл CShellDispatch.h и закомментируй в нём (или удали) строку с директивой #import, которая будет находиться в самом начале файла.
6. Далее в исходный код проекта добавь такие строки...
Код: Выделить всё
#include "stdafx.h"
#include "ShellApp.h"
[b]#include "CShellDispatch.h"[/b]
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
[b]::CoInitialize(NULL);[/b]
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
[b]CShellDispatch shell;[/b]
[b]shell.CreateDispatch(_T("Shell.Application"));[/b]
[b]shell.MinimizeAll();[/b]
[b]shell.Open(CComVariant(L"C:\\WINDOWS\\Notepad.exe"));[/b]
}
[b]::CoUninitialize();[/b]
return nRetCode;
}
Желаю удачи...
У меня получилось. Спасибо, WinMain!
Ну а как мне теперь попробовать загрузить СОМ-объект напрямую из DLL?
Ну а как мне теперь попробовать загрузить СОМ-объект напрямую из DLL?
Для чистоты эксперимента сделай так:
1. Скопируй модуль SHELL32.DLL в рабочую папку своего проекта ShellApp и переименуй его. К примеру, назови его "MyShell.dll", как-будто это твой модуль.
2. В исходном коде проекта замени текст...
3. Скомпилируй проект и запусти приложение. В результате так же должны свернуться все окна и запуститься "Блокнот".
Дерзай, Decoder...
1. Скопируй модуль SHELL32.DLL в рабочую папку своего проекта ShellApp и переименуй его. К примеру, назови его "MyShell.dll", как-будто это твой модуль.
2. В исходном коде проекта замени текст...
Код: Выделить всё
#include "stdafx.h"
#include "ShellApp.h"
#include "CShellDispatch.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CWinApp theApp;
using namespace std;
[b]typedef HRESULT (WINAPI *PFNDLLGETCLASSOBJECT)(REFCLSID, REFIID, LPVOID*);[/b]
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
::CoInitialize(NULL);
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
// Загрузка модуля...
[b]HMODULE hModule = ::LoadLibrary(_T("MYSHELL.DLL"));[/b]
[b]if (hModule != NULL)[/b]
{
[b]CLSID cls;[/b]
[b]::CLSIDFromString(L"{13709620-C279-11CE-A49E-444553540000}", &cls);[/b]
[b]PFNDLLGETCLASSOBJECT GetClassObject = [/b]
[b](PFNDLLGETCLASSOBJECT)::GetProcAddress(hModule, "DllGetClassObject");[/b]
[b]CComPtr<IClassFactory> pCF;[/b]
[b]GetClassObject(cls, IID_IClassFactory, (LPVOID*)&pCF);[/b]
[b]LPDISPATCH pDisp(NULL);[/b]
[b]HRESULT hr = pCF->CreateInstance(NULL, IID_IDispatch, (LPVOID*)&pDisp);[/b]
[b]if (hr == S_OK && pDisp != NULL)[/b]
{
[b]CShellDispatch shell(pDisp);[/b]
shell.MinimizeAll();
shell.Open(CComVariant(L"C:\\WINDOWS\\Notepad.exe"));
}
[b]} else[/b]
{
[b]ATLASSERT (hModule != NULL);[/b]
}
// Выгрузка модуля...
[b]if (hModule != NULL)[/b]
{
[b]::FreeLibrary(hModule);[/b]
}
}
::CoUninitialize();
return nRetCode;
}
Дерзай, Decoder...
Фантастика!!! А ведь реально работает...
WinMain, а как мне теперь в своём модуле сделать экспорт функции, возвращающую GUID ко-класса? А ещё лучше было бы сделать так, чтобы GUID вообще не требовался...
WinMain, а как мне теперь в своём модуле сделать экспорт функции, возвращающую GUID ко-класса? А ещё лучше было бы сделать так, чтобы GUID вообще не требовался...
Добавить экспортируемую функцию в DLL-модуль совсем несложно.
Чтобы вернуть GUID ко-класса из DLL, необходимо в файл к уже имеющимся экспортируемым функциям добавить ещё одну.
Предположим, у тебя в DLL реализован некий интерфейс IHelloWorld...
В твоём коде это примерно так будет выглядеть:
В файл с расширением .def нужно будет дописать имя этой функции.
Можно пойти по другому пути и не экспортировать никаких дополнительных функций, а создать в рабочем каталоге INI-файл, к примеру Plugins.ini и указать там для каждого модуля GUID его ко-класса.
Например так:
Чтобы вернуть GUID ко-класса из DLL, необходимо в файл к уже имеющимся экспортируемым функциям добавить ещё одну.
Предположим, у тебя в DLL реализован некий интерфейс IHelloWorld...
В твоём коде это примерно так будет выглядеть:
Код: Выделить всё
STDAPI DllGetClassID(LPCLSID lpClsID)
{
*lpClsID = CLSID_HelloWorld;
return S_OK;
}
В файл с расширением .def нужно будет дописать имя этой функции.
Можно пойти по другому пути и не экспортировать никаких дополнительных функций, а создать в рабочем каталоге INI-файл, к примеру Plugins.ini и указать там для каждого модуля GUID его ко-класса.
Например так:
Потом просто читаешь эти данные из INI-файла, преобразовываешь строку в структуру CLSID и создаёшь свой СОМ-объект.[CLSID]
Hello.dll={BF837F27-8728-4F7C-93C9-2BE164F96542}
FuckOff.dll={CD235F78-1245-4F7C-93C9-2AB123F00000}
Пытаюсь то же самое организовать в Delphi, однако возникает ошибка Access violation. Не могу понять в чем дело. Вот код:
Код: Выделить всё
type
TDllGetClassObject = function(const CLSID, IID: TGUID; var Obj): HResult; stdcall;
var
hndl: THandle;
dll_name: String;
p: pointer;
DllGetClassObject: TDllGetClassObject;
factory: IClassFactory;
V: LongWord;
begin
dll_name:= 'MYSHELL.DLL';//
try
hndl:=LoadLibrary(PChar(dll_name));
if hndl=0 then
begin
ShowMessage(PChar('Не могу загрузить DLL '+dll_name+'!') );
end
else
begin
ShowMessage('Dll загружена!');
//инстанцируем
DllGetClassObject := GetProcAddress(hndl,'DllGetClassObject');
if not Assigned(DllGetClassObject) then
showMessage('Not assigned dll!')
else
begin
OleCheck(DllGetClassObject(MY_CLSID,IID_IClassFactory,factory));
//ClassFactory.CreateInstance(nil,IID_IDispatch, ...);
//...
end;
end;
finally
FreeLibrary(hndl);//здесь возникает ошибка Access violation
end;
end;
end.