Pull to refresh

Руководство по созданию ActiveX-контролов на C++ с помощью ATL

Reading time13 min
Views29K
В интернете существует множество учебников по использованию ATL, и в частности, по созданию COM-компонентов с его помощью, в том числе и ActiveX/OLE контролов, но большинство из них почему-то описывают процесс тыкания мышкой в разные интересные места Visual Studio, использование графических инструментов последней, и мало какой из них затрагивает нутро сгенерированного средой разработки кода в достаточно глубоком объеме. В русскоязычном сегменте интернета ситуация и того хуже — материалов по ATL крайне мало, практически нет(да и не только по ATL, а и по созданию COM-компонентов вообще), поэтому я решил этот недостаток компенсировать.

Ну, начать следует, наверное, с краткого описания того, что из себя представляют ActiveX-компоненты и что такое ATL.

ActiveX это ребрендинг аббревиатуры OLE, «Object Linking and Embedding», технологии от Microsoft, основанной на COM, Component Object Model — языконезависимой компонентной модели, придуманной MS. OLE позволяет встраивать отдельные контролы, документы, да или просто компоненты в разные программы, написанные на разных языках программирования, работающие под Windows. ActiveX-контролы, в частности, известны тем, что их можно «вклеивать» в веб-браузер Internet Explorer, и одним из самых известных таких компонентов для IE является, к примеру, модуль Adobe Flash.

Интерфейс ActiveX-компонента поставляют с собой многие известные и популярные программы для Windows, как от самой Microsoft(Windows Media Player, или, например, программы из Microsoft Office, в частности Word, Excel и т.п.), так и от сторонних компаний(уже вышеупомянутый флеш, Adobe Reader, плюс многие другие программы того же Adobe — например Photoshop, если я правильно помню).

Есть мнение, что технология устаревает, или уже устарела, но тем не менее, я лично считаю, что она просто вытесняется в область низкоуровневого программирования и системных сервисов, и пока ядро Windows пишется на Си, а многие компоненты системы используют COM, и основаны на COM-интерфейсах, она уж точно никуда не денется.

Теперь про то, что такое ATL. ATL, Active Template Library — известная библиотека от Microsoft, которая упрощает работу с Winapi на C++. В ATL входят классы/шаблоны не только для работы с COM/OLE/ActiveX, но и классы для, к примеру, построения и управления GUI и т.п.

ATL обычно поставляется с полноценными версиями Microsoft Visual Studio, но если у вас ее нет, то можете взять эту библиотеку из Windows DDK.

Так вот, в данной статье я опишу процесс создания простенького компонента, который будет у нас средствами DirectX 11 рисовать крутящуюся растеризованную в wireframe сферу, и у которого будет два метода — Run — запустить вращение сферы, и Stop — остановить вращение.


Для начала нам нужно придумать интерфейс нашего модуля, придумать GUID для библиотеки, интерфейсов и класса компонента, и записать все это, как водится, в MIDL, Microsoft Interface Definition Language.

[
  uuid(1E4E47F3-21AF-407C-9544-59C34C81F3FA),
  version(1.0),
  helpstring("MyActiveX 1.0 Type Library")
]
library MyActiveXLib
{
  importlib("stdole32.tlb");
  importlib("stdole2.tlb");

  [
    object,
    uuid(2B26D028-4DA6-4D69-9513-D0CA550949D1),
    dual,
    helpstring("IMyControl Interface"),
    pointer_default(unique)
  ]
  interface IMyControl : IDispatch
  {
    [id(1), helpstring("method Run")] HRESULT Run();
    [id(2), helpstring("method Stop")] HRESULT Stop();
  };

  [
    uuid(E7D13B5A-0A09-440A-81EA-C9E3B0105DB0),
    helpstring("_IMyControlEvents Interface")
  ]
  dispinterface _IMyControlEvents
  {
    properties:
    methods:
  };

  [
    uuid(5747094E-84FB-47B4-BC0C-F89FB583895F),
    helpstring("MyControl Class")
  ]
  coclass MyControl
  {
    [default] interface IMyControl;
    [default, source] dispinterface _IMyControlEvents;
  };
};



Сохраним это дело в файл, и назовем MyActiveX.idl

Как видно, мы записали определение двух интерфейсов, а также описание COM-класса, который реализует наши интерфейсы. Первый, IMyControl, у нас представляет собой собственно интерфейс компонента, а второй нужен для оповещения внешнего мира о событиях, которые случаются с нашим контролом. Событий у нас не записано, и мы их делать в нашем примере не будем, поэтому этот интерфейс у нас пустой.

Интерфейс, реализуемый нашим классом, относится к так называемым dual-интерфейсам. Это значит, что его можно использовать не только из языков, способных на общение с native-кодом, а соответственно, и способных общаться с COM-компонентами через таблицы виртуальных методов, но и из скриптовых языков, посредством интерфейса IDispatch.

Далее нам нужно записать определения IMyControl и _IMyControlEvents в заголовочном файле для C++ — MyControl.hpp
#ifndef __MY_CONTROL_HPP__
#define __MY_CONTROL_HPP__

#include <windows.h>

typedef interface IMyControl IMyControl;

MIDL_INTERFACE("2B26D028-4DA6-4D69-9513-D0CA550949D1")
IMyControl : IDispatch
{
public:
  virtual HRESULT STDMETHODCALLTYPE Run() = 0;
  virtual HRESULT STDMETHODCALLTYPE Stop() = 0;
};

MIDL_INTERFACE("E7D13B5A-0A09-440A-81EA-C9E3B0105DB0")
_IMyControlEvents : public IDispatch
{
};

DEFINE_GUID(IID_IMyControl,0x2B26D028,0x4DA6,0x4D69,0x95,0x13,0xD0,0xCA,0x55,0x09,0x49,0xD1);

DEFINE_GUID(LIBID_MyActiveXLib,0x1E4E47F3,0x21AF,0x407C,0x95,0x44,0x59,0xC3,0x4C,0x81,0xF3,0xFA);

DEFINE_GUID(DIID__IMyControlEvents,0xE7D13B5A,0x0A09,0x440A,0x81,0xEA,0xC9,0xE3,0xB0,0x10,0x5D,0xB0);

DEFINE_GUID(CLSID_MyControl,0x5747094E,0x84FB,0x47B4,0xBC,0x0C,0xF8,0x9F,0xB5,0x83,0x89,0x5F);

#endif __MY_CONTROL_HPP__


Макрос MIDL_INTERFACE раскрывается во что-то вроде «struct __declspec(novtable) __declspec(uuid(строка GUID для интерфейса))». Microsoft довольно удобно интегрировали COM в свой компилятор C++, и это позволяет(и дальше это будет видно особенно хорошо) нам работать с интерфейсами и компонентами COM из MSVC++, как с более-менее обычными классами и структурами C++.

Макрос DEFINE_GUID же, в свою очередь, раскрывается в зависимости от определения макроса INITGUID — в случае отсутствия оного, он декларирует extern-переменную типа GUID с определенным названием. В случае INITGUID, он ее еще и инициализирует.

Теперь следует определить переменную _Module, которая принадлежит классу CComModule из ATL.

Запишем декларацию переменной в отдельный заголовочный файл, скажем MyActiveX.hpp
#ifndef __MY_ACTIVE_X_HPP__
#define __MY_ACTIVE_X_HPP__

#include <windows.h>
#include <atlbase.h>

extern CComModule _Module;

#endif // __MY_ACTIVE_X_HPP__


CComModule это класс, реализующий функциональность COM-модуля, в частности регистрацию классов компонентов, инициализацию COM-сервера и прочие подобные вещи.

Для регистрации COM-серверов в реестре(а COM работает именно через реестр) мы могли бы написать reg-файл, или вручную создать в реестре соответствующие записи, но также мы можем использовать программу regsvr32.exe, входящую в состав Windows, и позволяющую проводить автоматическую регистрацию, средствами самого компонента. Для этого необходимо, чтобы наша библиотека экспортировала некоторые функции, и в частности DllRegisterServer и DllUnregisterServer.

CComModule упрощает весь процесс автоматической регистрации, и позволяет сводить ее к вызову соответствующих методов в вышеупомянутых экспортируемых функциях библиотеки, но ему требуется, чтобы в секции ресурсов dll, реализующего компонент, находился скрипт регистрации. Назовем файл, который мы позже добавим в ресурсы, MyControl.rgs, и добавим туда такой текст:

HKCR
{
  MyActiveX.MyControl.1 = s 'MyControl Class'
  {
    CLSID = s '{5747094E-84FB-47B4-BC0C-F89FB583895F}'
  }
  MyActiveX.MyControl = s 'MyControl Class'
  {
    CLSID = s '{5747094E-84FB-47B4-BC0C-F89FB583895F}'
    CurVer = s 'MyActiveX.MyControl.1'
  }
  NoRemove CLSID
  {
    ForceRemove {5747094E-84FB-47B4-BC0C-F89FB583895F} = s 'MyControl Class'
    {
      ProgID = s 'MyActiveX.MyControl.1'
      VersionIndependentProgID = s 'MyActiveX.MyControl'
      ForceRemove 'Programmable'
      InprocServer32 = s '%MODULE%'
      {
        val ThreadingModel = s 'Apartment'
      }
      ForceRemove 'Control'
      ForceRemove 'Insertable'
      ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 101'
      'MiscStatus' = s '0'
      {
          '1' = s '139665'
      }
      'TypeLib' = s '{1E4E47F3-21AF-407C-9544-59C34C81F3FA}'
      'Version' = s '1.0'
    }
  }
}


Наиболее значимые части скрипта регистрации — CLSID, то есть GUID класса нашего компонента, ProgID, т.е. человекочитаемое представление CLSID, и ThreadingModel — модель многопоточности нашего компонента, которая в данном случае устанавливается в Apartment, что значит что напрямую к нашему контролу можно обращаться только из того потока, в котором он был создан, а все внешние вызовы, в том числе извне процесса(или даже с другого компьютера — через DCOM) будут сериализоваться и синхронизироваться средствами рантайма COM.

Кстати, о сериализации, то есть, вернее сказать, о маршалинге. В теории, для того чтобы сериализовать вызовы методов нашего компонента и указатели на его интерфейсы, мы должны поставить параллельно с ним отдельную библиотеку, так называемую прокси-dll, которая будет подгружаться как в клиентское приложение(если оно на другом компьютере), так и к процессу, куда загружен наш COM-компонент.

Компилятор MIDL от Microsoft мог бы генерировать код для прокси-библиотеки, или как она в данном случае называлась бы, proxy/stub, но в нашем случае можно поступить проще — так как типы данных у нас более-менее стандартные, мы может использовать встроенный маршалинг рантайма OLE. Для данного дела нам нужно из нашего IDL-файла, опять же средствами компилятора MIDL, midl.exe(входит в состав как Windows SDK, так и VS), скомпилировать так называемую библиотеку типов(type library, tlb), и поставить с нашим компонентом ее. Более того, мы можем еще дальше упростить все это дело, и включить скомпилированную библиотеку типов в секцию ресурсов DLL, что мы и сделаем.

Заголовочный файл для ресурсов нашего модуля:
#ifndef __RESOURCE_H__
#define __RESOURCE_H__

#define IDB_MAIN_ICON 101
#define IDR_MYCONTROL 102
#define IDS_SHADER 103
#define SHADER_RESOURCE 256

#endif // __RESOURCE_H__


IDR_MYCONTROL — id ресурса скрипта регистрации.
IDB_MAIN_ICON — иконка 16x16 для нашего компонента, в формате BMP. Я лично для этого файла взял иконку DirectX из MS DirectX SDK.
IDS_SHADER и SHADER_RESOURCE — id ресурса и типа ресурса, содержащего код шейдеров для отрисовки сферы.

Сам файл ресурсов, MyActiveX.rc, такой:
#include <windows.h>
#include "Resource.h"

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

1 TYPELIB "MyActiveX.tlb"

IDR_MYCONTROL REGISTRY "MyControl.rgs"

IDB_MAIN_ICON BITMAP "DirectX.bmp"

IDS_SHADER SHADER_RESOURCE "MyActiveX.fx"

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,0
 PRODUCTVERSION 1,0,0,0
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x4L
 FILETYPE 0x2L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904B0"
        BEGIN
            VALUE "CompanyName", "\0"
            VALUE "FileDescription", "MyActiveX component module\0"
            VALUE "FileVersion", "1, 0, 0, 0\0"
            VALUE "InternalName", "MyActiveX\0"
            VALUE "LegalCopyright", "Copyright 2012 (C) Dmitry Ignatiev <lovesan.ru at gmail.com>\0"
            VALUE "OriginalFilename", "MyActiveX.dll\0"
            VALUE "ProductName", "MyActiveX component module\0"
            VALUE "ProductVersion", "1, 0, 0, 0\0"
            VALUE "OLESelfRegister", "\0"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END


Теперь перейдем непосредственно к реализации нашего контрола.
Назовем класс CMyControl, и создадим заголовочный файл CMyControl.hpp
#ifndef __CMY_CONTROL_HPP__
#define __CMY_CONTROL_HPP__

#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h>
#include "Resource.h"
#include "MyActiveX.hpp"
#include "MyControl.hpp"

class DECLSPEC_UUID("5747094E-84FB-47B4-BC0C-F89FB583895F") CMyControl :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CStockPropImpl<CMyControl, IMyControl, &IID_IMyControl, &LIBID_MyActiveXLib>,
  public CComControl<CMyControl>,
  public IPersistStreamInitImpl<CMyControl>,
  public IPersistPropertyBagImpl<CMyControl>,
  public IObjectSafetyImpl<CMyControl, INTERFACESAFE_FOR_UNTRUSTED_CALLER | INTERFACESAFE_FOR_UNTRUSTED_DATA>,
  public IOleControlImpl<CMyControl>,
  public IOleObjectImpl<CMyControl>,
  public IOleInPlaceActiveObjectImpl<CMyControl>,
  public IViewObjectExImpl<CMyControl>,
  public IOleInPlaceObjectWindowlessImpl<CMyControl>,
  public IConnectionPointContainerImpl<CMyControl>,
  public IPersistStorageImpl<CMyControl>,
  public ISpecifyPropertyPagesImpl<CMyControl>,
  public IQuickActivateImpl<CMyControl>,
  public IDataObjectImpl<CMyControl>,
  public IProvideClassInfo2Impl<&CLSID_MyControl, &DIID__IMyControlEvents, &LIBID_MyActiveXLib>,
  public IPropertyNotifySinkCP<CMyControl>,
  public CComCoClass<CMyControl, &CLSID_MyControl>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_MYCONTROL)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CMyControl)
  COM_INTERFACE_ENTRY(IMyControl)
  COM_INTERFACE_ENTRY(IDispatch)
  COM_INTERFACE_ENTRY(IViewObjectEx)
  COM_INTERFACE_ENTRY(IViewObject2)
  COM_INTERFACE_ENTRY(IViewObject)
  COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
  COM_INTERFACE_ENTRY(IOleInPlaceObject)
  COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
  COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
  COM_INTERFACE_ENTRY(IOleControl)
  COM_INTERFACE_ENTRY(IOleObject)
  COM_INTERFACE_ENTRY(IPersistStreamInit)
  COM_INTERFACE_ENTRY(IPersistPropertyBag)
  COM_INTERFACE_ENTRY(IObjectSafety)
  COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
  COM_INTERFACE_ENTRY(IConnectionPointContainer)
  COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
  COM_INTERFACE_ENTRY(IQuickActivate)
  COM_INTERFACE_ENTRY(IPersistStorage)
  COM_INTERFACE_ENTRY(IDataObject)
  COM_INTERFACE_ENTRY(IProvideClassInfo)
  COM_INTERFACE_ENTRY(IProvideClassInfo2)
END_COM_MAP()

BEGIN_PROP_MAP(CMyControl)
END_PROP_MAP()

BEGIN_CONNECTION_POINT_MAP(CMyControl)
  CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
END_CONNECTION_POINT_MAP()

BEGIN_MSG_MAP(CMyControl)  
  MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
  MESSAGE_HANDLER(WM_CREATE, OnCreate)
  MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
  MESSAGE_HANDLER(WM_SIZE, OnSize)
  MESSAGE_HANDLER(WM_TIMER, OnTimer)
  CHAIN_MSG_MAP(CComControl<CMyControl>)
END_MSG_MAP()

DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE)

  CMyControl();
  ~CMyControl();
  STDMETHOD(Run)();
  STDMETHOD(Stop)();
  HRESULT OnDraw(ATL_DRAWINFO& di);

private:
  LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
  LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
  LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
  LRESULT OnTimer(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
  CMyControl(const CMyControl& copy);
  class CMyControlImpl;
  CMyControlImpl *_impl;
};

#endif // __CMY_CONTROL_HPP__


Как видно, определение класса получилось довольно большим.
Для реализации ActiveX-контрола наш класс, на самом деле, должен реализовать крайне немаленькое количество разнообразных COM-интерфейсов, начиная от IDispatch, но так как мы используем ATL, процесс очень сильно упрощается, благодаря наследованию от специальных классов, названия которых оканчиваются на «Impl», которые собственно говоря, и реализуют необходимую для интерфейсов функциональность в стандартном виде.

Три наиболее важных базовых класса нашего CMyControl — CComObjectRootEx, управляющий, в частности, подсчетом ссылок объектов нашего класса, CComCoClass, реализующий фабрику класса(IClassFactory), и CComControl, в свою очередь наследующийся от CWindowImpl(класс, обертывающий HWND, то есть дескриптор окна), и реализующий большую часть необходимой для встроенных ActiveX-контролов фунциональности.

Наиболее значимые макросы в теле класса:

DECLARE_REGISTRY_RESOURCEID — указывает id ресурса, в котором находится скрипт регистрации компонента.

BEGIN_COM_MAP + END_COM_MAP — реализуют метод QueryInterface интерфейса IUnknown(который является вершиной иерархии интерфейсов COM), который позволяет получать ссылки на разные интерфейсы объекта(и каждая из COM_INTERFACE_ENTRY указывает один из вариантов).

BEGIN_CONNECTION_POINT_MAP и соответствующий END — необходимы для реализации интерфейсов, связанных с оповещениями о событиях контрола.

BEGIN_MSG_MAP, MESSAGE_HANDLER и END_MSG_MAP — реализуют отображение сообщений Windows-окна на методы C++ классов.

Метод OnDraw у нас будет вызываться каждый раз, когда контрол будет получать от системы сообщение о необходимости перерисовки. Кроме этого, перерисовка контрола у нас будет вызываться по таймеру.

Вся функциональность нашего компонента, и, в частности, работа с Direct3D, реализуется приватным классом CMyControlImpl, согласно паттерну pimpl, в файле CMyControl.cpp. Я не буду в деталях его описывать, отмечу только то, что в конструкторе самого CMyControl необходимо выставить внутреннее свойство m_bWindowOnly в TRUE — это будет означать, что наш компонент поддерживает встраивание исключительно в графические приложение.

Также, стоит отметить что в реализации компонента для управления подсчетом ссылок на COM-интерфейсы активно используется шаблонный класс умного указателя CComPtr из ATL, очень похожий на intrusive_ptr из boost.

Теперь создадим файл MyActiveX.cpp, в котором у нас будет определены GUID класса и интерфейсов, переменная _Module, а также реализована точка входа DLL и необходимые для ActiveX-модуля экспортируемые функции:
#include <windows.h>
#include <atlbase.h>
#include "MyActiveX.hpp"
#include "CMyControl.hpp"

const IID IID_IMyControl = {0x2B26D028,0x4DA6,0x4D69,{0x95,0x13,0xD0,0xCA,0x55,0x09,0x49,0xD1}};
const IID LIBID_MyActiveXLib = {0x1E4E47F3,0x21AF,0x407C,{0x95,0x44,0x59,0xC3,0x4C,0x81,0xF3,0xFA}};
const IID  DIID__IMyControlEvents= {0xE7D13B5A,0x0A09,0x440A,{0x81,0xEA,0xC9,0xE3,0xB0,0x10,0x5D,0xB0}};
const CLSID CLSID_MyControl= {0x5747094E,0x84FB,0x47B4,{0xBC,0x0C,0xF8,0x9F,0xB5,0x83,0x89,0x5F}};

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
  OBJECT_ENTRY(CLSID_MyControl, CMyControl)
END_OBJECT_MAP()

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
  if(DLL_PROCESS_ATTACH == dwReason)
  {
    _Module.Init(ObjectMap, hInstance, &LIBID_MyActiveXLib);
    DisableThreadLibraryCalls(hInstance);
  }
  else if(DLL_PROCESS_DETACH == dwReason)
    _Module.Term();
  return TRUE;
}

STDAPI DllCanUnloadNow(void)
{
    return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    return _Module.GetClassObject(rclsid, riid, ppv);
}

STDAPI DllRegisterServer(void)
{
    return _Module.RegisterServer(TRUE);
}

STDAPI DllUnregisterServer(void)
{
    return _Module.UnregisterServer(TRUE);
}



Прежде чем скомпилировать dll, определим, какие функции наш модуль экспортирует, в def-файле:
LIBRARY      "MyActiveX.dll"

EXPORTS
  DllCanUnloadNow     PRIVATE
  DllGetClassObject   PRIVATE
  DllRegisterServer   PRIVATE
  DllUnregisterServer PRIVATE


Весь исходный код проекта, включая Makefile для сборки с помощью Windows SDK, приведен на github, по ссылке в конце статьи. Но сначала несколько примеров встраивания компонента:

Встраивание в HTML-страницу


<html>
<head>
  <title>Test page for MyControl ActiveX object</title>
  <script type="text/javascript">
var running = false;
function OnClick()
{
  var ctl = document.getElementById("MyControl");
  var btn = document.getElementById("btn");
  if(running)
  {
    ctl.Stop();
    running = false;
    btn.value = "Run";
  }
  else
  {
    ctl.Run();
    running = true;
    btn.value = "Stop";
  }
}
</script>
</head>
<body>
  <center>
    <input type=button value="Run" id="btn"
           style="display:block; padding: 3px 20px;"
           onclick="OnClick();"/>
    <object id="MyControl"
            style="width:500px; height:500px;"
            classid="CLSID:5747094E-84FB-47B4-BC0C-F89FB583895F">
    </object>
  </center>
</body>
</html>



Встраивание в приложение на Windows.Forms


using System;
using System.Windows.Forms;

namespace MyControl
{
    class MyControl : AxHost
    {
        public MyControl()
            : base("5747094E-84FB-47B4-BC0C-F89FB583895F")
        {
        }

        public void Run()
        {
            dynamic ax = GetOcx();
            ax.Run();
        }

        public void Stop()
        {
            dynamic ax = GetOcx();
            ax.Stop();
        }
    }

    class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();

            Form f = new Form();
            f.Text = "My control";
            f.StartPosition = FormStartPosition.CenterScreen;
            f.Width = 640;
            f.Height = 480;

            MyControl c = new MyControl();
            c.Dock = DockStyle.Fill;
            c.BeginInit();

            Button b = new Button();            
            b.Dock = DockStyle.Top;
            b.Text = "Run/Stop";

            bool running = false;
            b.Click += (s, e) =>
            {
                if (running)
                {
                    c.Stop();
                    running = false;
                }
                else
                {
                    c.Run();
                    running = true;
                }
            };

            f.Controls.Add(b);
            f.Controls.Add(c);
            f.ShowDialog();
        }
    }
}



Исходный код: github.com/Lovesan/MyActiveX
Tags:
Hubs:
+16
Comments43

Articles

Change theme settings