8 / 3 / 1
Регистрация: 11.08.2016
Сообщений: 44
|
|||||||||||||||||||||||||||||||
1 | |||||||||||||||||||||||||||||||
Простой пример создания COM компонента15.04.2021, 09:59. Показов 19466. Ответов 8
Доброго времени суток! В универе по системному программированию дали лабу: написать либу с COM-компонентом и программу, которая бы ее использовала. Не могу никак разобраться, как грамотно, нормально написать простой COM-компонент. В теории я вроде бы разобрался, но на практике ничего не выходит.
На MSDN я нашел пример "простого" COM-компонента с использованием WinRT (https://docs.microsoft.com/ru-... -coclasses), но из того кода я ничего не понял - мои знания C++ на уровне "Си + ООП от C#". Нагуглив несколько старых статей в интернете... 1) https://www.rsdn.org/article/com/introcom.xml 2) http://www.comprice.ru/article... p?ID=42510 ...я сумел кое-что написать (библиотеку) и это даже скомпилилось) А вот клиент не компилится. XD Но проблема еще и в том, что я не могу понять, как теперь эту библиотеку использовать: .lib-файл не создается (т.к., насколько я понимаю, нет ни одной переменной, которая бы содержала __declspec(dllexport) ). Объясните, пожалуйста, как решить эту проблему, где у меня ошибки. На данный момент я в тупике, ощущение, что сделал неправильно все, что можно было) XD Но хочу разобраться. Когда .lib-файл не создался, я попробовал добавить DllExport везде, где можно, но само собой это не сработало) БИБЛИОТЕКА framework.h - тут ничего интересного, но все же скину для полноты картины Кликните здесь для просмотра всего текста
CA.h Кликните здесь для просмотра всего текста
IX.h Кликните здесь для просмотра всего текста
IY.h Кликните здесь для просмотра всего текста
CA.cpp Кликните здесь для просмотра всего текста
Еще есть pch.h и pch.cpp, но их показывать нет смысла. КЛИЕНТ В клиенте VS жалуется на строки 16, 30 и 43. Этот пример я взял из интернета. Кликните здесь для просмотра всего текста
Надеюсь, кто-нибудь сможет грамотно объяснить, как это работает и как все соединить. Заранее спасибо!
0
|
|
15.04.2021, 09:59 | |
Ответы с готовыми решениями:
8
простой пример использования компонента TrackBar Простой пример создания нового потока с интерфейсом Runnable Привидите простой пример создания Dll файла, но чтобы в самом Dll находился файл. Простой вариант дублирования компонента |
2350 / 810 / 309
Регистрация: 10.02.2018
Сообщений: 1,903
|
|
15.04.2021, 18:07 | 2 |
Библиотека должна экспортировать функции DllGetClassObject и DllCanUnloadNow. А в клиентской части нужно создавать объект с помощью специальных функций, например CoCreateInstance.
1
|
Модератор
3382 / 2154 / 352
Регистрация: 13.01.2012
Сообщений: 8,364
|
|
16.04.2021, 13:57 | 3 |
0
|
8 / 3 / 1
Регистрация: 11.08.2016
Сообщений: 44
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
30.04.2021, 21:05 [ТС] | 4 | ||||||||||||||||||||||||||||||||||||||||||||||||||
Разобрался получше, сумел создать и зарегистрировать COM-компонент, написал клиент и вроде как все работает. Но возникли 2 другие проблемы.
Компонент "по классике жанра" - MathComponent, но с двумя интерфейсами (+ и -, * и /). Проблемы: 1) Клиент вылетает почти в самом конце при вызове
3) По диаграмме последовательности COM, которую я прикрепил, видно, что у нас должна быть возможность получить один интерфейс компонента через другой, но почему-то я так сделать не могу - функции QueryInterface в моем интерфейсе нет, хотя они и наследуются от IUnknown. Клиент: Кликните здесь для просмотра всего текста
Библиотека: Laba4COM.def Кликните здесь для просмотра всего текста
Код
; ; Laba4Com.def : Объявляем параметры модуля для DLL. ; LIBRARY "Laba4COM" EXPORTS ; Имена точек входа для внешнего пользования помещаются здесь DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllInstall PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE pch.h Кликните здесь для просмотра всего текста
Laba4COM.cpp Кликните здесь для просмотра всего текста
Math.h Кликните здесь для просмотра всего текста
Math.cpp Кликните здесь для просмотра всего текста
MathClassFactory.h Кликните здесь для просмотра всего текста
MathClassFactory.cpp Кликните здесь для просмотра всего текста
registration.h Кликните здесь для просмотра всего текста
registration.cpp Кликните здесь для просмотра всего текста
0
|
2350 / 810 / 309
Регистрация: 10.02.2018
Сообщений: 1,903
|
|
01.05.2021, 00:38 | 5 |
Вы как-то не правильно интерфейсы реализуете и вызываете их то же не очень правильно.
IAdder и IMultiplier - это два экспортируемых из библиотеки интерфейса (кода вы не дали, но я так предполагаю). Math - это уже не интерфейс, это внутренний библиотечный класс, реализующий оба вышеозначенных интерфейса. Это весьма спорное решение и, как видно из вашего кода, вы не совсем понимаете, что с этим нужно делать дальше. Самым простым и наглядным примером было бы написать две разные реализации интерфейсов, одну для IAdder, вторую для IMultiplier. Добавить ещё два GUID-а, по одному на каждый интерфейс. Из клиента создать интерфейсы двумя вызовами CreateInstance с указанием соответствующих GUID-ов. Каждый вызов CreateInstance будет создавать новый объект реализующий соответствующий интерфейс. Два интерфейса - два объекта. Если же вы решили делать один объект, который будет поддерживать два интерфейса, то нужно писать соответствующий обработчик QueryInterface, который будет грамотно преобразовывать указатель на this. На разные клиентские запросы нужно возвращать (IAdder*)this или (IMultiplier*)this. Общие замечания к вашему коду. 1) #include "Math.h" внутри клиента - это нонсенс, как и преобразование ((Math*)pAdder). Клиент оперирует только интерфейсами и GUID-ами. 2) Каждый QueryInterface требует последующего вызова Release. Вы дважды в клиентском коде запрашиваете интерфейсы и только один раз делаете освобождение.
0
|
8 / 3 / 1
Регистрация: 11.08.2016
Сообщений: 44
|
||||||||||||||||
02.05.2021, 23:32 [ТС] | 6 | |||||||||||||||
Блин, точно) XD Я и не заметил, что коды интерфейсов не скинул) На всякий случай скину)
IAdder.h Кликните здесь для просмотра всего текста
IMultiplier.h Кликните здесь для просмотра всего текста
Да, именно так и требуется сделать. Да, я это понимаю, просто сделал так на автомате, когда потребовалось добавить GUID в проект клиента. Позже просто скопирую нужные GUID'ы, а эту чушь удалю. Точно( Не заметил, когда экспериментировал с интерфейсами, выявляя причину ошибки. Буду дальше исправлять косяки, большое спасибо! Добавлено через 17 минут Точно, сделал приведение типов, где нужно было - и проблема с вызовами функций исчезла) Math.cpp Кликните здесь для просмотра всего текста
Но как быть с вызовом Release()? Я понимаю, что приводить тип интерфейса к типу класса Math для вызова Release() - полный бред, но что нужно исправить, чтобы можно было из самого интерфейса вызывать функцию? Вот это я понять никак не могу...(
0
|
2350 / 810 / 309
Регистрация: 10.02.2018
Сообщений: 1,903
|
|
03.05.2021, 10:16 | 7 |
![]() Решение
Просто вызывайте функцию у нужного интерфейса и всё.
Запросили интерфейс сложения, поработали с ним - сделайте его освобождение. Запросили интерфейс умножения, поработали с ним - сделайте его освобождение. Что такое интерфейс? Интерфейс - это табличка в памяти, которая содержит адреса всех функций перечисленных в описании интерфейса. Получая интерфейс - вы получаете указатель на начало соответствующей таблички. Вызовы функций интерфейса выполняются через вызовы адресов из таблички. Класс, который наследуется от интерфейса, содержит в себе помимо переменных ещё и табличку функций интерфейса. Это значит, что когда вы создаёте экземпляр класса, то выделяется память, в которой размещаются все переменные класса и табличка виртуальных функций. Когда вы переопределяете виртуальную функцию на новую, то её новый адрес помещается в старую табличку. Если же вы решили сделать класс, который наследуется от двух интерфейсов, то внутри такого класса размещаются две таблички функций, по одной на каждый интерфейс. Именно к этим табличкам вы приводите указатель this, когда клиент запрашивает соответствующие интерфейсы. Каждый ваш интерфейс ещё унаследован от IUnknown. Это означает, что табличка функций интерфейса содержит как собственные функции, так и функции базового интерфейса. Как я уже говорил, при создании класса, который наследуется от двух ваших интерфейсов, получаются две таблички функций. Каждая табличка будет содержать три функции IUnknown. Нужно, что бы функции IUnknown каждой таблички ссылались на общую реализацию сделанную для класса. Тогда не важно какой интерфейс вы освобождаете, всё равно должна быть вызвана единая общая функция. Добавлено через 50 минут Наврал малость по табличкам, но не принципиально. В экземпляре класса хранится не сама табличка, а адрес таблички. Важно, что нужно вызывать единую функцию Release (AddRef, QueryInterface). А в разных табличках интерфейсов одного класса могут быть указаны одинаковые адреса функций.
2
|
8 / 3 / 1
Регистрация: 11.08.2016
Сообщений: 44
|
|||||||||||
09.05.2021, 16:55 [ТС] | 8 | ||||||||||
Да, я знаю, но у меня почему-то не получалось вызвать Release у интерфейса - я просто не мог вызвать функции интерфейса IUnknown через указатель интерфейса. Как оказалось, интерфейс IUnknown нужно было наследовать другими интерфейсами с модификатором public:
Только так я могу вызывать его функции в клиенте. Что касается теории по интерфейсам, то это я знаю, и все же спасибо - освежил память) :-) Теперь я до конца понял, как что делать, и все работает. Большое спасибо за помощь!) Добавлено через 30 минут Теперь только осталось убедиться, что исправил эту мелочь правильно. Значит, include интерфейсов из библиотеки в клиент - это нормально, но include самого класса компонента - нет, все верно? Кликните здесь для просмотра всего текста
0
|
2350 / 810 / 309
Регистрация: 10.02.2018
Сообщений: 1,903
|
|
11.05.2021, 11:12 | 9 |
Да, верно. В целом, код клиента стал вполне рабочим.
Могу сделать пару замечаний, но они скорее косметические. 1. MathClassFactory, это стандартный интерфейс фабрики. Инклудить его имеет смысл только ради CLSID. С другой стороны, вы явно определяете в коде клиента CLSID_Math. Получается некоторая избыточность, нужно либо одно, либо другое. Обычно GUID-ы подключают инклудами, а не копипастой констант. 2. Я ни разу не встречал создание интерфейса/объекта через запрос IID_IUnknown (37 строка). Обычно есть имя интерфейса и к нему прилагается два GUID-а: CLSID_<имя интерфейса> и IID_<имя интерфейса>. 3. Я не встречал ручное создание фабрики. Для создания интерфейса обычно используется CoCreateInstance. Эта функция сама создает фабрику, создает фабрикой объект и удаляет фабрику. Такой алгоритм создания объектов универсален, нет смысла каждый раз заново прописывать лишний однотипный код в новом приложении.
0
|
11.05.2021, 11:12 | |
11.05.2021, 11:12 | |
Помогаю со студенческими работами здесь
9
Пример внешенего компонента на Delphi
Ошибка создания компонента Проблема создания компонента Простой пример Простой пример на С++ Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |