Zásuvný modul pro Microsoft Visual Studio. Pavel Plasz

Podobné dokumenty
TÉMATICKÝ OKRUH Softwarové inženýrství

Bridge. Známý jako. Účel. Použitelnost. Handle/Body

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

Sdílení dokumentů ve stávajícím informačním systému

ČÁST 1. Základy 32bitového programování ve Windows

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15

Generické programování

Platforma.NET 11.NET Framework 11 Visual Basic.NET Základní principy a syntaxe 13

Úvod. Programovací paradigmata

2015 GEOVAP, spol. s r. o. Všechna práva vyhrazena.

typová konverze typová inference

8 Třídy, objekty, metody, předávání argumentů metod

1. Programování proti rozhraní

Architektura COM. Historie Component Object Model (COM) Komunikace s komponentami Rozhraní komponent COM komponenty v.net.

Komponenty v.net. Obsah přednášky

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu.

Úvod Seznámení s předmětem Co je.net Vlastnosti.NET Konec. Programování v C# Úvodní slovo 1 / 25

Seznámení s prostředím dot.net Framework

Programování v jazyce C a C++

Dědění, polymorfismus

Objektové programování

2015 GEOVAP, spol. s r. o. Všechna práva vyhrazena.

Technologické postupy práce s aktovkou IS MPP

Dynamicky vázané metody. Pozdní vazba, virtuální metody

Úvod do programovacích jazyků (Java)

Postupy práce se šablonami IS MPP

přetížení operátorů (o)

PŘETĚŽOVÁNÍ OPERÁTORŮ

MS Excel makra a VBA

MIKROPROCESORY PRO VÝKONOVÉ SYSTÉMY

TÉMATICKÝ OKRUH Softwarové inženýrství

Programování v C++ 3, 3. cvičení

PB161 Programování v jazyce C++ Přednáška 7

PB161 Programování v jazyce C++ Přednáška 7

Novinky. Autodesk Vault helpdesk.graitec.cz,

1. Dědičnost a polymorfismus

Preprocesor. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze. Karel Richta, Martin Hořeňovský, Aleš Hrabalík, 2016

2015 GEOVAP, spol. s r. o. Všechna práva vyhrazena.

Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost rozhraním a výjimkám.

Příloha 6. Palety nástrojů

1 Uživatelská dokumentace

Reliance 3 design OBSAH

11. Přehled prog. jazyků

PB161 Programování v jazyce C++ Přednáška 9

Teoretické minimum z PJV

Softwarové komponenty a Internet

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

Abstraktní třídy, polymorfní struktury

Matematika v programovacích

Vytvoření.NET komponenty (DLL) ve Visual Studiu

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

DUM 06 téma: Tvorba makra pomocí VBA

APS mini.ed programová nadstavba pro základní vyhodnocení docházky. Příručka uživatele verze

PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE DATUM VYTVOŘENÍ: KLÍČOVÁ AKTIVITA: 02 PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) HODINOVÁ DOTACE: 1

1 Webový server, instalace PHP a MySQL 13

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

Obsah SLEDOVÁNÍ PRÁCE... 4

První kroky s METEL IEC IDE

Více o konstruktorech a destruktorech

Maturitní otázky z předmětu PROGRAMOVÁNÍ

Mělká a hluboká kopie

Windows a real-time. Windows Embedded

Programování ve Windows Dynamické knihovny. Andrea Číková Martin Osovský

for (i = 0, j = 5; i < 10; i++) { // tělo cyklu }

3. Je defenzivní programování technikou skrývání implementace? Vyberte jednu z nabízených možností: Pravda Nepravda

Pro označení disku se používají písmena velké abecedy, za nimiž následuje dvojtečka.

5a. Makra Visual Basic pro Microsoft Escel. Vytvořil Institut biostatistiky a analýz, Masarykova univerzita J. Kalina

Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) Fakulta elektrotechniky a informatiky Katedra softwarových technologií

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Programování v C++ 2, 4. cvičení

Princip funkce počítače

RMI - Distribuované objekty v Javě

2 PŘÍKLAD IMPORTU ZATÍŽENÍ Z XML

PHP framework Nette. Kapitola Úvod. 1.2 Architektura Nette

OPS Paralelní systémy, seznam pojmů, klasifikace

EPLAN Electric P8 2.7 s databázemi na SQL serveru

INSTALACE SOFTWARE PROID+ NA MS WINDOWS

4a. Makra Visual Basic pro Microsoft Excel Cyklické odkazy a iterace Makra funkce a metody

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

DSL manuál. Ing. Jan Hranáč. 27. října V této kapitole je stručný průvodce k tvorbě v systému DrdSim a (v

Profilová část maturitní zkoušky 2017/2018

Instalace a konfigurace web serveru. WA1 Martin Klíma

Podpora skriptování v Audacity

Programování v C++ 1, 6. cvičení

Systém adresace paměti

TŘÍDY POKRAČOVÁNÍ. Události pokračování. Příklad. public delegate void ZmenaSouradnicEventHandler (object sender, EventArgs e);

7. přednáška - třídy, objekty třídy objekty atributy tříd metody tříd

Základy jazyka C# Obsah přednášky. Architektura.NET Historie Vlastnosti jazyka C# Datové typy Příkazy Prostory jmen Třídy, rozhraní

Semestrální práce 2 znakový strom

Čtvrtek 3. listopadu. Makra v Excelu. Obecná definice makra: Spouštění makra: Druhy maker, způsoby tvorby a jejich ukládání

Nápověda k aplikaci EA Script Engine

Program a životní cyklus programu

III/2 Inovace a zkvalitnění výuky prostřednictvím ICT

Průvodce instalací modulu Offline VetShop verze 3.4

Novinky. Autodesk Vault helpdesk.graitec.cz,

Úvod Třídy Rozhraní Pole Konec. Programování v C# Hodnotové datové typy, řídící struktury. Petr Vaněček 1 / 39

Transkript:

České vysoké učení technické v Praze Fakulta elektrotechnická ČVUT FEL katedra počítačů Bakalářská práce Zásuvný modul pro Microsoft Visual Studio Pavel Plasz Vedoucí práce: Ing. Tomáš Zahradnický Studijní program: Elektrotechnika a informatika strukturovaný bakalářský Obor: Informatika a výpočetní technika červenec 2007

ii

Poděkování Poděkovat bych chtěl především vedoucímu bakalářské práce Ing. Tomáši Zahradnickému za trpělivou pomoc při jejím psaní. Dále děkuji všem svým přátelům, kteří mě podporovali v psaní mého zatím nejrozsáhlějšího díla. iii

iv

Prohlášení Prohlašuji, že jsem svou bakalářskou práci vypracoval samostatně a použil jsem pouze podklady uvedené v přiloženém seznamu. Nemám závažný důvod proti užití tohoto školního díla ve smyslu 60 Zákona č. 121/2000 Sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých zákonů (autorský zákon). V Praze dne 22.8. 2007............................................................. v

vi

Abstract The bachelor thesis discusses Microsoft Visual Studio add-in module creation. A sample, step by step created add-in demonstrates the essentials of COM programming and extends the development environment with a new functionality. The resulting DLL library extends Microsoft Visual Studio by adding an option to convert source code from C/C++ language to the assembly language, which is consequently displayed. The add-in is easily accessible via a Disassemble command shown in the contextual menu of any C or CPP file in the Solution Explorer window. Abstrakt Bakalářská práce pojednává o tvorbě zásuvného modulu pro vývojové prostředí Microsoft Visual Studio. Vysvětlen je princip programování s využitím technologie COM na příkladu kompletního modulu vytvořeného jako součást této práce. Výsledný modul v podobě DLL knihovny rozšiřuje funkce Microsoft Visual Studia o možnost převést zdrojový kód z jazyka C/C++ do jazyka symbolických instrukcí a ten následně zobrazit. Akce je snadno přístupná jako položka Disassemble v rozevírací nabídce libovolného C nebo CPP souboru v okně Solution Explorer. vii

viii

Obsah Seznam obrázků a tabulek xi 1 Úvod 1 2 Analýza a návrh řešení 3 2.1 Forma řešení..................................... 3 2.2 Objektový soubor................................... 3 2.3 Převod do assembleru................................ 3 2.4 Uživatelské rozhraní................................. 4 2.5 Instalace........................................ 5 3 Zásuvné moduly pro Microsoft Visual Studio 6 3.1 Rozšiřitelnost (Visual Studio Extensibility).................... 6 3.1.1 Makra..................................... 6 3.1.2 Zásuvné moduly............................... 6 3.2 Komponentový model COM............................. 6 3.2.1 Rozhraní komponent............................. 7 3.2.2 Rozhraní IUnknown.............................. 7 3.2.3 Počítání odkazů................................ 8 3.2.4 Identifikátor GUID.............................. 10 3.2.5 Datový typ HRESULT............................. 11 3.2.6 Registrace komponent............................ 11 3.2.7 Chytré ukazatele............................... 12 3.2.8 Rozhraní IDispatch............................. 12 3.3 Hierarchie komponent Microsoft Visual Studia.................. 14 3.4 Vytváření zásuvného modulu............................ 15 3.4.1 Průvodce pro vytváření kostry add-inu................... 15 3.4.2 Rozhraní _IDTExtensibility2....................... 18 3.4.3 Přidávání prvků do uživatelského rozhraní (IDTCommandTarget).... 19 3.4.4 Reakce na události (IDispEventImpl)................... 20 3.5 Registrace zásuvného modulu............................ 20 4 Implementace 21 4.1 Microsoft Visual Studio 8.0 (2005)......................... 21 4.1.1 Inicializace zásuvného modulu (metoda CConnect::OnConnection()). 22 4.1.2 Nastavení stavu ovládacího prvku (metoda CConnect::QueryStatus()) 23 4.1.3 Provedení příkazu (metoda CConnect::Exec()).............. 24 4.1.4 Třída CBuildEvents obsluhující překladové události........... 25 4.1.5 Obsluha dokončení překladu (metoda CBuildEvents::OnBuildDone()) 25 4.2 Microsoft Visual Studio 7.1 (2003)......................... 27 4.3 NSIS instalátor.................................... 27 5 Závěr 29 6 Literatura 30 A Hierarchie komponent Microsoft Visual Studia 33 B Obsah přiloženého CD 34 ix

x

Seznam obrázků 2.1 Solution Explorer kontextová nabídka s položkou Disassemble........ 4 3.1 Formát datového typu HRESULT........................... 11 3.2 Spuštění průvodce pro vytvoření add-inu...................... 15 3.3 Průvodce pro vytvoření add-inu - krok č. 1.................... 16 3.4 Průvodce pro vytvoření add-inu - krok č. 2.................... 16 3.5 Průvodce pro vytvoření add-inu - krok č. 3.................... 17 3.6 Průvodce pro vytvoření add-inu - krok č. 4.................... 17 4.1 Komponenty využívané zásuvným modulem Disassembler............ 22 5.1 Ukázka použití zásuvného modulu Disassembler.................. 29 A.1 Hierarchie komponent Microsoft Visual Studia.................. 33 Seznam tabulek 3.1 Popis projektových souborů add-inu........................ 18 xi

xii

KAPITOLA 1. ÚVOD 1 1 Úvod Microsoft Visual Studio patří v současné době k jednomu z nejrozšířenějších vývojových prostředí pro platformu Microsoft Windows. Má mnoho funkcí, které usnadňují programátorům práci a umožňuje tvorbu rozsáhlých projektů. Distribuce Microsoft Visual Studia obsahuje kompilátory pro všechny programovací jazyky, které podporuje, tj. C/C++, C#, Visual Basic a J# z nichž dlouhodobě nejvíce používaným je jazyk C++. Hardwarová náročnost vyvíjené aplikace velmi záleží na programátorovi. To platí zejména u tzv. neřízeného kódu (unmanaged code), ve kterém se uvolňování paměti provádí explicitně ve zdrojovém kódu 1, a po přeložení do nativního kódu procesoru může program běžet rychleji. Pro vývojáře je proto důležité vědět, jak se jeho kód zapsaný v C/C++ překládá. Kvalita kódu je ovlivněna algoritmem a použitím vhodných klíčových slov, ale také nastavením přepínačů překladače. Kromě integrovaných kompilátorů od Microsoftu lze používat i jiné překladače, které produkují různý výsledný kód. Pro správné fungování přeloženého programu tedy nezáleží na tom jaký kód je vygenerován, stačí když překladač dodržuje ABI (Application Binary Interface), tj. specifikaci rozhraní mezi aplikací a operačním systémem, která definuje způsob předávání parametrů funkcím, vlastnosti datových typů, organizaci paměti, používání registrů procesoru a podobně. Například chceme-li efektivně provádět operace s vektory na procesorech Intel 2, je vhodné použít překladač Intel C++ Compiler a správně nastavit využívání SIMD (Single Instruction Multiple Data) instrukcí poskytovaných v rozšířeních SSE, SSE2, SSE3 a SSE4, což znamená dobře zvolit z více než sta přepínačů. Pro nalezení optimální konfigurace je potřeba reagovat na změny přeloženého kódu. Správným nastavením přepínačů lze docílit velmi výrazného zrychlení běhu vyvíjené aplikace. Sledováním přeloženého kódu je také možné odhalovat souběhové chyby (tzv. race condition). Například máme-li v programu cyklus podmíněný hodnotou proměnné, kterou uvnitř tohoto cyklu nijak neměníme, překladač považuje za zbytečné ji při každém testování číst z paměti a považuje ji za konstantu. Podmínka v cyklu je pak bud úplně vynechána (pokud by při porovnávání byla vždy splněna) anebo je vynechán celý cyklus (pokud by naopak nebyla splněna nikdy). Programátor ale očekává změnu proměnné v paměti způsobenou například jiným vláknem nebo obsluhou přerušení. Taková chyba by se ve zdrojovém kódu hledala špatně, ale při pohledu na procesorové instrukce vytvořené kompilátorem je chyba vidět okamžitě a lze ji ošetřit přidáním klíčového slova volatile k deklaraci proměnné. Příklad fragmentu kódu v jazyce C++ obsahující tuto souběhovou chybu (smyčka čeká na snížení hodnoty proměnné cislo v paměti): int cislo = 5, cislo2 = 0; while (cislo > 4) { cislo2 ++; } printf("%d", cislo2); V jazyce symbolických instrukcí po přeložení s nejvyšší optimalizační úrovní je kód uplně vynechán a provádí se pouze jednoduchá nepodmíněná smyčka: _main: 00000000: jmp _main 1 u řízeného kódu (managed code) provádí automatickou správu paměti tzv. Garbage Collector 2 procesory Intel jsou dnes běžně vícejádrové

2 KAPITOLA 1. ÚVOD Po přeložení se stejným nastavením, ale proměnná je deklarována volatile int cislo = 5, nedojde k vynechání kódu a cyklus skončí pokud bude proměnná v paměti snížena jiným vláknem: 00000003: mov dword ptr [esp+4],5 0000000B: mov dword ptr [esp],0 00000012: mov ecx,dword ptr [esp+4] 00000016: mov eax,4 0000001B: cmp ecx,eax 0000001D: jle 0000002D 0000001F: nop 00000020: mov ecx,dword ptr [esp] 00000023: inc ecx 00000024: mov dword ptr [esp],ecx 00000027: cmp dword ptr [esp+4],eax 0000002B: jg 00000020 0000002D: xor eax,eax Na adrese 00000027 instrukce cmp (compare) porovnává hodnotu proměnné cislo, což určuje zda následující instrukce jg provede skok zpět, tj. další iteraci cyklu. I přes vysoký stupeň optimalizace je díky klíčovému slovu volatile hodnota operandu instrukce cmp získávána vždy z paměti. Z kódu je také vidět, že použitý překladač Microsoft Visual Studia neoptimalizoval kód nejlépe, nebot například instrukce na adrese 00000012 zbytečně kopíruje hodnotu proměnné cislo do registru, přitom následné porovnání mohlo být provedeno přímo s hodnotou z paměti. Přímé zobrazení přeloženého zdrojového kódu Microsoft Visual Studio neumožňuje. Umí ho zobrazit jen po spuštění programu, a to vždy celý kód vzniklý spojením všech zdrojových souborů pomocí linkeru. Navíc ve verzi 7.1 (2003), která je dnes stále hojně používaná, je zobrazení dosti nepřehledné (pouze čistý kód bez názvů funkcí). Naštěstí je toto vývojové prostředí modulární, skládá se z komponent COM[1] modelu a podporuje přidání vlastních COM objektů, které mohou implementovat nové funkce a využívat již existující komponenty. Bakalářská práce si klade za cíl tento nedostatek ve Visual Studiu opravit vytvořením zásuvného modulu. Cílem zásuvného modulu je umožnit programátorovi kdykoliv snadno zjistit jak bude přeložený zdrojový kód vypadat v jazyce symbolických instrukcí assembleru.

KAPITOLA 2. ANALÝZA A NÁVRH ŘEŠENÍ 3 2 Analýza a návrh řešení 2.1 Forma řešení Při vyvíjení aplikace v Microsoft Visual Studiu může vzniknout potřeba vidět výstup kompilátoru celkem často. Při psaní částí kódu, které pracují s větším objemem dat, nebo cyklů s velkým počtem iterací záleží na každé instrukci, kterou bude procesor provádět. Při každé změně algoritmu nebo při změně nastavení kompilátoru je dobré zjistit zda došlo ke zlepšení efektivity přeloženého programu, tj. zda vykonání stejné požadované operace vyžaduje méně systémových prostředků. Pro přesné analyzování výkonu vyvíjeného programu se používají tzv. profilery. Profilerů je celá řada a fungují na různých principech měření. Například profilery na principu vzorkování zjišt ují v pravidelných časových intervalech o kolik se zvýšil čítač programových instrukcí (program counter) a podle toho pak lze odhalit, v kterých místech se program nejvíce zdržel. Dalším způsobem analýzy je měření času trvání provádění podprogramů profiler reaguje na instrukce volání a návratu z podprogramu a je schopen vypočítat jak velkou poměrnou část času běhu programu jednotlivé podprogramy trvaly. Pro optimalizaci programu je výhodné sledovat nejnáročnější části kódu v jazyce symbolických instrukcí a ty pak vhodně upravit. Zobrazení přeloženého kódu by tedy mělo být snadno přístupné a jednoduché, aby programátora zbytečně nezdržovalo. Používání další aplikace je zbytečně složité, a proto ideálním řešením, které Microsoft Visual Studio nabízí, je forma zásuvného modulu pracujícího na principu technologie COM (tzv. add-inu). Výhodou COM modelu je možnost využití komponent, které jsou již součástí vývojového prostředí. Konkrétně pro získání kódu v jazyce assembler je potřeba nejprve objektový soubor (object file), který vznikne kompilací zdrojového souboru v jazyce C/C++. Pro provedení kompilace lze využít komponentu Microsoft Visual Studia včetně jejího nastavení ve vývojovém prostředí, což zaručuje překlad stejný jako při vývoji. 2.2 Objektový soubor Objektový soubor obsahuje především instrukce a data přeloženého souboru v binární podobě. Dále soubor obsahuje tabulku se seznamem míst v objektovém kódu, na kterých se vyskytuje přímá adresace paměti (například instrukce skoku nebo load/store instrukce) nebo volání podprogramů definovaných v jiných zdrojových souborech, jejichž adresy zatím nejsou známy. Při spojování více objektových souborů linkerem jsou tyto adresy dopočítány tak, aby odkazovaly na odpovídající části kódu ve výsledném souboru. Neméně důležitou součástí souboru jsou ladící informace (debug information) jako je tabulka globálních symbolů definovaných ve zdrojovém souboru, lokální symboly, čísla řádek původního zdrojového kódu a případně i další informace. 2.3 Převod do assembleru Instrukce a operandy v objektovém souboru jsou v binárním tvaru a těžko by se v nich programátor vyznal, proto je nutné je převést do čitelné podoby v jazyce symbolických instrukcí assembleru. Například instrukce ve strojovém kódu procesorů Intel x86/i386[7] 10110000 01100001 bude převedena na instrukci assembleru mov al, 61h Provádění tohoto převodu není předmětem bakalářské práce, místo toho lze s výhodou využít např. externí programy nm[6] nebo objdump[6], které jsou pod GNU licencí a jejich použití je

4 KAPITOLA 2. ANALÝZA A NÁVRH ŘEŠENÍ zdarma. Vhodnější ale bude použít program dumpbin[3], který je součástí distribuce Microsoft Visual Studia, a také umožňuje převod do jazyka symbolických instrukcí. Do výstupu, který tento program produkuje, ještě přidává označení názvů funkcí, jejichž tělo je v assembleru zobrazeno, což zlepšuje orientaci v kódu. Názvy symbolů jsou získávány taktéž z objektového souboru. Vygenerovaný kód je zapisován do souboru s názvem shodným se zdrojovým souborem s příponou ASM. 2.4 Uživatelské rozhraní Spuštění překladu, následné převedení do assembleru a zobrazení bude přístupné kliknutím na položku Disassemble, která bude k dispozici v kontextové nabídce každého projektové souboru s příponou C, nebo CPP. V Microsoft Visual Studiu jsou všechny soubory vyvíjeného projektu zobrazeny v okně Solution Explorer, kde jsou rozdělené do kategorií a dále ve stromové struktuře podle složek, v kterých jsou umístěny. Snadno zde vybereme požadovaný soubor, na kterém kliknutím pravým tlačítkem myši vyvoláme jeho nabídku proveditelných akcí. Vytvořený kód se zobrazí otevřením ASM souboru přímo v textovém editoru Microsoft Visual Studia. Obrázek 2.1: Solution Explorer kontextová nabídka s položkou Disassemble

KAPITOLA 2. ANALÝZA A NÁVRH ŘEŠENÍ 5 2.5 Instalace Zásuvné moduly pro Microsoft Visual Studio jsou uloženy jako dynamicky linkované knihovny, které poskytují COM servery. Knihovnu je před použitím nutné zaregistrovat, aby ji mohlo vývojové prostředí využívat. K tomu lze využít program regsvr32, který je součástí operačního systému Microsoft Windows. Více o registraci COM serverů je popsáno v následující kapitole. Pro Microsoft Visual Studio verze 7.1 (2003) je navíc nutné přidat záznam do registrů Windows, aby se add-in objevil ve správci zásuvných modulů a bylo možné jej aktivovat. Dále je před používáním add-inu nutné vytvořit dávkový soubor, který bude nastavovat hodnotu systémové proměnné PATH a spustí program dumpbin se správnými parametry. Verze DLL knihovny a obsah dávkového souboru jsou pro Microsoft Visual Studio ve verzi 7.1 (2003) a verzi 8.0 (2005) odlišné a je nutné je volit podle hodnot zjištěných v registrech Windows. Aby toto všechno proběhlo automaticky a uživatel add-inu jej mohl snadno začít používat, je rozumné využít instalátor, který provede výše uvedené akce sám. Vhodným řešením je NSIS (Nullsoft Scriptable Install System)[4], který umožňuje zkopírování a registraci DLL knihovny, čtení a zápis do registrů Windows, větvení na základě přečtených hodnot i zápis do textového souboru (pro vytvoření dávky), tedy vše potřebné pro instalaci zásuvného modulu i většiny dalších aplikací. Navíc je zcela zdarma pro libovolné použití.

6 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 3 Zásuvné moduly pro Microsoft Visual Studio 3.1 Rozšiřitelnost (Visual Studio Extensibility) Přestože Microsoft Visual Studio poskytuje velké množství funkcí, při vývoji nejrůznějších projektů je někdy potřeba provádět akce, které toto vývojové prostředí umožňuje řešit provedením několika dílčích kroků, případně s nutností využít další vývojářské nástroje. V případech, kdy by se takových kroků mělo provádět více a celý sled akcí by se vícekrát opakoval, je výhodnější takový proces vhodným způsobem automatizovat a rozšířit Microsoft Visual Studio o možnost provádět takové úlohy automaticky. Microsoft Visual Studio nabízí dvě základní metody rozšíření makra a zásuvné moduly. 3.1.1 Makra Vytvoření makra je ve Visual Studiu podobné jako u většiny jiných aplikací podporujících makra. Jednoduše se spustí zaznamenávání, pak se provede sled akcí, které se mají automatizovat, a zaznamenávání se ukončí. Jednotlivé kroky jsou uloženy v podobě příkazů jazyka Visual Basic. Zaznamenané makro pak můžeme kdykoliv spustit a proces je opět proveden, například s jiným dokumentem. Tento způsob je velmi snadný a rychlý a v některých případech zcela dostačující, nicméně možnosti nejsou tak široké jako při použití zásuvného modulu. 3.1.2 Zásuvné moduly Zásuvný modul je dynamická knihovna provádějící operace pro program, který ji používá. Z pohledu uživatele se ovládání funkcí poskytovaných zásuvným modulem nemusí nijak lišit od ovládání základního programu. Ve skutečnosti Visual Studio jako celek je složeno z mnoha dílčích komponent, které spolu vzájemně komunikují a jsou využívány klientskou aplikací, která nám poskytuje uživatelské rozhraní. Add-in je tedy přídavná komponenta k modulární aplikaci, v našem případě k Microsoft Visual Studiu fungujícím na technologii COM. 3.2 Komponentový model COM COM (Component Object Model) je specifikace určující jakým způsobem vytvářet aplikace skládájící se z více nezávisle pracujících modulů COM komponent. COM komponenty obsahují proveditelný kód v podobě dynamicky linkované knihovny (typicky s příponou DLL), nebo jako spustitelný soubor (s příponou EXE)[5]. Aplikace složená z COM komponent umožňuje uživateli pracovat více podle jeho představ, nebot může volit z více komponent poskytujících podobné služby, instalovat nové moduly rozšiřující aplikaci o nové funkce nebo naopak v nové aplikaci využívat komponentu, na kterou je již zvyklý z jiného programu (typickým příkladem je textový editor použitý v různých aplikacích, který se může vyvíjet nezávisle). Kdybychom například chtěli vyvíjet aplikaci obsahující prohlížeč webových stránek, stačí připojit komponentu Internet Explorer k našemu programu a předávat jí URL požadovaného HTML dokumentu. Komponenta prohlížeče již sama provede načtení dat a zobrazení stránky. Pokud je splněna specifikace COM je zaručeno, že komponenty spolu budou umět pracovat. Komponenty mohou být vyvíjeny v téměř libovolném procedurálním programovacím jazyku, nebot se distribuují vždy přeložené v binárním tvaru. Pro správné fungování COM komponent je nutné dodržet dvě základní podmínky. Komponenty vždy musí být linkovatelné dynamicky, tj. kód jimi poskytovaných služeb se do paměti nahrává až když je potřeba a po jeho použití je možné jej z paměti odstranit. To je důležité, nebot statické linkování by vyžadovalo přelinkování celého programu a jednotlivé moduly by

KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 7 se pak nemohly nezávisle vyvíjet. Dynamické linkování umožňuje připojovat komponenty za běhu aplikace a navíc může šetřit pamět. Aby mělo dynamické linkování smysl a bylo možné libovolnou komponentu odpojit a nahradit ji za jinou bez nutnosti znovu kompilovat a linkovat celou aplikaci, je nutné kód komponenty zapouzdřit, tj. skrýt vlastní kód komponenty za určité jednotné rozhraní, přes které se k ní program nebo jiná komponenta připojuje. Pokud takové rozhraní zůstane zachováno, připojovaná komponenta se může jakkoliv změnit a program ji bude moci nadále používat. 3.2.1 Rozhraní komponent Aby se program nebo komponenta, tzv. COM klient, mohl připojit k jiné komponentě, tzv. COM serveru, a mohl ji začít využívat, musí s ní umět komunikovat. Interakce mezi COM objekty je umožněna pomocí COM rozhraní, což je vlastně struktura v paměti obsahující pole ukazatelů na funkce implementované komponentou. V jazyce C++ jsou rozhraní tvořeny abstraktní třídou, jejíž virtuální metody jsou implementovány v potomcích. Třída komponenty tedy musí být potomkem nějakého rozhraní, případně může být zděděna i z více rozhraní najednou. Naopak více komponent může dědit ze stejného rozhraní a díky polymorfismu tak získáme možnost manipulovat stejným způsobem s různými komponentami[2], což je právě ta stěžejní vlastnost COM modelu, která umožňuje nahrazovat komponenty za jiné, ke kterým se přistupuje shodně. V praxi při tvorbě modulárních aplikací vzniká celá hierarchie tříd, které definují různá rozhraní. Implementace těchto rozhraní je již formou COM objektů, které lze nezávisle zaměňovat. Uživatel možnost změny komponenty vždy mít nemusí, ale aplikace může této vlastnosti sama využívat pro svou aktualizaci, nebot se nemusí nahrazovat celá, ale pouze modifikované komponenty. 3.2.2 Rozhraní IUnknown Rozhraní IUnknown musí být předkem všech definovaných rozhraní, což zaručí, že každá COM komponenta bude implementovat jeho virtuální metody AddRef(), QueryInterface() a Release(). Rozhraní IUnknown je definováno takto: class IUnknown { public: virtual HRESULT stdcall QueryInterface( const IID& iid, void** ppv ) = 0; virtual ULONG stdcall AddRef() = 0; virtual ULONG stdcall Release() = 0; }; Metody AddRef() a Release() budou vysvětleny v sekci 3.2.3. Metoda QueryInterface() slouží k získání ukazatele na požadované rozhraní. Každý COM objekt musí tuto metodu implementovat tak, aby pomocí ní bylo možné získat libovolné požadované rozhraní, které komponenta implementuje. Prvním parametrem určíme jaké rozhraní potřebujeme, druhý parametr je výstupní a v případě úspěchu obsahuje ukazatel na dotazované rozhraní. Abychom mohli volat metodu QueryInterface() je potřeba nejdříve vytvořit instanci COM objektu. Vytváření instance musí probíhat na straně COM serveru, nebot klient vůbec implementační třídu nezná. Pokud bychom sami připojovali komponentu načtením dynamické knihovny, řešením by bylo volat vhodně vytvořenou statickou metodu exportovanou knihovnou, která by vytvořila instanci COM objektu a vrátila by ukazatel na ni přetypovaný na IUnknown*. Díky polymorfismu klient může pomocí získaného ukazatele na rozhraní IUnknown získávat ukazatele na další implementovaná rozhraní a komponentu ovládat. V praxi je výhodnější použít funkci COM knihovny s názvem CoCreateInstance(), která je deklarována takto:

8 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO HRESULT stdcall CoCreateInstance( const CLSID& clsid, IUnknown * piunknownouter, DWORD dwclscontext, const IID& iid, void ** ppv ); Prvním parametrem je clsid, což je unikátní identifikátor komponenty. Druhý parametr je ukazatel na rozhraní IUnknown vnější komponenty, které má být zpřístupněna vnitřní komponenta se zadaným CLSID. Tímto způsobem 1 se nahrazuje dědičnost, která mezi COM objekty není možná běžnými konvencemi C++ (nebot technologie COM je jazykově nezávislá). Třetí parametr dwclscontext určuje zda se bude kód komponenty provádět ve stejném procesu jako klient, nebo v jiném procesu na lokálním počítači, nebo v jiném procesu vzdáleně. Čtvrtý parametr iid je identifikátor rozhraní, kterým chceme s komponentou pracovat. Poslední parametr je výstupní a při úspěchu obsahuje ukazatel na hledané rozhraní. Návratová hodnota typu HRESULT je nastavena v případě úspěšného nalezení rozhraní na S_OK, v opačném případě na hodnotu konstanty popisující chybu. Funkce CoCreateInstance() nejprve vyhledá záznam v registrech Windows odpovídající zadanému CLSID a zjistí potřebné informace 2 o komponentě, na základě kterých načte dynamickou knihovnu do paměti a vytvoří instanci COM objektu. Pak se pomocí metody QueryInterface() dotazuje komponenty na rozhraní s identifikátorem IID, které je vráceno v pátém parametru funkce. 3.2.3 Počítání odkazů Vytvořenou instanci komponenty je vhodné odstranit z paměti, pokud již přestane být zapotřebí, tj. pokud již neexistuje žádný odkaz na některé z rozhraní, která komponenta implementuje. Aby COM objekt měl přehled o tom, zda existuje odkaz na některé z jeho rozhraní, využívá metodu zvanou počítání odkazů (reference counting), tj. komponenta si udržuje v proměnné počet existujících odkazů. Každá funkce, která vrací ukazatel na rozhraní komponenty (minimálně QueryInterface() je takovou funkcí), musí volat metodu komponenty AddRef(), která inkrementuje hodnotu počtu odkazů. Pokud klient již použité rozhraní nepotřebuje, měl by volat metodu Release(), která hodnot počtu odkazů sníží a v případě, že dosáhne nulové hodnoty, postará se o odstranění instance COM objektu z paměti. Implementace metod AddRef() a Release() je vyžadována při tvorbě komponenty z důvodu dědění z rozhraní IUnknown. Následující příklad zdrojového kódu v jazyce C++ znázorňuje vytvoření dvou COM rozhraní a komponenty, která tyto rozhraní implementuje. Z ukázky je patrný princip počítání odkazů a možný způsob implementace metod rozhraní IUnknown. Názvy rozhraní začínají písmenem I (interface) a názvy tříd s implementací písmenem C (class), což odpovídá konvencím programování COM aplikací. 1 tato metoda se nazývá aggregation 2 informace uložené v registrech Windows jsou detailněji popsány v sekci 3.2.6

KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 9 Nejprve ukázka definice COM rozhraní: class IZvire : public IUnknown { public: virtual int stdcall get_rasa() = 0; }; class IVzhled : public IUnknown { public: virtual int stdcall get_barva() = 0; }; Fragment třídy implementující rozhraní IZvire a IVzhled: class CKocka : public IZvire, public IVzhled { public: CKocka() { m_cref = 0; } virtual ~CKocka() { } virtual int stdcall get_rasa() { return R_KOCKA_DIVOKA; } virtual int stdcall get_barva() { return B_CERNA; } // metoda pro získání ukazatele na implementované rozhraní virtual HRESULT stdcall QueryInterface( const IID& iid, void** ppv ) { if (iid == IID_IUnknown) { *ppv = (IUnknown *) this; } else if (iid == IID_IZvire) { *ppv = (IZvire *) this; } else if (iid == IID_IVzhled) { *ppv = (IVzhled *) this; } else { *ppv = NULL; return E_NOINTERFACE; } ((IUnknown *) *ppv)->addref(); return S_OK; } // zvýšení počtu existujících odkazů na rozhraní virtual ULONG stdcall AddRef() { return InterlockedIncrement(&m_cRef); } // snížení počtu existujících odkazů na rozhraní // a případné odstranění objektu z paměti virtual ULONG stdcall Release() { if (InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return m_cref; } protected: long m_cref; };

10 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO Pro zajištění atomicity zvyšování a snižování hodnoty proměnné m_cref jsou použity Win32 API funkce InterlockedIncrement() a InterlockedDecrement(), které umožní přístup k proměnné m_cref vždy jen jednomu vláknu. Implementace rozhraní mohou být libovolné, například rozhraní IVzhled může implementovat komponenta CAutomobil a klient používající toto rozhraní je schopen stejným způsobem zjistit barvu jak kočky, tak automobilu, aniž by věděl další informace o konkretní připojené komponentě. Pro kompletnost ještě demonstrace použití komponenty z příkladu (předpokladem je registrace COM serveru v operačním systému, aby funkce CoCreateInstance() mohla komponentu načíst a vytvořit její instanci): int rasa, barva; IZvire * pizvire = NULL; if (SUCCEEDED(CoCreateInstance(CLSID_Kocka, NULL, CLSCTX_INPROC_SERVER, IID_IZvire, (void **) &pizvire))) { rasa = pizvire->get_rasa(); IVzhled * pivzhled = NULL; if (SUCCEEDED(pIZvire->QueryInterface(IID_IVzhled, (void **) &pivzhled))) { barva = pivzhled->get_barva(); pivzhled->release(); } pizvire->release(); } Nejprve je funkcí CoCreateInstance() vytvořena instance COM komponenty s identifikátorem CLSID_Kocka a získáno rozhraní, které je poté použito k volání metody komponenty. Zda získání rozhraní proběhlo úspěšně je zjištěno pomocí makra SUCCEEDED(), které testuje návratovou hodnotou typu HRESULT. Následně je metodou QueryInterface() získáno další implementované rozhraní a to je také použito. Nakonec postupným voláním metody Release() jsou uvolněny oba odkazy na rozhraní a provedena destrukce objektu. 3.2.4 Identifikátor GUID Již zmiňované identifikátory rozhraní (IID) a identifikátory komponent (CLSID) jsou datové typy GUID. GUID (globally unique identifier) je 128-bitová struktura, která je globálně unikátní a lze pomocí ní určit jedno konkrétní rozhraní nebo komponentu na celém světě. Při vývoji nové komponenty nebo rozhraní je potřeba použít takový identifikátor GUID, který ještě neexistuje. K tomuto účelu slouží program guidgen, který vygeneruje unikátní klíč. Aby byla zajištěna jednoznačnost místa i času, 48 bitů z generovaného GUID obvykle odpovídá adrese sít ové karty v počítači, na kterém je guidgen spuštěn. Dalších 60 bitů kódu reprezentuje počet 100-nanosekundových intervalů od 15. října 1582. Zbývajících 20 bitů je generováno náhodně. Program guidgen je součástí Microsoft Visual Studia ve verzi dialogové i konzolové aplikace (uuidgen).

KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 11 3.2.5 Datový typ HRESULT Datový typ HRESULT je 32-bitové číslo nesoucí informaci o úspěšnosti vykonávané funkce. Hodnota HRESULT je rozdělena do tří částí: Obrázek 3.1: Formát datového typu HRESULT Nejvýznamnější je první bit nazývaný Severity, který indikuje zda došlo k chybě. Pokud je hodnota 0, znamená to úspěšné provedení funkce. Hodnota 1 označuje, že došlo k chybě, která je blíže specifikována v dalších částech. Následujících 15 bitů je označováno jako Facility a představují identifikátor části systému, která vrací návratový kód obsažený ve zbývajících 16 bitech. Microsoft definuje univerzální hodnoty facility kódů v souboru WINERROR.H (například FACILITY_WIN32, FACILITY_RPC, atd.). Poslední 16-bitová část obsahuje již zmiňovaný návratový kód (Return Code), který přesně specifikuje chybu, a opět používá hodnoty definované v souboru WINERROR.H. Kromě návratových kódů definovaných Microsoftem je možné definovat vlastní kódy, které nemusí být unikátní globálně, ale pouze v rámci rozhraní, které ho vrací. Vlastní návratové kódy musí mít v hodnotě HRESULT obsažen Facility kód FACILITY_ITF, což indikuje, že návratový kód je závislý na rozhraní. Pro jednoduchou práci s hodnotou typu HRESULT, aniž bychom si museli pamatovat jaká hodnota severity bitu označuje úspěch či neúspěch, slouží makra SUCCEEDED() a FAILED(), která již podle názvu napovídají o svém významu. Makro SUCCEEDED() nabývá hodnoty true pokud je hodnota HRESULT nezáporné číslo. Makro FAILED() je true pro záporné číslo (chyba). 3.2.6 Registrace komponent Pro připojení COM komponenty do klientské aplikace je potřeba načíst dynamicky linkovanou knihovnu, což se v praxi provádí nejčastěji použitím funkce CoCreateInstance(), které předáme identifikátor CLSID určující vyžadovanou komponentu. Pro načtení souboru knihovny je potřeba, aby komponenta byla registrována v operačním systému, tj. aby měla záznam v registrech Windows obsahující informaci o umístění a názvu DLL souboru. Automatickou registraci lze provést pomocí programu regsvr32, který volá funkci DllRegisterServer(), kterou musí knihovna implementovat a exportovat. Stejným způsobem lze program použít pro zrušení registrace s využitím funkce DllUnregisterServer(). Implementace funkce DllRegisterServer() závisí na autorovi komponenty, nicméně musí dodržet strukturu záznamu, který přidává do registrů Windows. Pro práci s registry existují Win32 API funkce, které jsou přístupné po vložení hlavičkového souboru WINREG.H do zdrojového kódu a přilinkování souboru ADVAPI32.LIB. Informace o COM komponentách se v registrech ukládájí do větve HKEY_CLASSES_ROOT (HKCR) do klíče CLSID, jehož podklíče jsou veškeré registrované CLSID identifikátory v operačním systému. Každý CLSID klíč obsahuje jako svou výchozí hodnotu název komponenty, dále obsahuje podklíče InprocServer32, ProgID, VersionIndependentProgID a další. Nejdůležitější je klíč InprocServer32, který musí být nastaven vždy, když je komponenta tzv. in-proc server, tj. nahrává se do procesu COM klienta, kterému poskytuje své služby. Výchozí hodnota klíče InprocServer32 je úplná cesta umístění souboru DLL knihovny komponenty. ProgID je řetězec mapovaný na klíč CLSID, který je pro programátora lépe čitelný, ale není u něj zaručena

12 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO unikátnost. Využívá se například k identifikaci komponent při vývoji v jazyce Visual Basic. Formát ProgID je podle konvencí COM ve tvaru Program.Komponenta.Verze, ale nebývá to vždy dodrženo. Například komponenta vyvíjená v této bakalářské práci svou registrací vytvoří záznamy v registrech takto: HKCR\CLSID\{9B250574-1E2A-4F31-9A06-FA9DEE9668A2}\InprocServer32 @="C:\\Program Files\\Disassembler add-in for Visual Studio\\Disassembler.dll" HKCR\CLSID\{9B250574-1E2A-4F31-9A06-FA9DEE9668A2}\ProgID @="Disassembler.Connect.1" 3.2.7 Chytré ukazatele Chytrý ukazatel (Smart Pointer) je třída, která přetěžuje operátor ->, aby vracel hodnotu ukazatele na jiný objekt. Použití chytrého ukazatele je stejné jako použití běžného ukazatele v C++, navíc ale třída chytrého ukazatele může provádět další operace s objektem, na který ukazuje. V případě chytrého ukazatele na COM rozhraní jeho třída implementuje metodu počítání odkazů, tj. konstruktor volá metodu AddRef() komponenty a destruktor metodu Release(), automaticky je volána metoda QueryInterface() v těle kopírujícího konstruktoru a při přiřazení hodnoty z jiného rozhraní. Při práci s COM rozhraním již není potřeba volat metodu Release(), o uvolňování komponent z paměti se třída chytrého ukazatele postará sama. Chytrý ukazatel na rozhraní definovaný jako šablona třídy lze použít univerzálně k libovolnému rozhraní, stačí pouze předat název třídy jako parametr šablony při jeho deklaraci. Knihovna ATL (ActiveX Template Library) nabízí dvě generické třídy chytrých ukazatelů pro usnadnění práce s COM rozhraními CComPtr a CComQIPtr. Typicky se používají jako staticky deklarované objekty, které se zruší na konci bloku a jejich destruktor provede uvolnění rozhraní. 3.2.8 Rozhraní IDispatch Kromě možnosti pracovat s komponentou přes COM rozhraní, které s ní přímo komunikuje, existuje další způsob nazývaný Automation. Je také postaven na COM architektuře a je tvořen tzv. Automation serverem, což je COM komponenta implementující rozhraní IDispatch, přes které s ní komunikuje tzv. Automation controller, což je COM klient. Funkce COM serveru nejsou klientem volány přímo, ale pomocí členských metod rozhraní IDispatch. Nejdůležitějšími funkcemi, které rozhraní IDispatch poskytuje jsou GetIDsOfNames() a Invoke(). Funkci GetIDsOfNames() se v parametru předá název funkce komponenty, kterou chce COM klient provést, a vrácena je hodnota typu DISPID (Dispatch ID), což je číslo identifikující hledanou funkci. HRESULT GetIDsOfNames( REFIID riid, OLECHAR FAR* FAR* rgsznames, unsigned int cnames, LCID lcid, DISPID FAR* rgdispid ); První parametr riid je rezervován pro budoucnost a zatím musí být vždy nastaven na IID_NULL. Druhý parametr rgsznames je pole názvů požadovaných funkcí, jejichž počet je předán v dalším parametru cnames. Následuje lcid, kterým se volí lokalizace názvů. Například

KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 13 lze použít hodnotu vrácenou funkcí GetUserDefaultLCID(), což odpovídá lokalizačnímu nastavení uživatele v operačním systému. Poslední parametr rgdispid je výstupní a při úspěchu bude obsahovat DISPID. Pro provedení funkce se získaným DISPID slouží metoda Invoke(). HRESULT Invoke( DISPID dispidmember, REFIID riid, LCID lcid, WORD wflags, DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarresult, EXCEPINFO FAR* pexcepinfo, unsigned int FAR* puargerr ); První parametr je DISPID identifikující funkci, kterou chce klient volat. Dále je opět rezervovaný parametr riid, který je nutno nastavit na IID_NULL, a parametr lcid pro nastavení lokalizace. Čtvrtý parametr wflags určuje v jakém kontextu má být funkce volána. Může být volána jako běžná metoda, nebo jako funkce pro získání či nastavení vlastnosti objektu, která má název odpovídající zadanému názvu při předchozím volání metody GetIDsOfNames(). Následující parametr pdispparams je ukazatel na strukturu obsahující pole argumentů, které se mají předat volané funkci. Šestý parametr pvarresult je ukazatel na datový typ VARIANT, v kterém bude uložen výsledek vrácený volanou funkcí. V parametru pexcepinfo bude uložena informace o výjimce, pokud nějaká vznikne, což je indikováno hodnotou HRESULT vrácenou metodou Invoke(). V případě výjimky bude vráceno DISP_E_EXCEPTION, v ostatních případech struktura EXCEPINFO nebude naplněna. Poslední parametr puargerr, pokud Invoke() vrátí hodnotu DISP_E_PARAMNOTFOUND nebo DISP_E_TYPEMISMATCH, bude nastaven na index argumentu, který vyvolal chybu, tj. bud chyběl nebo byl nesprávného datového typu. Libovolné z posledních tří parametrů lze nastavit na NULL v případě, že klient takovou informaci získat nepotřebuje. Další metoda rozhraní IDispatch s názvem GetTypeInfo() slouží k zjišt ování informací o datových typech parametrů, které jsou předávány funkci vykonávané metodou Invoke(). Princip používání komponenty přes rozhraní IDispatch je jednoduchý. Stačí vytvořit instanci komponenty a získat ukazatel na rozhraní IDispatch, což lze provést obvyklým způsobem pomocí funkce CoCreateInstance(), a pak již kombinací metod GetIDsOfNames() a Invoke() vykonávat funkce komponenty na základě jejich názvu. Komponenta tedy nemusí implementovat žádné další rozhraní mimo IDispatch, nebot je dostačující pro ovládání všech metod COM serveru. Implementace metod rozhraní IDispatch může být realizována pomocí tzv. typové knihovny (type library), což je komponenta, která přes rozhraní ITypeLib poskytuje informace o metodách a vlastnostech Automation serveru získané z přeloženého IDL souboru. IDL soubor má podobný význam jako hlavičkové soubory v C++, ale je zapsán v jazyce MIDL 3 (Microsoft Interface Definition Language) a jeho přeložená podoba se distribuuje spolu s knihovnou komponenty (může být dokonce obsažena v DLL souboru). Vyvíjená komponenta při své inicializaci načte svou typovou knihovnu a v implementaci metod GetIDsOfNames() a Invoke() použije její metody totožného názvu a významu. Předpokládejme, že dříve uvedenená demonstrační komponenta CKocka nyní implementuje rozhraní IDispatch a informace o jejích metodách jsou poskytovány její typovou knihovnou. Použití komponenty pomocí metod rozhraní IDispatch se od přímého volání přes IZvire a 3 jazyk určený speciálně k definici rozhraní komponent, který je nezávislý na programovacím jazyku, ve kterém je vyvíjena komponenta

14 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO IVzhled bude výrazně lišit (pro větší přehlednost je v ukázce vynecháno testování návratových hodnot typu HRESULT): CLSID clsid; DISPID dispid; CLSIDFromProgID(L"Program1.Kocka", &clsid); IDispatch * pidispatch = NULL; CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (void **) &pidispatch); VARIANT pvarres; OLECHAR * nazevfunkce = L"get_Barva"; pidispatch->getidsofnames(iid_null, &nazevfunkce, 1, GetUserDefaultLCID(), &dispid); DISPPARAMS bezparametru = { NULL, NULL, 0, 0 }; pidispatch->invoke(dispid, IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, &bezparametru, &pvarres, NULL, NULL); Identifikátor komponenty CLSID je získán z ProgID funkcí CLSIDFromProgID(), poté je vytvořena instance komponenty a obdržen ukazatel na IDispatch. Metodou GetIDsOfNames() je získán DISPID jediné funkce s názvem get_barva. Následuje založení struktury DISPPARAMS, kterou se předají argumenty požadované funkci v tomto případě žádné nevyžaduje. Nakonec je volána metoda Invoke(), která zajistí provedení funkce get_barva a vrácení výsledku v podobě datového typu VARIANT. Funkce CLSIDFromProgID() slouží k zjišt ování CLSID identifikátorů komponenty z jejího ProgID. V registrech Windows ve větvi HKEY_CLASSES_ROOT jsou umístěny klíče s názvy ProgID všech komponent registrovaných v operačním systému. Hodnota CLSID je zapsána jako výchozí hodnota podklíče s názvem CLSID. Naopak pro zjišt ování hodnoty ProgID podle identifikátoru CLSID lze použít funkci ProgIDFromCLSID() s využitím položky ProgID v registrech popsané v sekci 3.2.6. 3.3 Hierarchie komponent Microsoft Visual Studia Microsoft Visual Studio je modulární aplikace založená na technologii COM. Jednotlivé dílčí celky (COM komponenty) mají za úkol zpracovávat množinu souvisejících úloh a k tomu mohou využívat další komponenty, které mohou využívat další a další. Takto vzniká velká hierarchie (viz. obrázek v dodatku A), která dohromady tvoří aplikaci Microsoft Visual Studio. Chceme-li vytvořit zásuvný modul, který tuto architekturu rozšíří, je nezbytné mít přehled o rozhraních, přes která je možné s komponentami Microsoft Visual Studia pracovat. Pro získání rozhraní při programování zásuvného modulu nemusíme nutně používat metodu QueryInterface(), nebot většina komponent svými členskými metodami umožňuje zpřístupnit rozhraní k dalším COM serverům, které původní komponenta používá a již vytvořila jejich instanci. Na nejvyšší úrovni hierarchie komponent se nachází objekt DTE 4, který je základem pro všechny ostatní komponenty. Prvním krokem při vyvíjení add-inu je tedy získání ukazatele na objekt DTE, který umožní získat další ukazatele na téměř všechny komponenty, at už přímo, nebo postupně přes meziobjekty, což znázorňuje následující obrázek. Microsoft Visual Studio ve verzi 8.0 (2005) obsahuje mnoho nových rozhraní, které jsou vylepšením předchozích verzí. Názvy nových rozhraní, metod atd. mají většinou stejný název jako jejich starší verze, ale na konec je přidána dvojka (například DTE2), aby bylo možné používat původní verze pod shodným názvem. Doporučeno je ovšem použití novějších verzí, která mají vylepšené funkce a zaručují vyšší kompatibilitu se současnými operačními systémy. V této pu- 4 objekt DTE je umístěn ve jmenném prostoru (namespace) s názvem EnvDTE

KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 15 blikaci jsou použity převážně názvy novějších verzí pro Microsoft Visual Studio 8.0 (2005), pokud není explicitně uvedeno, že se jedná o verzi pro Microsoft Visual Studio 7.1 (2003). 3.4 Vytváření zásuvného modulu Vytvořit zásuvný modul pomocí technologie COM obecně znamená implementovat metody rozhraní, které jsou speciálně vytvořeny pro tento účel. Microsoft Visual Studio nabízí pro zásuvné moduly rozhraní _IDTExtensibility2, jehož implementace zajišt uje základní životní funkce zásuvného modulu. Samotné rozhraní _IDTExtensibility2 by pro vytvoření užitečného addinu nestačilo, a proto je důležité implementovat další rozhraní s názvem IDTCommandTarget, které umožní provádět operace vyvolané akcí uživatele, například je možné reagovat na kliknutí na položku v menu podle jejího názvu. Samozřejmě je možné implementovat další rozhraní v závislosti na potřebách konkrétního vyvíjeného add-inu. Pro ulehčení práce při tvorbě zásuvného modulu (a obecně při tvorbě COM komponent) je výhodné použít tříd ATL knihovny, které implementují některé rutinní operace. Základem jsou třídy CComObjectRootEx a CComCoClass, které poskytují metody pro vytváření instancí třídy a počítání odkazů. 3.4.1 Průvodce pro vytváření kostry add-inu Pro vytvoření funkčního zásuvného modulu je nutné dodržet mnoho pravidel a příprava projektu před samotnou implementací jeho funkcí by byla ručně příliš zdlouhavá. Jednodušším řešením, které Microsoft Visual Studio nabízí, je vytvoření základního kódu automaticky pomocí průvodce, který vygeneruje projekt se všemi náležitostmi, které jsou potřeba pro přeložení a připojení add-inu. Autor zásuvného modulu si může kód upravit podle svých představ a následně přidávat vlastní kód, který bude vykonávat požadované operace. Průvodce pro vytvoření zásuvného modulu se spustí při vytváření nového projektu v Microsoft Visual Studiu a nastavení typu projektu na Visual Studio Add-in, který se nachází v kategorii Other Project Types/Extensibility. Obrázek 3.2: Spuštění průvodce pro vytvoření add-inu Kliknutím na tlačítko OK je spuštěn průvodce, který se postupně dotazuje na základní vlastnosti nově vytvářeného add-inu:

16 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 1. Nejprve se volí programovací jazyk, v kterém má být zásuvný modul vyvíjen C#, Visual Basic, J# nebo C++. V jazyce C++ je možné zvolit programování pro CLR (Common Language Runtime), což je virtuální počítač zpracovávající programy v jazyce MSIL (Microsoft Intermediate Language). CLR překladá MSIL kód do nativního kódu procesoru až za běhu aplikace a programátorovi poskytuje důležité služby pro správu paměti a vláken, obsluhu výjimek a zajištění bezpečnosti. Jedná se o již zmiňovaný řízený kód. Druhou možností vývoje v jazyce C++ je použití ATL knihovny, která programátorovi také nabízí mnoho služeb. Narozdíl od CLR je program přeložen rovnou do binárního (neřízeného) kódu proveditelného procesorem a add-in provádí užitečné operace rychleji. Volba závisí na požadavcích a zkušenostech programátora. Obrázek 3.3: Průvodce pro vytvoření add-inu - krok č. 1 2. V dalším kroku jsou na výběr klientské aplikace, pro které je zásuvný modul určen. Zde je podstatná první položka, což je Microsoft Visual Studio. Obrázek 3.4: Průvodce pro vytvoření add-inu - krok č. 2

KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 17 3. V následujícím okně průvodce lze nastavit název zásuvného modulu a krátký text stručně popisující jeho funkci. Ve správci add-inů bude tímto názvem a popiskem zásuvný modul reprezentován po jeho registraci. Obrázek 3.5: Průvodce pro vytvoření add-inu - krok č. 3 4. Další krok umožňuje nastavit tři přepínače. Zatrhnutí první volby způsobí vygenerování zdrojového kódu, který přidá položku do nabídky Tools, jejíž obsluhu je možné v zásuvném modulu implementovat. Druhá volba způsobí, že add-in bude ke klientské aplikaci připojován bezprostředně po jejím spuštění. Zatrhnutí poslední položky oznamuje, že add-in nikdy nebude vyžadovat reakci uživatele prostřednictvím grafického rozhraní, a že je možné ho použít s programy na příkazové řádce. Obrázek 3.6: Průvodce pro vytvoření add-inu - krok č. 4 5. V pátém kroku je možné nastavit, zda se má v okně About v nápovědě k Microsoft Visual Studiu zobrazovat informační text uvedený v textovém poli pod touto volbou.

18 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO V posledním okně průvodce je zobrazeno shrnutí nastavených voleb a po stisknutí tlačítka Finish je vygenerována základní struktura zásuvného modulu, který lze již přeložit, zaregistrovat a připojit k Microsoft Visual Studiu. Je-li pro add-in zvolen programovací jazyk C++ s využitím knihovny ATL, průvodce vygeneruje soubory uvedené v tabulce 3.1. Název souboru AddIn.cpp AddIn.def AddIn.idl AddIn.rgs Connect.h Connect.cpp stdafx.h Popis obsahu Soubor obsahuje definici funkcí exportovaných výslednou DLL knihovnou, které poskytují základní služby pro práci s COM serverem registraci modulu v operačním systému (funkcí DllRegisterServer()), vytvoření instance komponenty (funkcí DllGetClassObject()) a další související funkce. Implementace všech funkcí s využitím metod ATL knihovny je vytvořena průvodcem. Deklarace exportovaných funkcí, které jsou definovány v AddIn.cpp. Zdrojový kód typové knihovny v jazyce IDL. Umístění a hodnoty klíčů vytvářených v registrech Windows při registraci add-inu. Deklarace hlavní třídy komponenty s názvem CConnect, která je odvozena děděním ze tříd knihovny ATL (CComObjectRootEx a CComCoClass) a z rozhraní IDTCommandTarget a _IDTExtensibility2. Připravená implementace třídy CConnect definice virtuálních funkcí rozhraní IDTCommandTarget a _IDTExtensibility2 s minimálním potřebným kódem pro přeložení. Autor add-inu doplní implementace metod v tomto souboru o vlastní kód vykonávající účelnou funkci zásuvného modulu. V souboru je definováno vložení hlavičkových souborů ATL knihovny a import použitých rozhraní. Tabulka 3.1: Popis projektových souborů add-inu 3.4.2 Rozhraní _IDTExtensibility2 Metody rozhraní _IDTExtensibility2 implementované třídou komponenty vykonávají svůj kód jako reakci na událost, která ovlivňuje add-in. Microsoft Visual Studio volá tyto funkce když se modul připojí, odpojí, změní a podobně. Členské metody, které musí každý add-in implementovat jsou: OnAddInsUpdate() metoda je volána pokaždé když se add-in připojí nebo odpojí od integrovaného vývojové prostředí Microsoft Visual Studia OnBeginShutdown() metoda se provede před vypnutím Microsoft Visual Studia, pokud je zásuvný modul připojen během vypínání OnConnection() funkce se provede ihned po připojení add-inu a proto je vhodné v ní provést inicializaci, například získat rozhraní DTE2 voláním funkce QueryInterface() a jeho ukazatel uložit do členské proměnné, nebot jej jistě bude add-in dále využívat OnDisconnection() funkce se provede při odpojování add-inu a je tedy vhodné, aby prováděla případné úklidové operace OnStartupComplete() pokud je zásuvný modul nastaven na automatické spouštění při startu Microsoft Visual Studia, je při spuštění volána tato metoda

KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 19 3.4.3 Přidávání prvků do uživatelského rozhraní (IDTCommandTarget) Pokud má zásuvný modul provádět některé operace na požadavek uživatele, je nutné přidat příkaz, který bude moci uživatel spustit použitím prvku v uživatelském rozhraní. Pomocí rozhraní DTE2 je možné získat ukazatel na rozhraní Commands2, které umožňuje svou metodou AddNamedCommand2() přidat příkaz označený klíčovým názvem. Funkce AddNamedCommand2() jako parametr vyžaduje název příkazu, status a typ ovládacího prvku, a další informace. Návratový parametr je ukazatel na objekt typu Command, který lze pomocí volání metody AddControl() využít pro přidání ovládacího prvku do některého menu Microsoft Visual Studia. Příklad přidání příkazu a jeho ovládacího prvku je uveden v kapitole Implementace na straně 21. Aby mohl add-in reagovat na příkaz podle jeho názvu, musí implementovat rozhraní IDTCommandTarget, které obsahuje dvě virtuální metody QueryStatus() a Exec(). Metoda QueryStatus() je vývojovým prostředím volána vždy bezprostředně před zpřístupněním ovládacího prvku uživateli, například před rozbalením menu, ve kterém se ovládací prvek nachazí. HRESULT stdcall QueryStatus( BSTR CmdName, vscommandstatustextwanted NeededText, vscommandstatus* StatusOption, VARIANT* CommandText ); V jejím prvním parametru bstrcmdname je obsažen název příkazu, který je právě kontrolován. Druhý argument s názvem NeededText je konstanta určující typ požadované návratové informace. Třetí parametr StatusOption je návratový a jeho hodnota určuje status příkazu, zda je zapnutý, vypnutý, skrytý atd. Poslední parametr CommandText obsahuje ukazatel na textový řetězec vrácený funkcí, pokud byl argumentem NeededText vyžadován. Metodu Exec() Microsoft Visual Studio volá při spuštění příkazu. Porovnáním názvu příkazu, který je získán z parametru CmdName, lze zjistit, jaký požadavek má být zpracován. Implementace funkce Exec() je vlastně veškerý kód proveditelný všemi přidanými příkazy. HRESULT stdcall Exec( BSTR CmdName, vscommandexecoption ExecuteOption, VARIANT* VariantIn, VARIANT* VariantOut, VARIANT_BOOL* handled ); První parametr CmdName je název příkazu, který se má provést. Druhý parametr ExecuteOption určuje jak se má příkaz provést zda je možné se dotazovat uživatele na informace či nikoliv, nebo zda se má pouze zobrazit nápověda a příkaz se provádět nemá. Následující parametry VariantIn a VariantOut jsou ukazatele na datový typ VARIANT a umožňují předat libovolná data do funkce a z funkce. Poslední parametr handled je výstupní a měl by být nastaven na hodnotu true pokud je příkaz implementován a proveden, v opačném případě na false.

20 KAPITOLA 3. ZÁSUVNÉ MODULY PRO MICROSOFT VISUAL STUDIO 3.4.4 Reakce na události (IDispEventImpl) Při vývoji zásuvného modulu je kromě provádění příkazů vyvolaných uživatelem často nutné také reagovat na vnitřní události vývojového prostředí. Knihovna ATL nabízí řešení pomocí šablony rozhraní IDispEventImpl<>, jejímž parametrem je identifikátor IID konkrétního rozhraní pracujího s údálostmi požadovaného typu, jehož metody budou implementovány v odvozené třídě. Například lze použít BuildEvents na obsluhu událostí souvisejících s překladem (začátek čí dokončení překladu), CommandEvents pro události volání příkazu z menu (před či po provedení libovolného příkazu), DocumentEvents na události editoru dokumentů (otevření, zavření či uložení dokumentu) a další. Ve třídě odvozené od IDispEventImpl<> je možné implementovat obsluhy událostí v metodách, jejichž názvy jsou přiřazeny ke konkrétním událostem pomocí makra SINK_ENTRY_EX. Před začátkem bloku maker mapujících jednotlivé metody na události, je nutné zadat makro BEGIN_SINK_MAP() s parametrem třídy, ve které jsou definovány mapované metody. Na závěr je blok ukončen makrem END_SINK_MAP(). Propojení zdroje událostí s třídou implementující jejich obsluhy se provádí funkcí ATL knihovny s názvem DispEventAdvise(), která jako argument vyžaduje ukazatel na rozhraní jehož implementace produkuje události. Rozhraní lze získat vhodnou metodou objektu Events (například get_buildevents(), get_commandevents(), get_documentevents()). Ukazatel na Events poskytuje objekt DTE2 metodou get_events(). Zrušení směrování událostí na nastavené metody obsluhy lze provést voláním funkce DispEventUnadvise(). 3.5 Registrace zásuvného modulu Dříve než je možné vytvořený zásuvný modul používat, je nutné jej zaregistrovat jako COM komponentu v operačním systému, což bylo již dříve popsáno v sekci 3.2.6. Druhou možností, kterou nabízí Microsoft Visual Studio ve verzi 8.0 (2005), je do složky s DLL knihovnou přidat další soubor s příponou.addin, ve kterém jsou ve formátu XML popsány potřebné informace pro zobrazení zásuvného modulu ve správci add-inů. Microsoft Visual Studio při svém spuštění vyhledá soubory s příponou.addin v nastavené složce (výchozím nastavením je Visual Studio 2005\Addins ve složce Dokumenty ve Windows) a podle jejich obsahu přidá položky do správce add-inů, kde je možné zásuvné moduly aktivovat. Tento způsob umožňuje snadnou instalaci zásuvného modulu s řízeným kódem pouhým zkopírováním souborů s příponou.dll a.addin. V Microsoft Visual Studiu verze 7.1 (2003) je kromě registrace COM komponenty nutné vytvořit další záznam v registrech Windows, který je načítán správcem add-inů. Do registrů ke klíči HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\AddIns je potřeba přidat podklíč s názvem tvořeným názvem add-inu a tečkou odděleným názvem jeho hlavní třídy, tedy již zmiňovaný ProgID zásuvného modulu, například MujAddin.Connect. Hodnoty tohoto podklíče jsou pouze volitelné (není nutné je vytvářet), nicméně zpřehledňují zobrazení ve správci add-inů hodnota FriendlyName obsahuje název v čitelnější podobě, hodnota Description je text stručně popisující funkci zásuvného modulu a hodnota LoadBehavior je číselná konstanta určující zda se má add-in připojovat při startu vývojového prostředí (hodnota 1 znamená, že ano). V projektovém souboru AddIn.rgs jsou definovány všechny klíče a jejich hodnoty, které se mají vytvořit v registrech Windows při registraci add-inu metodou DllRegisterServer(). Úpravou tohoto souboru lze snadno měnit hodnoty ukládané do registrů, které vygeneroval průvodce při vytváření projektu, a případně přidávat další klíče pro vlastní potřeby zásuvného modulu.

KAPITOLA 4. IMPLEMENTACE 21 4 Implementace Součástí této bakalářské práce je zásuvný modul pro Microsoft Visual Studio. S využitím teorie programování COM komponent a zásuvných modulů pro Microsoft Visual Studio, která je popsána v přechozí kapitole, je vytvořen add-in s názvem Disassembler, jehož implementace bude dále vysvětlena. Na přiloženém CD je k dispozici kompletní zdrojový kód zásuvného modulu pro obě verze Microsoft Visual Studia, které podporuje 1. Dále je na CD instalátor, který podle verze Microsoft Visual Studia instalované v počítači zkopíruje správnou verzi DLL knihovny komponenty a provede další operace nutné pro její používání. 4.1 Microsoft Visual Studio 8.0 (2005) Add-in byl nejprve vyvíjen pro Microsoft Visual Studio verze 8.0 (2005) a poté byl duplikát hotového zdrojového kódu upraven pro verzi 7.1 (2003). Implementace bude popsána ve stejném pořadí jako byl zásuvný modul vyvíjen. Prvním krokem k vytvoření zásuvného modulu bylo použití průvodce s volbou programovaní v jazyce C++ s využitím knihovny ATL (viz. sekce 3.4.1). Průvodce vygeneroval kostru add-inu s deklarací třídy CConnect, jejíž metody je nutno definovat v připraveném souboru Connect.cpp. Základem při programování zásuvného modulu je schopnost používat již existující komponenty, které poskytují uživatelské rozhraní pro výběr souboru, pro spuštění vlastního převodu zdrojového kódu z jazyka C/C++ do assembleru ovládacím prvkem a pro zobrazení výstupu v okně textového editoru. Další komponenty jsou nutné pro zkompilování vybraného souboru a zjištění kdy byl překlad dokončen obsluhou vhodné události. Aby bylo možné volat metody těchto komponent, je nutné k nim nejprve získat rozhraní. V Microsoft Visual Studiu je ve většině případech možné získat ukazatel na rozhraní metodou nadřazené komponenty. V jazyce C++ se používají názvy metod začínající na get_ a následuje název požadovaného rozhraní. Základní komponenta, ze které lze postupně získat všechna rozhraní použitá v bakalářské práci, se nazývá DTE2. Seznam použitých komponent a jejich hierarchie je znázorněna na obrázku 4.1. Například pro získání rozhraní pro práci s komponentou SolutionBuild je nutné nejprve obržet ukazatel na rozhraní Solution od objektu DTE2, což ukazuje následující fragment zdrojového kódu (proměnná m_pdte je chytrý ukazatel na rozhraní DTE2): CComQIPtr<_Solution> psolution; CComQIPtr<SolutionBuild> psolutionbuild; m_pdte->get_solution(&psolution); psolution->get_solutionbuild(&psolutionbuild); psolutionbuild->build(true); První dva řádky zdrojového kódu deklarují ukazatele na rozhraní Solution a SolutionBuild pomocí šablony třídy chytrého ukazatele na rozhraní s názvem CComQIPtr<>, který je součástí knihovny ATL. Následně je postupně získáno rozhraní SolutionBuild, které je na posledním řádku použito k přeložení projektových souborů metodou Build(). 1 kompatibilita s novějšími verzemi Microsoft Visual Studia je možná, nicméně funkčnost je ověřena jen pro verze 7.1 (2003) a 8.0 (2005)

22 KAPITOLA 4. IMPLEMENTACE Obrázek 4.1: Komponenty využívané zásuvným modulem Disassembler 4.1.1 Inicializace zásuvného modulu (metoda CConnect::OnConnection()) Metoda OnConnection() je prováděna při připojení add-inu k Microsoft Visual Studiu a je vhodné aby inicializovala objekty, které bude zásuvný modul dále používat. Prototyp funkce OnConnection vypadá takto: virtual HRESULT stdcall OnConnection(IDispatch * papplication, ext_connectmode ConnectMode, IDispatch *paddininst, SAFEARRAY **custom); První parametr papplication je ukazatel na rozhraní IDispatch, který reprezentuje základní aplikaci Microsoft Visual Studia. Další argument ConnectMode oznamuje, jak byl add-in připojen zda automaticky po spuštění vývojového prostředí, externím programem, nebo komponentou, otevřením projektu, který jej vyžaduje, nebo jiným způsobem. Třetí parametr paddininst je ukazatel na rozhraní IDispatch reprezentující konkrétní instanci právě připojeného zásuvného modulu. Poslední parametr může obsahovat ukazatel na pole objektů typu VARIANT předávající libovolná data do funkce add-in Disassembler jej nevyužívá. Pro práci s existujícími komponentami Microsoft Visual Studia je potřeba získat zmiňovaný ukazatel na rozhraní DTE2, což je řešeno metodou QueryInterface() z objektu papplication. Jako vstupní parametr QueryInterface() je místo přímého zadání identifikátoru rozhraní GUID použit operátor uuidof(), který zjistí a vrátí GUID na základě jeho zadaného názvu. Podobně je získáno i rozhraní AddIn z objektu paddininst. papplication->queryinterface( uuidof(dte2), (LPVOID*)&m_pDTE); paddininst->queryinterface( uuidof(addin), (LPVOID*)&m_pAddInInstance);

KAPITOLA 4. IMPLEMENTACE 23 Následuje volání metody SetSink() třídy CBuildEvents, které nastaví obsluhy událostí překladu na metody definované v třídě CBuildEvents. Podrobněji je tato třída popsána v sekci 4.1.4. Další kód slouží k přidání ovládacího prvku tlačítka Disassemble do kontextové nabídky v okně Solution Explorer. Příkaz vykonávaný po stisknutí tlačítka je vytvořen metodou AddNamedCommand2(), kterou implementuje rozhraní Commands2. pcommands2->addnamedcommand2(m_paddininstance, CComBSTR("Disassemble"), CComBSTR("Disassemble"), CComBSTR("Disassembles a C++ source code."), VARIANT_TRUE, CComVariant(0), NULL, vscommandstatussupported, vscommandstyletext, vscommandcontroltypebutton, &ppopupcommand); Prvním parametrem metody AddNamedCommand2, proměnnou m_paddininstance, je předán ukazatel na rozhraní IDispatch aktuální instance add-inu. Další tři argumenty jsou textové řetězce typu CComBSTR název identifikující příkaz, popisek ovládacího prvku (tlačítka) a stručný popisný text, který bude zobrazen při najetí kurzoru myši nad tlačítko. Následující parametr je typu bool a určuje, zda případný obrázek na tlačítku bude typu Office. Hodnota je zde nepodstatná, nebot v následujícím parametru je identifikátor obrázku nastaven na 0, tj. nebude se zobrazovat žádný obrázek. Sedmý parametr určuje v jakém kontextu má být příkaz přístupný (například může být přístupný pouze v ladícím, nebo v návrhovém módu) hodnota NULL znamená, že jeho přístupnost není omezena. Následující tři argumenty nastavují zobrazení ovládacího prvku příkazu vscommandstatussupported umožní měnit stav tlačítka před jeho zobrazením (viz. následující sekce 4.1.2), vscommandstyletext udává, že tlačítko bude označeno pouze textem a vscommandcontroltypebutton říká, že ovládací prvek má být tlačítko. Poslední parametr ppopupcommand je výstupní a při úspěšném provedení funkce v něm je obsažen ukazatel typu Command na právě přidaný příkaz. Nyní je potřeba umístit ovládací prvek vytvořeného příkazu do požadované nabídky. Nejprve je metodou get_commandbars() získano rozhraní CommandBars pro práci s příkazovými nabídkami, jehož metoda get_item() vrací ukazatel na rozhraní CommandBar konkrétní nabídky podle názvu. Disassembler používá nabídku označenou názvem Item, což představuje kontextovou nabídku souborů v okně Solution Exploreru. Přidání příkazu do této nabídky provede metoda AddControl() rozhraní Command: pcommandbars->get_item(ccomvariant(l"item"), &pitemcommandbar); ppopupcommand->addcontrol(pitemcommandbar, 1, &pdispatch); Prvním parametrem metody AddControl() je ukazatel na rozhraní nabídky Item, do které se má příkaz ppopupcommand přidat. Druhý parametr je číslo udávající pořadí tlačítka v nabídce. Nastavená hodnota 1 způsobí přidání ovládacího prvku úplně nahoru. Posledním parametrem pdispatch je vrácen ukazatel na rozhraní CommandBarControl, který dále není zapotřebí. Posledním krokem inicializace je získat handle hlavního okna aplikace Microsoft Visual Studio, což je nutný parametr funkce MessageBox(), která je použita pro zobrazení informačních oken za běhu add-inu. Handle okna, obdržený metodou komponenty MainWindow, je typu HWND a je uložen do třídní proměnné hwndmain. 4.1.2 Nastavení stavu ovládacího prvku (metoda CConnect::QueryStatus()) Před každým zobrazením přidaného tlačítka v rozbalovací nabídce je vývojovým prostředím volána metoda zásuvného modulu s názvem QueryStatus(). Její vhodná implementace u- možňuje měnit stav tlačítka před jeho zobrazením. Zásuvný modul Disassembler pomocí této metody zapíná nebo vypíná zobrazení tlačítka na základě přípony souboru, na kterém je kontextová nabídka vyvolána.

24 KAPITOLA 4. IMPLEMENTACE Nejprve metoda QueryStatus() porovná funkcí _wcsicmp() název příkazu, který má být zpracován. Hodnota předaná ve vstupním parametru bstrcmdname obsahuje název příkazu ve tvaru Program.Komponenta.Příkaz. Pokud je hodnota bstrcmdname nastavena na textový řetězec Disassembler.Connect.Disassemble, tj. je zjišt ován stav tlačítka, které bylo přidáno při inicializaci, provede se pomocí komponenty SelectedItems zjištění názvu označeného souboru. Jestliže název souboru končí příponou C nebo CPP, je vrácen stav vscommandstatussupported + vscommandstatusenabled a tlačítko Disassemble je v nabídce zobrazeno a je možné na něj kliknout. Pro ostatní soubory je vrácen stav vscommandstatusinvisible, což znamená, že je tlačítko skryto. 4.1.3 Provedení příkazu (metoda CConnect::Exec()) Když uživatel vývojového prostředí klikne na tlačítko Disassemble, Microsoft Visual Studio volá metodu zásuvného modulu s názvem Exec(), která má provést obsluhu příkazu. V prvním argumentu je metodě předán název příkazu, který se má provést, a podobně jako u metody QueryStatus() je tato hodnota testována a v případě, že odpovídá názvu tlačítka Disassemble, jsou provedeny následující akce. Nejprve je pomocí rozhraní SelectedItems získána vybraná položka v podobě ukazatele na typ SelectedItem, která poskytuje rozhraní ProjectItem, pomocí kterého je zjištěna absolutní cesta ke zdrojovému souboru. Z cesty k C nebo CPP souboru je odvozen název objektového souboru nahrazením původní přípony za OBJ a odříznutím části textového řetězce před zpětným lomítkem. Obdržený řetězec je uložen do proměnné objfile a může mít hodnotu například zdrojovy_soubor.obj. Microsoft Visual Studio ukládá objektové soubory do podsložky projektu, která má název podle zvolené konfigurace překladu (například předdefinované konfigurace Debug a Release). Pro zjištění absolutní cesty k objektovému souboru, který vznikne při překladu, je tedy nutné znát ještě název konfigurace. Nejdříve je metodou get_activesolutionprojects() získáno pole typu SafeArray obsahující aktivní projekty otevřené ve vývojovém prostředí. Funkcí SafeArrayGetLBound() je zjištěn index prvního prvku, který je pak předán funkci SafeArrayGetElement() a ta vrátí objekt typu VARIANT, který již obsahuje ukazatel na rozhraní Project představující otevřený projekt uživatele add-inu. Rozhraní Project umožňuje získat rozhraní ConfigurationManager a z něj pak rozhraní ActiveConfiguration, které je použito pro zjištění názvu konfigurace, kterou má uživatel zásuvného modulu právě zvolenu. Výslednou absolutní cestu k objektovému souboru tvoří cesta ke složce projektu zjištěná z cesty ke zdrojovému souboru, následuje složka s názvem aktivní konfigurace a nakonec název souboru z proměnné objfile. Cesta k objektovému souboru je uložena do globální proměnné OBJFile. Pokud objektový soubor již existuje z předchozího překladu, je smazán, aby bylo zaručeno, že je vždy zpracováván aktuální objektový soubor odpovídající přeloženému zdrojovému kódu. Dále je nastavena textová proměnná asmfile na hodnotu absolutní cesty k vytvářenému souboru se symbolickými instrukcemi, která se liší od cesty k objektovému souboru pouze příponou změněnou na ASM. Následně je vygenerován textový řetězec obsahující spouštěcí parametry k programu dumpbin, který je zapsán do proměnné params. Před spuštěním překladu zásuvný modul ještě zjistí, zda není soubor se symbolickými instrukcemi již otevřen z předchozího použití add-inu. Pokud by byl otevřen a byl by změněn programem dumpbin, vývojové prostředí by hlásilo, že ho upravila externí aplikace, a bylo by nutné klikat na dialogové okno. Add-in metodou objektu DTE2 s názvem get_isopenfile() zjistí, jestli soubor s názvem v proměnné asmfile je otevřen. Pokud ano, je metodou OpenFile() získáno rozhraní Window, které umožní soubor zavřít metodou Close(). Posledním krokem, který je možné v metodě Exec() provést, je spuštění překladu. To je provedeno použitím komponenty SolutionBuild, která metodou Build() přeloží projekt

KAPITOLA 4. IMPLEMENTACE 25 s použitím aktivní konfigurace a vznikne potřebný objektový soubor. Rozhraní SolutionBuild je získáno z objektu DTE2 přes rozhraní Solution: m_pdte->get_solution(&psolution); psolution->get_solutionbuild(&psolutionbuild); disassembling = TRUE; psolutionbuild->build(disassembling); Z uvedené části zdrojového kódu je vidět nastavení disassembling na hodnotu TRUE. Proměnná disassembling je globální a určuje, že právě prováděný překlad je pro účel tohoto zásuvného modulu. Parametr metody Build() neplní svůj význam uváděný Microsoftem a proto jeho hodnota může být libovolná. Další operace pro převedení zdrojového kódu do assembleru je nutno provádět až po dokončení překladu, tj. po vytvoření objektového souboru. 4.1.4 Třída CBuildEvents obsluhující překladové události Za účelem detekce dokončení překladu je vytvořena třída CBuildEvents, která je odvozena děděním od šablony rozhraní IDispEventImpl<>. Důležitým parametrem IDispEventImpl<> je GUID identifikující rozhraní, přes které vývojové prostředí přistupuje k metodám implementovaným ve třídě CBuildEvents, pokud jsou mapovány k některé události zvoleného rozhraní. Pro zachycení události konce překladu je jako parametr šablony použito rozhraní _dispbuildevents. Ve třídě CBuildEvents jsou definovány (kromě konstruktoru a destruktoru) tři metody SetSink(), RemoveSink a OnBuildDone(). Funkce SetSink() je volána při inicializaci v metodě CConnect::OnConnection() a jako argument je do ní předán ukazatel na rozhraní DTE2. Od DTE2 je získáno rozhraní Events, které poskytuje rozhraní BuildEvents. Na závěr je volána funkce DispEventAdvise(), která nastaví obsluhu událostí produkovaných rozhraním BuildEvents na metody definované v třídě CBuildEvents: m_pdte->get_events(&pevents); pevents->get_buildevents((envdte::_buildevents**)&m_pbuildevents); DispEventAdvise((IUnknown*)m_pBuildEvents.p); Metoda RemoveSink() slouží k odpojení zdroje událostí od jejich obsluhy definované třídou CBuildEvents a je volána při odpojování add-inu v těle metody CConnect::OnDisconnection(). Odpojení provádí pomocí funkce DispEventUnadvise() s parametrem m_pbuildevents, což je třídní proměnná nastavená ve funkci SetSink(). Poslední metoda OnBuildDone() představuje obsluhu události dokončení překladu. Mapovaní události na tuto metodu je provedeno následujícími makry: BEGIN_SINK_MAP(CBuildEvents) SINK_ENTRY_EX(1, uuidof(envdte::_dispbuildevents), 4, OnBuildDone) END_SINK_MAP() Makro SINK_ENTRY_EX nastavuje jako obsluhu události reprezentované metodou rozhraní _dispbuildevents s identifikátorem DISPID = 4 (zadáno v druhém a třetím parametru) funkci s názvem OnBuildDone (zadáno v posledním parametru). 4.1.5 Obsluha dokončení překladu (metoda CBuildEvents::OnBuildDone()) Po dokončení překladu projektu ve vývojovém prostředí je za běhu add-inu vždy provedena funkce OnBuildDone(). Je nutné ještě rozlišit, zda byl překlad spuštěn add-inem za

26 KAPITOLA 4. IMPLEMENTACE účelem převodu zdrojového kódu do assembleru, nebo zda byl spuštěn jiným způsobem (například uživatel add-inu pouze přeložil vyvíjený projekt příkazem v nabídce). Proto metoda OnBuildDone() nejdříve testuje hodnotu proměnné disassembling, která je před spuštěním překladu v metodě Exec() nastavena na TRUE. Další kód funkce se provede pouze pokud je hodnota TRUE, v opačném případě metoda OnBuildDone() končí. Následuje test existence objektového souboru s názvem v proměnné OBJFile, jehož nenalezení znamená chybu a taktéž ukončení metody. Pokud objektový soubor existuje, provádí se spuštění dávky disasm.bat, která volá program dumpbin. K tomu je nutné nejprve nastavit parametry ve struktuře SHELLEXECUTEINFO, která obsahuje veškeré informace pro provedení externího programu funkcí ShellExecuteEx(): SHELLEXECUTEINFO exec; ZeroMemory(&exec, sizeof(shellexecuteinfo)); exec.cbsize = sizeof(shellexecuteinfo); exec.fmask = SEE_MASK_NOCLOSEPROCESS; exec.hwnd = hwndmain; exec.lpfile = _T("disasm.bat"); exec.lpparameters = params; exec.lpdirectory = get_disasmdir(); exec.nshow = SW_HIDE; ShellExecuteEx(&exec); DWORD exitcode; GetExitCodeProcess(exec.hProcess, &exitcode); int cykly = 0; while (exitcode == STILL_ACTIVE) { if (++cykly > 50) { MessageBox(hWndMain, _T("Timeout!"), _T("Disassembler error"), MB_OK MB_ICONERROR); break; } Sleep(200); GetExitCodeProcess(exec.hProcess, &exitcode); } Nejprve je struktura SHELLEXECUTEINFO vymazána metodou ZeroMemory() a následně jsou nastaveny jednotlivé atributy pro spouštění (vynechaným atributům zůstane hodnota 0, což označuje jejich výchozí nastavení například lpverb specifikující požadovanou operaci se souborem má výchozí hodnotu open a bude se tedy provádět otevření (spuštění) souboru): cbsize velikost struktury v bytech fmask obsah a platnost atributů struktury nastavená kombinací konstant hodnota SEE_MASK_NOCLOSEPROCESS určuje, že atribut hproces bude nastaven na handle procesu, ve kterém bude program spuštěn hwnd handle okna aplikace, ve kterém budou zobrazena případná informační okna lpfile ukazatel na textový řetězec obsahující název souboru, který se má spustit lpparameters ukazatel na textový řetězec obsahující parametry, které se předají spouštěnému programu (jednotlivé parametry musí být v textu odděleny mezerou stejně jako při spouštění aplikace v systémové konzoli)

KAPITOLA 4. IMPLEMENTACE 27 lpdirectory ukazatel na textový řetězec obsahující název složky, ze které je volán spouštěný program nshow způsob zobrazení spouštěné aplikace Po zavolání metody ShellExecuteEx() se cyklicky zjišt uje stav procesu, ve kterém je prováděn program dumpbin funkcí GetExitCodeProcess() je vrácena hodnota označující stav procesu identifikovaného jeho handlem v proměnné exec.hprocess a pokud proces stalé probíhá (tj. hodnota odpovídá konstantě STILL_ACTIVE), add-in je funkcí Sleep() na 200 milisekund pozastaven a poté se cyklus opakuje dokud proces neskončí. V případě, že by volaný program běžel déle než 10 vteřin (což odpovídá vykonání padesáti iterací cyklu), je cyklus ukončen a zobrazeno varovné okno. Toto opatření zajišt uje, aby nedošlo k zacyklení add-inu v případě, že externě volaný program z jakéhokoliv důvodu zamrzne. Pokud proces skončí bezchybně a je opuštěn čekací cyklus, tj. předpokládá se, že soubor se zdrojovým kódem v assembleru je již vytvořen, je možné zobrazit výsledek v okně textového editoru Microsoft Visual Studia. Otevření je provedeno metodou objektu DTE2 s názvem OpenFile(), které je předán název souboru v proměnné asmfile. Po otevření okna zobrazujícího obsah souboru se symbolickými instrukcemi je metodou Activate() zajištěno jeho zobrazení na popředí. 4.2 Microsoft Visual Studio 7.1 (2003) Implementace verze add-inu pro Microsoft Visual Studio 7.1 (2003) je principiálně stejná jako pro verzi 8.0 (2005), rozdíl je především v nahrazení tříd jmenného prostoru EnvDTE2, který ve starší verzi ještě neexistuje. Použity jsou obdobné komponenty definované ve jmenném prostoru EnvDTE. Konkrétně místo DTE2 je použit DTE, spouštěcí příkaz je přidán pomocí komponenty Commands (místo novější verze Commands2) metodou AddNamedCommand() (místo AddNamedCommand2()). Z důvodu starší verze je navíc potřeba při odpojování zásuvného modulu (v metodě CConnect::OnDisconnection()) odebírat přidaný příkaz a jeho ovládací prvek, což je řešeno metodou Delete() rozhraní CommandBar, které je získáno z rozhraní CommandBars funkcí get_item() na základě názvu příkazu. Pokud by nebyl prvek odebírán, při dalších připojeních add-inu by se do kontextového menu přidávaly další shodné položky při přidávání novější metodou AddNamedCommand2() duplikace nevznikají. 4.3 NSIS instalátor Vytvoření instalátoru NSIS spočívá v napsání skriptu ve speciálním jazyce navrženém firmou Nullsoft, který je zpracován překladačem a vzniká spustitelný EXE soubor. Nejprve jsou v instalačním skriptu zvoleny hlavičkové soubory umožňující použití předdefinovaných funkcí, konkrétně je použit soubor WordFunc.nsh, který poskytuje potřebnou funkci WordReplace. Následně je definován název aplikace (klíčovým slovem Name), název výstupního spustitelného souboru (OutFile), výchozí cílová složka pro instalaci (InstallDir) a umístění klíče v registrech Windows, ve kterém bude zapsána cílová složka pro případ přeinstalování, aby byla přepsána původní instalace ve stejné složce (InstallDirRegKey). Další část skriptu klíčovým slovem Page nastavuje, že se mají zobrazit při instalaci stránky pro výběr instalovaných součástí, pro změnu cílové složky a nakonec stránka s průběhem instalace. Dále jsou v blocích mezi slovy Section a SectionEnd uzavřeny jednotlivé součásti instalace první dvě sekce odpovídají instalaci add-inu pro verzi Microsoft Visual Studia 8.0 (2005) a 7.1 (2003), třetí s názvem Uninstall obsahuje kód pro odinstalovaní zásuvného modulu, který se provede spuštěním vygenerovaného souboru uninstall.exe.

28 KAPITOLA 4. IMPLEMENTACE V první sekci se provádí instalace modulu pro verzi 8.0 (2003) neprve se z registrů Windows načte složka, ve které je nainstalované Microsoft Visual Studio, a poté je náhradou části řetězce získána složka umístění programu dumpbin. Pak je nastavena cílová složka pro instalaci příkazem SetOutPath, zkopírován DLL soubor s add-inem použitím klíčového slova File a knihovna je zaregistrována v operačním systému voláním příkazu RegDLL. Dále je vytvořen dávkový soubor obsahující nastavení systémové proměnné PATH přidáním složky, kde je umístěn dumpbin, nebot v ní budou nalezeny další soubory, které dumpbin vyžaduje pro svůj běh. Následně je v dávce vlastní volání programu dumpbin s předáním parametrů, které jsou dávce předávány add-inem při jejím spouštění. V poslední části sekce jsou do registrů zapsány informace pro odinstalování, které Windows využívají při odstraňování aplikace přes Ovládací panely, a je zapsán spustitelný soubor provádějící odinstalaci příkazem WriteUninstaller. Další sekce funguje analogicky stejně pro verzi 7.1 (2003), pouze se liší názvy klíčů čtených z registrů a názvy DLL knihovny a dávkového souboru. Narozdíl od verze 8.0 se v této sekci navíc zapíšou informace o zásuvném modulu do registrů Windows, nebot je vyžaduje správce add-inů ve starší verzi Microsoft Visual Studia. Poslední sekce s názvem Uninstall provede odstranění add-inu z počítače. Zrušení registrace DLL knihovny se provede příkazem UnRegDLL, dále jsou smazány záznamy v registrech vytvořené při instalaci a všechny soubory v instalační složce. Na konci skriptu je definována funkce se speciálním názvem.oninit, která je volána bezprostředně po spuštění instalátoru. Funkce čte z registrů Windows záznamy o instalaci obou podporovaných verzí Microsoft Visual Studia. Pokud je záznam nalezen (tj. daná verze Microsoft Visual Studia je v počítači nainstalována), je zatrhnuta položka na stránce výběru součástí. Pokud klíč v registrech nalezen není, součást není vybrána a je zamezeno uživateli, aby ji mohl zatrhnout. Stav položek na stránce výběru součástí lze měnit nastavením příznaků pomocí volání SectionSetFlags.

KAPITOLA 5. ZÁVĚR 29 5 Závěr Vytvořený zásuvný modul pro převod zdrojových kódů z jazyka C/C++ do assembleru může být užitečným pomocníkem vývojářů programujících v prostředí Microsoft Visual Studio. Díky instalátoru je možné velmi snadno add-in připravit k použití, aniž by uživatel musel znát principy, na kterých funguje. Dalším přínosem této bakalářské práce je shrnutí informací o tvorbě zásuvných modulů do Microsoft Visual Studia a obecně programování komponentových aplikací postavených na architektuře COM. Teorie je lépe pochopitelná díky detailnímu popisu implementace add-inu, který provádí operace často potřebné i u zásuvných modulů s úplně jiným významem (například přidávání ovládacích prvků do nabídek a jejich obsluha, reakce na různé typy událostí a podobně). Vše je vysvětleno velmi do hloubky, nebot materiálů k této problematice existuje velmi málo, především pak v českém jazyce. Zásuvný modul byl testován na několika různých počítačích a pro obě podporované verze Microsoft Visual Studia fungoval správně podle předem vymezených požadavků. Nicméně addin by bylo možné dále rozšířit o podporu převodu do assembleru z dalších programovacích jazyků použitelných při vývoji v Microsoft Visual Studiu komponentový model by zůstal stejný, pouze by bylo nutné přidat použití dalších programů pro generování kódu v jazyce symbolických instrukcí a individuálně řešit náležitosti související s jednotlivými programovacími jazyky. Obrázek 5.1: Ukázka použití zásuvného modulu Disassembler

30 KAPITOLA 6. LITERATURA 6 Literatura [1] R. Chalupa. Programování COM objektů, ActiveX a Win32 aplikací. BEN, 2006. [2] Eternal s.r.o. Progres, 2007. http://progres.eternal.cz/. [3] Microsoft. Microsoft Developer Network, 2007. http://msdn.microsoft.com/. [4] Nullsoft. Nullsoft Scriptable Install System, 2007. http://nsis.sourceforge.net. [5] D. Rogerson. Inside COM. Microsoft Press, 1997. [6] sourceware.org. GNU Binary Utilities, 2007. http://sourceware.org/binutils/docs-2.17/. [7] Wikipedia Foundation. Wikipedia encyklopedia, 2007. http://en.wikipedia.org/.

KAPITOLA 6. LITERATURA 31

32 KAPITOLA 6. LITERATURA

DODATEK A. HIERARCHIE KOMPONENT MICROSOFT VISUAL STUDIA 33 A Hierarchie komponent Microsoft Visual Studia Obrázek A.1: Hierarchie komponent Microsoft Visual Studia