ATLを使用してC ++でActiveXコントロールを作成するためのガイドライン

インターネットには、ATLの使用、特にActiveX / OLEコントロールを含むヘルプを使用したCOMコンポーネントの作成に関する多くのチュートリアルがありますが、それらのほとんどは何らかの理由でVisual Studioのさまざまな興味深い場所にマウスを突っ込むプロセスを説明しています後者のグラフィカルツール、およびそれらのいくつかは、かなり深いボリュームで開発環境の内部生成コードに影響を与えます。 インターネットのロシア語セグメントでは、状況はさらに悪化しています-ATLの資料はほとんどなく、実際にはまったくありません(ATLだけでなく、一般にCOMコンポーネントの作成にも)、この欠点を補うことにしました。



おそらく、ActiveXコンポーネントとATLについて簡単に説明することから始める必要があります。



ActiveXは、OLEのブランド変更である「オブジェクトのリンクと埋め込み」、Microsoftが開発したCOM、コンポーネントオブジェクトモデル、MSが発明した言語に依存しないコンポーネントモデルに基づくマイクロソフトのテクノロジです。 OLEを使用すると、Windows上で実行されるさまざまなプログラミング言語で記述されたさまざまなプログラムに、個々のコントロール、ドキュメント、または単にコンポーネントを埋め込むことができます。 特に、ActiveXコントロールはInternet Explorerに「貼り付け」られることで知られており、IEで最もよく知られているコンポーネントの1つは、たとえばAdobe Flashモジュールです。



ActiveXコンポーネントのインターフェイスは、Microsoft自体(Windows Media Player、またはMicrosoft Office、特にWord、Excelなどのプログラム)とサードパーティ企業の両方から、Windows用の多くの有名で人気のあるプログラムとともに提供されます。 (前述のフラッシュ、Adobe Reader、および同じAdobeの他の多くのプログラム-例えば、Photoshopを正しく覚えていれば)。



技術は時代遅れになっている、またはすでに時代遅れになっているという意見がありますが、それにもかかわらず、私はそれが単純に低レベルのプログラミングとシステムサービスの分野に絞り込まれ、WindowsカーネルがCで書かれており、多くのシステムコンポーネントがCOMを使用しており、 COMインターフェイス、それは確かにどこにも行きません。



次に、ATLについて説明します。 ATL、Active Template LibraryはMicrosoftの有名なライブラリで、C ++でのWinapiの操作を簡素化します。 ATLには、COM / OLE / ActiveXを操作するためのクラス/テンプレートだけでなく、GUIの構築や管理などのためのクラスも含まれています。



通常、ATLにはMicrosoft Visual Studioのフルバージョンが付属していますが、お持ちでない場合は、 Windows DDKからこのライブラリを入手できます。



そのため、この記事では、DirectX 11を使用して、ワイヤフレームにラスタライズされた回転する球体を描画する単純なコンポーネントを作成するプロセスについて説明します。2つの方法があります。実行-球体の回転を開始し、停止-回転を停止します。





まず、モジュールのインターフェイスを作成し、ライブラリの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を呼び出します



ご覧のとおり、2つのインターフェイスの定義と、インターフェイスを実装するCOMクラスの説明を記録しました。 私たちが持っている最初のIMyControlは、実際にはコンポーネントのインターフェースであり、2番目はコントロールに発生するイベントについて外部に警告するために必要です。 イベントを記録していないため、この例ではイベントを記録しません。したがって、このインターフェイスは空です。



このクラスによって実装されるインターフェイスは、いわゆるデュアルインターフェイスを指します。 つまり、ネイティブコードと通信できる言語からだけでなく、仮想メソッドテーブルを介してCOMコンポーネントと通信できるだけでなく、IDispatchインターフェイスを使用してスクリプト言語からも使用できます。



次に、C ++-MyControl.hppのヘッダーファイルにIMyControlおよび_IMyControlEventsの定義を記述する必要があります

 #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 ++コンパイラに便利に統合しており、これにより、多かれ少なかれ通常のC ++クラスおよび構造体と同様に、MSVC ++のCOMインターフェイスおよびコンポーネントを使用できるようになります(特に今後よく見られます)。



マクロDEFINE_GUIDは、マクロINITGUIDの定義に応じて展開されます。マクロが存在しない場合、特定の名前を持つGUID型の外部変数を宣言します。 INITGUIDの場合、それも初期化します。



ここで、ATLのCComModuleクラスに属する_Module変数を定義する必要があります。



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ファイルを書き込むか、レジストリに対応するエントリを手動で作成できますが、Windowsの一部であり、自動を許可するregsvr32.exeプログラムを使用することもできますコンポーネント自体を使用した登録。 そのためには、ライブラリがいくつかの関数、特に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ランタイムによってシリアル化および同期されます。



ところで、シリアル化について、つまり、マーシャリングについてです。 理論的には、コンポーネントのメソッド呼び出しとそのインターフェイスへのポインターをシリアル化するために、クライアントアプリケーション(別のコンピューター上にある場合)の両方にロードされる別のライブラリ、いわゆるproxy-dllを並列に配置する必要があります。 COMコンポーネントがロードされるプロセス。



MicrosoftのMIDLコンパイラは、プロキシライブラリ、またはこの場合はプロキシ/スタブと呼ばれるようにコードを生成できますが、この場合はそれが簡単です-データ型は多かれ少なかれ標準であるため、組み込みのマーシャリングを使用できますランタイムOLE。 この場合、IDLファイルからいわゆるタイプライブラリ(タイプライブラリ、TLB)をコンパイルし、再びMIDLコンパイラーmidl.exe(Windows SDKとVSに含まれています)を使用して、コンポーネントとともにインストールする必要があります。彼女。 さらに、全体をさらに簡素化し、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形式。 私は個人的に、このファイルのMS DirectX SDKからDirectXアイコンを取りました。

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コントロールを実装するには、クラスはIDispatchから始まるかなり多数のさまざまなCOMインターフェイスを実装する必要がありますが、ATLを使用するため、名前が「Impl」で終わる特別なクラスからの継承によりプロセスが大幅に簡素化されます実際には、標準形式のインターフェイスに必要な機能も実装しています。



CMyControlの最も重要な3つの基本クラスは、特にクラスのオブジェクトの参照カウントを制御するCComObjectRootEx、クラスファクトリ(IClassFactory)を実装するCComCoClass、およびCWindowImpl(HWNDをラップするクラス、つまりウィンドウハンドル)を継承するCComControlです。 、および組み込みのActiveXコントロールに必要なほとんどの機能を実装します。



クラス本体の最も重要なマクロ:



DECLARE_REGISTRY_RESOURCEID-コンポーネント登録スクリプトが配置されているリソースのIDを示します。



BEGIN_COM_MAP + END_COM_MAP-IUnknownインターフェイス(COMインターフェイスの階層の最上位)のQueryInterfaceメソッドを実装します。これにより、オブジェクトの異なるインターフェイスへのリンクを取得できます(各COM_INTERFACE_ENTRYはオプションの1つを示します)。



BEGIN_CONNECTION_POINT_MAPおよび対応するENDは、制御イベントに関するアラートに関連付けられたインターフェースの実装に必要です。



BEGIN_MSG_MAP、MESSAGE_HANDLER、およびEND_MSG_MAP-WindowsウィンドウメッセージのC ++クラスのメソッドへのマッピングを実装します。



コントロールが再描画の必要性についてシステムからメッセージを受信するたびに、OnDrawメソッドを呼び出します。 さらに、コントロールの再描画はタイマーで呼び出されます。



コンポーネントのすべての機能、特にDirect3Dで動作する機能は、pimplパターンに従って、CMyControl.cppファイルのプライベートクラスCMyControlImplによって実装されます。 詳細については説明しません。CMyControl自体のコンストラクターでは、内部プロパティm_bWindowOnlyをTRUEに設定する必要があることに注意してください。これは、コンポーネントがグラフィックアプリケーションへの埋め込みのみをサポートすることを意味します。



また、COMインターフェイスへのリンクのカウントを制御するコンポーネントの実装では、ATLのスマートポインターCComPtrのテンプレートクラスが積極的に使用され、boostのintrusive_ptrと非常に似ていることに注意してください。



次に、クラスとインターフェイスのGUID、_Module変数を定義するMyActiveX.cppファイルを作成し、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
      
      







プロジェクトのソースコード全体(Windows SDKを使用してビルドするためのMakefileを含む)は、記事の最後のリンクにある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



All Articles