СОМ-объект без регистрации в реестре

Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain

Ответить
Аватара пользователя
Decoder
Сообщения: 303
Зарегистрирован: 19 фев 2008, 23:11
Откуда: Moscow

10 мар 2009, 18:31

Всем привет!
У меня такой вопрос: как мне использовать DLL-модуль, в котором реализован некий СОМ-объект, но таким образом, чтобы этот модуль не регистрировать в системе, а непосредственно самому его загружать из указанного каталога и обращаться к его интерфейсам?
Для чего мне это нужно: чтобы реализовать набор плагинов для основного приложения. Каждый DLL-модуль реализует однотипные функции (к примеру: формирование и печать того или иного документа или получение исходных данных из различных источников).
Аватара пользователя
Romeo
Сообщения: 3091
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

10 мар 2009, 23:05

Если COM, то только регистрация, иначе никак.

Для того, чтобы реализовать поддержку плагинов, кстати, чаще всего не используют 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" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
WinMain
Сообщения: 913
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

11 мар 2009, 01:01

Модуль с СОМ-объектом можно и не регистрировать в системе.
Тогда для создания экземпляра объекта вместо вызова функции 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 такие классы-обёртки генерирует визард из библиотеки типов СОМ-объекта.
Аватара пользователя
Decoder
Сообщения: 303
Зарегистрирован: 19 фев 2008, 23:11
Откуда: Moscow

11 мар 2009, 21:48

Спасибо за информацию. Но как мне на практике это применить?
Как генерировать классы-обёртки для IDispatch и затем использовать их?
Как на примере самому загрузить какой-нибудь СОМ-объект без использования CoCreateInstance()?
Буду благодарен за помощь.
Аватара пользователя
WinMain
Сообщения: 913
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

12 мар 2009, 18:40

Если желаешь потренироваться в создании классов-обёрток, то можешь выполнить следующее упражнение...
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;
}
После этого скомпилируй проект и запусти приложение. В результате на экране должны свернуться все окна и запуститься редактор "Блокнот".
Желаю удачи...
Аватара пользователя
Decoder
Сообщения: 303
Зарегистрирован: 19 фев 2008, 23:11
Откуда: Moscow

13 мар 2009, 17:13

У меня получилось. Спасибо, WinMain!
Ну а как мне теперь попробовать загрузить СОМ-объект напрямую из DLL?
Аватара пользователя
WinMain
Сообщения: 913
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

14 мар 2009, 11:36

Для чистоты эксперимента сделай так:
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;
}
3. Скомпилируй проект и запусти приложение. В результате так же должны свернуться все окна и запуститься "Блокнот".
Дерзай, Decoder...
Аватара пользователя
Decoder
Сообщения: 303
Зарегистрирован: 19 фев 2008, 23:11
Откуда: Moscow

16 мар 2009, 10:25

Фантастика!!! А ведь реально работает...
WinMain, а как мне теперь в своём модуле сделать экспорт функции, возвращающую GUID ко-класса? А ещё лучше было бы сделать так, чтобы GUID вообще не требовался...
Аватара пользователя
WinMain
Сообщения: 913
Зарегистрирован: 14 янв 2005, 10:30
Откуда: Москва
Контактная информация:

21 мар 2009, 02:09

Добавить экспортируемую функцию в DLL-модуль совсем несложно.
Чтобы вернуть GUID ко-класса из DLL, необходимо в файл к уже имеющимся экспортируемым функциям добавить ещё одну.
Предположим, у тебя в DLL реализован некий интерфейс IHelloWorld...
В твоём коде это примерно так будет выглядеть:

Код: Выделить всё

STDAPI DllGetClassID(LPCLSID lpClsID)
{
	*lpClsID = CLSID_HelloWorld;
    return S_OK;
}

В файл с расширением .def нужно будет дописать имя этой функции.

Можно пойти по другому пути и не экспортировать никаких дополнительных функций, а создать в рабочем каталоге INI-файл, к примеру Plugins.ini и указать там для каждого модуля GUID его ко-класса.
Например так:
[CLSID]
Hello.dll={BF837F27-8728-4F7C-93C9-2BE164F96542}
FuckOff.dll={CD235F78-1245-4F7C-93C9-2AB123F00000}
Потом просто читаешь эти данные из INI-файла, преобразовываешь строку в структуру CLSID и создаёшь свой СОМ-объект.
Philipp
Сообщения: 2
Зарегистрирован: 15 ноя 2016, 23:27

16 ноя 2016, 02:20

Пытаюсь то же самое организовать в 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.
Ответить