Здорова!
Сегодня разберем как создать с помощью технологии OLE автоматизация приложение dll которое будет подгружаться в excel и запускать диалоговое окно простейшее. Мы пошагово рассмотрим как это делать.
1 Создание проекта
Создавать мы будем в Visual Studio 2010, запускаем ее и выбираем «создать проект», выбираем «Библиотека DLL MFC», назовем его «MyOleDlg»
нажимаем «ОК», затем жмем «Далее» и в следующем окошке выбираем «Обычная DLL со статической связью с MFC» и ставим обязательно галочку «Автоматизация»
Жмем «Готово», мастер у нас создаст каркас приложения со следующим набором файлов
вод код файлов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// MyOleDlg.h: главный файл заголовка для DLL MyOleDlg // #pragma once #ifndef __AFXWIN_H__ #error "включить stdafx.h до включения этого файла в PCH" #endif #include "resource.h" // основные символы // CMyOleDlgApp // Про реализацию данного класса см. MyOleDlg.cpp // class CMyOleDlgApp : public CWinApp { public: CMyOleDlgApp(); // Переопределение public: virtual BOOL InitInstance(); DECLARE_MESSAGE_MAP() }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
// MyOleDlg.cpp: определяет процедуры инициализации для DLL. // #include "stdafx.h" #include "MyOleDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // //TODO: если эта библиотека DLL динамически связана с библиотеками DLL MFC, // все функции, экспортированные из данной DLL-библиотеки, которые выполняют вызовы к // MFC, должны содержать макрос AFX_MANAGE_STATE в // самое начало функции. // // Например: // // extern "C" BOOL PASCAL EXPORT ExportedFunction() // { // AFX_MANAGE_STATE(AfxGetStaticModuleState()); // // тело нормальной функции // } // // Важно, чтобы данный макрос был представлен в каждой // функции до вызова MFC. Это означает, что // он должен быть первым оператором // функции и предшествовать даже любым объявлениям переменных объекта, // поскольку их конструкторы могут выполнять вызовы к MFC // DLL. // // В Технических указаниях MFC 33 и 58 содержатся более // подробные сведения. // // CMyOleDlgApp BEGIN_MESSAGE_MAP(CMyOleDlgApp, CWinApp) END_MESSAGE_MAP() // создание CMyOleDlgApp CMyOleDlgApp::CMyOleDlgApp() { // TODO: добавьте код создания, // Размещает весь важный код инициализации в InitInstance } // Единственный объект CMyOleDlgApp CMyOleDlgApp theApp; const GUID CDECL _tlid = { 0xCB0DA8CD, 0x2737, 0x4AB7, { 0xA6, 0x55, 0x1E, 0xF4, 0xB8, 0xBE, 0x4, 0x8A } }; const WORD _wVerMajor = 1; const WORD _wVerMinor = 0; // инициализация CMyOleDlgApp BOOL CMyOleDlgApp::InitInstance() { CWinApp::InitInstance(); // Регистрирует все OLE-серверы (фабрики) как работающие. Это позволяет // библиотекам OLE создавать объекты из других приложений. COleObjectFactory::RegisterAll(); return TRUE; } // DllGetClassObject - возвращает фабрику класса STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return AfxDllGetClassObject(rclsid, riid, ppv); } // DllCanUnloadNow - разрешает COM выгрузить DLL STDAPI DllCanUnloadNow(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return AfxDllCanUnloadNow(); } // DllRegisterServer - добавляет записи в системный реестр STDAPI DllRegisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid)) return SELFREG_E_TYPELIB; if (!COleObjectFactory::UpdateRegistryAll()) return SELFREG_E_CLASS; return S_OK; } // DllUnregisterServer - удаляет записи из системного реестра STDAPI DllUnregisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor)) return SELFREG_E_TYPELIB; if (!COleObjectFactory::UpdateRegistryAll(FALSE)) return SELFREG_E_CLASS; return S_OK; } |
2 Добавляем диалог
Проект мы создали, добавим теперь в наш проект ресурс диалог (заходим в окошко ресурсов, кликаем правок клавишей мышки и из контекстного меню выбираем добавить ресурс, дальше выбираем ресурс dialog)
Добавим к нему класс, для этого кликаем правой клавишей мышки по нашему диалогу и выбираем «Добавить класс…», назовем класс CMyDialo
жмем «Готово», мастер создаст два файла
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#pragma once // диалоговое окно CMyDialog class CMyDialog : public CDialog { DECLARE_DYNAMIC(CMyDialog) public: CMyDialog(CWnd* pParent = NULL); // стандартный конструктор virtual ~CMyDialog(); // Данные диалогового окна enum { IDD = IDD_DIALOG1 }; protected: virtual void DoDataExchange(CDataExchange* pDX); // поддержка DDX/DDV DECLARE_MESSAGE_MAP() }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// MyDialog.cpp: файл реализации // #include "stdafx.h" #include "MyOleDlg.h" #include "MyDialog.h" #include "afxdialogex.h" // диалоговое окно CMyDialog IMPLEMENT_DYNAMIC(CMyDialog, CDialog) CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/) : CDialog(CMyDialog::IDD, pParent) { } CMyDialog::~CMyDialog() { } void CMyDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CMyDialog, CDialog) END_MESSAGE_MAP() // обработчики сообщений CMyDialog |
Добавим две переменные к нашему диалогу для двух элементов управления editControl, для этого правой клавишей кликаем по диалогу из контекстного меню выбираем «добавить переменную…»
Добавляем и вторую переменную таким же способом через мастер
На этом все диалог у нас создан
3 Создание класса объекта COM с использованием OLE автоматизации
И так диалог мы добавили, осталось нам добавить наш класс который будет вызывать диалог. Назовем его CMyDialog_Auto, он будет наследоваться от класса CCmdTarget чтобы использовать возможности MFC, я не сильно понимаю что там за возможности, наверно в нем используется диспетчеризация и функции из него вызываются, там память очищают и всю фигню делают. В общем создаем класс, кликаем правой клавишей мышки в «Окне классов», выбираем в контекстном меню «Добавить класс…», выбираем в появившемся окне «Класс MFC»
обязательно не забудьте поставить галочку «Создается по идентификатору типа», без этого у вас класс и приложение не будет регистрироваться в реестре, если мы выставляем эту опцию то у нас создается идентификатор GUID для класса. Все жмем готово и мастер создает два файла
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#pragma once // конечный объект команды CMyDialog_Auto class CMyDialog_Auto : public CCmdTarget { DECLARE_DYNCREATE(CMyDialog_Auto) public: CMyDialog_Auto(); virtual ~CMyDialog_Auto(); virtual void OnFinalRelease(); protected: DECLARE_MESSAGE_MAP() DECLARE_OLECREATE(CMyDialog_Auto) DECLARE_DISPATCH_MAP() DECLARE_INTERFACE_MAP() }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
// MyDialog_Auto.cpp: файл реализации // #include "stdafx.h" #include "MyOleDlg.h" #include "MyDialog_Auto.h" // CMyDialog_Auto IMPLEMENT_DYNCREATE(CMyDialog_Auto, CCmdTarget) CMyDialog_Auto::CMyDialog_Auto() { EnableAutomation(); // Чтобы обеспечить работу приложения в течение всего периода активности объекта автоматизации OLE, // конструктор вызывает AfxOleLockApp. AfxOleLockApp(); } CMyDialog_Auto::~CMyDialog_Auto() { // Чтобы прервать работу приложения, когда все объекты созданы // при помощи OLE-автоматизации, деструктор вызывает AfxOleUnlockApp. AfxOleUnlockApp(); } void CMyDialog_Auto::OnFinalRelease() { // Когда будет освобождена последняя ссылка на объект автоматизации, // вызывается OnFinalRelease. Базовый класс автоматически // удалит объект. Перед вызовом базового класса добавьте // дополнительную очистку, необходимую вашему объекту. CCmdTarget::OnFinalRelease(); } BEGIN_MESSAGE_MAP(CMyDialog_Auto, CCmdTarget) END_MESSAGE_MAP() BEGIN_DISPATCH_MAP(CMyDialog_Auto, CCmdTarget) END_DISPATCH_MAP() // Примечание: мы добавили поддержку для IID_IMyDialog_Auto, чтобы обеспечить безопасную с точки зрения типов привязку // из VBA. Этот IID должен соответствовать GUID, связанному с // disp-интерфейсом в файле .IDL. // {35EFA93C-2419-4349-BAE6-CFA5E43A2633} static const IID IID_IMyDialog_Auto = { 0x35EFA93C, 0x2419, 0x4349, { 0xBA, 0xE6, 0xCF, 0xA5, 0xE4, 0x3A, 0x26, 0x33 } }; BEGIN_INTERFACE_MAP(CMyDialog_Auto, CCmdTarget) INTERFACE_PART(CMyDialog_Auto, IID_IMyDialog_Auto, Dispatch) END_INTERFACE_MAP() // {611C69EF-328F-494B-8E56-18D80EEDA81E} IMPLEMENT_OLECREATE_FLAGS(CMyDialog_Auto, "MyOleDlg.MyDialog_Auto", afxRegApartmentThreading, 0x611c69ef, 0x328f, 0x494b, 0x8e, 0x56, 0x18, 0xd8, 0xe, 0xed, 0xa8, 0x1e) // обработчики сообщений CMyDialog_Auto |
так же в «Окне классов» у нас появился новый подраздел с интерфейсом IMyDialog_Auto
Кликая по нему правой клавишей мышки мы добавляем функции и члены к нашему управляемому объекту. Добавим к нашему классу два свойства «LongData» и «TextData» и одну функцию DisplayDialog. Кликаем правой клавишей мышки над IMyDialog_Auto, в появившемся контекстном меню выбираем «Добавить»->»Добавить свойство…», в появившемся окошке выставляем настройки как на скрине ниже
Жмем «Далее» и дальше «Готов», снова таким же способом добавляем свойство «TextData»
Жмем «Далее», затем «Готово». Можно сразу «Готово» нажать. Так теперь добавляем метод, для этого так же само кликаем правой клавишей мышки, только уже из контекстного меню выбираем «Добавить»->»Добавить метод…», в появившемся окошке вводим название метода
Жмем «Далее» и «Готово». Посмотрим карту диспетчеризации в файле MyDialog_Auto.cpp
1 2 3 4 5 |
BEGIN_DISPATCH_MAP(CMyDialog_Auto, CCmdTarget) DISP_PROPERTY_NOTIFY_ID(CMyDialog_Auto, "LongData", dispidLongData, m_LongData, OnLongDataChanged, VT_I4) DISP_PROPERTY_NOTIFY_ID(CMyDialog_Auto, "TextData", dispidTextData, m_TextData, OnTextDataChanged, VT_VARIANT) DISP_FUNCTION_ID(CMyDialog_Auto, "DisplayDialog", dispidDisplayDialog, DisplayDialog, VT_BOOL, VTS_NONE) END_DISPATCH_MAP() |
Видно что добавилось два свойства LongData и TextData и функция DisplayDialog. в наш класс CMyDialog_Auto добавились две protected переменные члены и 3 функции
1 2 3 4 5 6 7 8 9 10 11 |
void OnLongDataChanged(void); LONG m_LongData; enum { dispidDisplayDialog = 3L, dispidTextData = 2, dispidLongData = 1 }; void OnTextDataChanged(void); VARIANT m_TextData; VARIANT_BOOL DisplayDialog(void); |
Функции void OnLongDataChanged и OnTextDataChanged это функции отклика, типо когда меняется переменные они вроде как вызываются. А функция DisplayDialog это функция которая будет вызываться из excel и будет создавать диалоговое окошко. И так заполним эти функции, я вам уже приведу полные файлы с кодом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#pragma once // конечный объект команды CMyDialog_Auto class CMyDialog_Auto : public CCmdTarget { DECLARE_DYNCREATE(CMyDialog_Auto) public: CMyDialog_Auto(); virtual ~CMyDialog_Auto(); virtual void OnFinalRelease(); protected: DECLARE_MESSAGE_MAP() DECLARE_OLECREATE(CMyDialog_Auto) DECLARE_DISPATCH_MAP() DECLARE_INTERFACE_MAP() void OnLongDataChanged(void); LONG m_LongData; enum { dispidDisplayDialog = 3L, dispidTextData = 2, dispidLongData = 1 }; void OnTextDataChanged(void); VARIANT m_TextData; VARIANT_BOOL DisplayDialog(void); }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
// MyDialog_Auto.cpp: файл реализации // #include "stdafx.h" #include "MyOleDlg.h" #include "MyDialog_Auto.h" #include "MyDialog.h" // CMyDialog_Auto IMPLEMENT_DYNCREATE(CMyDialog_Auto, CCmdTarget) CMyDialog_Auto::CMyDialog_Auto() { EnableAutomation(); ::VariantInit(&m_TextData);//инициализация m_LongData=0; // Чтобы обеспечить работу приложения в течение всего периода активности объекта автоматизации OLE, // конструктор вызывает AfxOleLockApp. AfxOleLockApp(); } CMyDialog_Auto::~CMyDialog_Auto() { // Чтобы прервать работу приложения, когда все объекты созданы // при помощи OLE-автоматизации, деструктор вызывает AfxOleUnlockApp. AfxOleUnlockApp(); } void CMyDialog_Auto::OnFinalRelease() { // Когда будет освобождена последняя ссылка на объект автоматизации, // вызывается OnFinalRelease. Базовый класс автоматически // удалит объект. Перед вызовом базового класса добавьте // дополнительную очистку, необходимую вашему объекту. CCmdTarget::OnFinalRelease(); } BEGIN_MESSAGE_MAP(CMyDialog_Auto, CCmdTarget) END_MESSAGE_MAP() BEGIN_DISPATCH_MAP(CMyDialog_Auto, CCmdTarget) DISP_PROPERTY_NOTIFY_ID(CMyDialog_Auto, "LongData", dispidLongData, m_LongData, OnLongDataChanged, VT_I4) DISP_PROPERTY_NOTIFY_ID(CMyDialog_Auto, "TextData", dispidTextData, m_TextData, OnTextDataChanged, VT_VARIANT) DISP_FUNCTION_ID(CMyDialog_Auto, "DisplayDialog", dispidDisplayDialog, DisplayDialog, VT_BOOL, VTS_NONE) END_DISPATCH_MAP() // Примечание: мы добавили поддержку для IID_IMyDialog_Auto, чтобы обеспечить безопасную с точки зрения типов привязку // из VBA. Этот IID должен соответствовать GUID, связанному с // disp-интерфейсом в файле .IDL. // {35EFA93C-2419-4349-BAE6-CFA5E43A2633} static const IID IID_IMyDialog_Auto = { 0x35EFA93C, 0x2419, 0x4349, { 0xBA, 0xE6, 0xCF, 0xA5, 0xE4, 0x3A, 0x26, 0x33 } }; BEGIN_INTERFACE_MAP(CMyDialog_Auto, CCmdTarget) INTERFACE_PART(CMyDialog_Auto, IID_IMyDialog_Auto, Dispatch) END_INTERFACE_MAP() // {611C69EF-328F-494B-8E56-18D80EEDA81E} IMPLEMENT_OLECREATE_FLAGS(CMyDialog_Auto, "MyOleDlg.MyDialog_Auto", afxRegApartmentThreading, 0x611c69ef, 0x328f, 0x494b, 0x8e, 0x56, 0x18, 0xd8, 0xe, 0xed, 0xa8, 0x1e) // обработчики сообщений CMyDialog_Auto void CMyDialog_Auto::OnLongDataChanged(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // TODO: добавьте код обработчика свойства TRACE("CMyDialog_Auto::OnLongDataChanged\n"); } void CMyDialog_Auto::OnTextDataChanged(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // TODO: добавьте код обработчика свойства TRACE("CMyDialog_Auto::OnTextDataChanged\n"); } VARIANT_BOOL CMyDialog_Auto::DisplayDialog(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // TODO: добавьте код обработчика отправки TRACE("Entering CEx25bAuto::DisplayDialog %p\n", this); BOOL bRet = TRUE; AfxLockTempMaps(); // See MFC Tech Note #3 CWnd* pTopWnd = CWnd::FromHandle(::GetTopWindow(NULL)); try { CMyDialog dlg(pTopWnd); if (m_TextData.vt == VT_BSTR){ dlg.m_pString = m_TextData.bstrVal; // converts double-byte // character to // single-byte // character } dlg.m_pLong = m_LongData; if (dlg.DoModal() == IDOK) { m_TextData = COleVariant(dlg.m_pString).Detach(); m_LongData = dlg.m_pLong; bRet = TRUE; } else { bRet = FALSE; } } catch (CException* pe) { TRACE("Exception: failure to display dialog\n"); bRet = FALSE; pe->Delete(); } AfxUnlockTempMaps(); return bRet; } |
Так же посмотрим что добавилось в файл MyOleDlg.idl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// MyOleDlg.idl: источник библиотеки типов для MyOleDlg.dll // Этот файл будет обработан компилятором MIDL для создания // библиотеки типов (MyOleDlg.tlb). #include "olectl.h" [ uuid(CB0DA8CD-2737-4AB7-A655-1EF4B8BE048A), version(1.0) ] library MyOleDlg { importlib("stdole32.tlb"); importlib("stdole2.tlb"); // Первичный интерфейс диспетчеризации для MyDialog_Auto [ uuid(35EFA93C-2419-4349-BAE6-CFA5E43A2633) ] dispinterface IMyDialog_Auto { properties: [id(1) ] LONG LongData; [id(2) ] VARIANT TextData; methods: [id(3)] VARIANT_BOOL DisplayDialog(void); }; // Сведения о классе для MyDialog_Auto [ uuid(611C69EF-328F-494B-8E56-18D80EEDA81E) ] coclass MyDialog_Auto { [default] dispinterface IMyDialog_Auto; }; }; |
не понятная фигня создалась. IDL это язык интерфейсов. tlb файл я пока что не разобрал что это такое, он у нас нигде не создается вроде.
Все ребятки создаем проект нажимаем Ctr+F5, у нас создастся наша dll.
4 Регистрация dll
Теперь нам нужно ее зарегистрировать, для этого существуют специальные утилиты, но мы просто возьмем и скопируем нашу dll в windows/system32 и из командной строки запускаем
нажимаем «OK» и нас уведомляют об успешном выполнении регистрации
Все наша DLL зарегистрирована в реестре, теперь можно тестировать наш модуль
Добавление модуля к excel создание макросов
[tip]как добавлять макросы в excel и кнопки читайте в тут[/tip]
Я использую excel 2010, запускаем наш excel, создаем новую книгу, добавляем в нее макросы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Dim Dllcomp As Object Private Declare Sub CoFreeUnusedLibraries Lib "OLE32" () Sub LoadDllComp() Set Dllcomp = CreateObject("MyOleDlg.MyDialog_Auto") Range("C3").Select Dllcomp.LongData = Selection.Value Range("D3").Select Dllcomp.TextData = Selection.Value End Sub Sub RefreshDllComp() 'Gather Data button Range("C3").Select Dllcomp.LongData = Selection.Value Range("D3").Select Dllcomp.TextData = Selection.Value Dllcomp.DisplayDialog Range("C3").Select Selection.Value = Dllcomp.LongData Range("D3").Select Selection.Value = Dllcomp.TextData End Sub Sub UnloadDllComp() Set Dllcomp = Nothing Call CoFreeUnusedLibraries End Sub |
Добавляем кнопки как на скрине ниже и каждой кнопке присваиваем свой макрос
Нажимаем Load DLL у нас подгружается наша DLL, дальше Gather Data жмем у нас подгружается диалоговое окно, вводим данные нажимаем Ок и у нас в excele меняются данные. Нажав Unload DLL мы выгружаем DLL.
5 Как отлаживать нашу DLL
Для этого мы в отладчике указываем наше приложение Excel, и запускаем отладчик, вот какие настройки должны быть в «свойствах проекта» во вкладке «Отладка»
Запускаем отладку и отладчик переходит в режим ожидания, затем мы запускаем excel и подгружаем наш модуль и в выводе отладки смотрим что у нас выводится
На этом все.
[youtube]https://www.youtube.com/watch?v=KUou63e5jnY[/youtube]