4GL.NET Problematyka migracji i integracji z Visual Studio .NET Krzysztof Kuśnierz Supra sp. z o.o.
O nas Historia Produkty UNIX Informix (obecnie IBM) Obecnie migrujemy do technologii Microsoft (SQL i .NET) Produkty System ERP ASIMS+ 4GL Migracje istniejących systemów napisanych w 4GL Jesteśmy firmą, która zaczęła swoją działalność ponad 13 lat temu. Od początku byliśmy firmą związaną ze środowiskiem UNIX. ASIMS+: Ponad 1100 tablic w systemie Ponad 300 tysięcy linii kodu w 4GLu samej logiki biznesowej 4GL: Własny język programowania Całe środowisko programistyczne w systemie UNIX Migracje: W dalszym ciągu istnieją na rynku aplikacje napisane w Informix-4GL liczące wiele milionów linii kodu.
Plan Co to jest 4GL Migracja bazy danych Migracja bibliotek runtime i terminala Integracja z Visual Studio .NET Migracja bazy danych oznacza umożliwienie pracy istniejącej aplikacji z inną bazą danych.
Co to jest Supra-4GL Prosty język zbliżony do naturalnego angielskiego Proceduralny Zorientowany na obsługę baz danych Można go podzielić na cztery części Sterowanie przepływem programu SQL Obsługa interakcji z użytkownikiem Raportowanie Duża część większych systemów zarządzających danymi jest napisane w jakiejś odmianie 4GLa: np. SAP (ABAP), Oracle (PL/SQL). Sam IBM podał po przejęciu Informixa, że wraz z Informixem przejął ponad 120 tysięcy klientów korporacyjnych. Supra 4GL opracowany na podstawie Informix-4GL 4GL nie jest językiem nowym – istnieje od lat osiemdziesiątych. 4GL jest bardzo prostym językiem, którym mogą posługiwać się nie tylko wykwalifikowani programiści. 4GL nie jest językiem obiektowym. Zorientowanie 4GLa na współpracę z bazą danych jest wyrażone poprzez ścisłe zintegrowanie z SQLem Części języka 4GL: Sterowanie przepływem programu: instrukcje definiujące funkcje (i ich parametry), zmienne, pętle, instrukcje podstawienia i warunkowe, itd.. SQL: instrukcje DDL (data definition language), DML (data management language) Obsługa interakcji z użytkownikiem: wydzielony podjęzyk służący do definiowania okien ekranowych oraz instrukcje do ich obsługi Raportowanie: instrukcje do definiowania raportów (analogiczne do definiowania funkcji), umożliwiają definiowanie całych raportów (z podziałem na strony, bloki itd.)
Pobieranie/przetwarzanie Prosty program w 4GL Definicja zmiennej DATABASE sample MAIN DEFINE r RECORD LIKE person.* DECLARE cx SCROLL CURSOR FOR SELECT * FROM person FOREACH cx INTO r.* DISPLAY r.first_name, r.last_name END FOREACH SELECT * INTO r.* FROM person WHERE nr=10 END MAIN Deklaracja kursora Pobieranie/przetwarzanie danych Instrukcja DEFINE pozwala na deklarację zmiennej (w tym przypadku klauzula RECORD LIKE deklaruje rekord na podstawie zawartości bazy danych). Instrukcja DECLARE pozwala na utworzenie kursora SQLowego (w tym przypadku kursora typu SCROLL, czyli umożliwiającego swobodne, niekoniecznie sekwencyjne odczytywanie danych zgromadzonych w kursorze). Pętla FOREACH pozwala na przetwarzanie kolejnych danych z kursora. Oczywiście są również dostępne inne instrukcje
Problem: jak napisać aplikację niezależną od bazy danych Wybór API (czyli język programowania) Uwzględnienie różnic funkcjonalnych różnych baz danych Typy danych (np. SERIAL, DATE) Składnia SQL (DDL i DML) (OUTER JOIN, tablice tymczasowe) Funkcje wbudowane Przetwarzanie transakcyjne Inne API: W chwili obecnej OdBC, OleDB, ADO.NET (dla C#), JdBC (dla Javy). Różnice w typach danych: należałoby stosować tylko typy danych. Różnice w składni można obejść stosując kod wariantowy (tzn. SELECT wygląda inaczej w zależności od rodzaju bazy danych). Rozwiązanie to ma wadę w postaci bardzo rozbudowanego kodu. Na dobrą sprawę należałoby stosować tylko część wspólną, ale to z kolei bardzo zawęża ilość dostępnych opcji. Są podejmowane próby ustandaryzowania SQLa (SQL/92, SQL/99). Udało się ustandaryzować OUTER JOINy, ale w międzyczasie pojawiły się nowe mechanizmy takie jak triggery, procedury składowane, mechanizmy relacyjno obiektowe, i ROLAPowe, a one są zupełnie inne w różnych bazach danych. Przetwarzanie transakcyjne: każda baza danych ma inny model blokowania dostępu do zasobów Inne: np.. Użytkownicy, autoryzacja, uprawnienia itd.. Wniosek: aplikacja pisana z uwzględnieniem różnic pomiędzy bazami danych musiałaby używać bardzo prostego SQLa
Nasze rozwiązanie Stale rozszerzany dialekt SQL opracowany na podstawie SQL/Informix Pośrednik dedykowany dla bazy danych Niwelowanie różnic składniowych Niwelowanie różnic funkcjonalnych Aplikacje Bazy Danych Pośrednik (MDC) Staramy się dostosować funkcjonalność silnika do naszych potrzeb. Dialekt jest rozszerzany o elementy, które są dostępne we wszystkich popularnych bazach danych lub na takie elementy dają się przetłumaczyć. Dialekt opracowany na podstawie SQL/Informix ponieważ od niego zaczynaliśmy (pierwotnie nasza aplikacja ERP była napisana w Informix-4GL). Pośrednik może łączyć się z aplikacją za pomocą TCP/IP: dzięki temu aplikacja pracująca na jednej maszynie (np.. pod UNIXem) może użyć MS-SQLa z pracującego na innej. Niwelowanie różnic składniowych polega na tłumaczeniu komend SQLowych na dialekt Microsoft
Rozwiązania dla MS-SQL cz. I O pośredniku: jest serwisem systemowym Dedykowany dla SQL Server 2000 API: OdBC Język Polish_CS_AS, ANSI_NULL Kursory W 4GLu: read-only, forwad-only Scroll For update Insert cursors Ostrożnie z kursorami w OdBC! Kursory serwerowe są zasobożerne Użyliśmy OdBC ponieważ mieliśmy już podobne rozwiązanie dla innej bazy danych napisane właśnie przy użyciu OdBC. Zamierzamy użyć OLEDB. Polish – code page 1250 (na systemach UNIX preferowane jest ISO8859-2) ANSI_NULL – porównanie wartości nienullowej z nullową daje null Default result set (firehorse cursor): jest najszybszym sposobem pobierania danych z serwera is read-only and forward-only Może być otwarty tylko jeden jednocześnie Kursory for insert Użycie kursorów w OdBC – problem z jednoczesnym używaniem kursorów typu ‘default result set’ i serwerowych Użyliśmy kursorów serwerowych: STATIC i SCROLLABLE/NONSCROLLABLE
Rozwiązania dla MS-SQL cz. II Typy danych W MS-SQLu nie ma typów: SERIAL, DATE, DATETIME (inna funkcjonalność) i INTERVAL Pseudokolumna ROWID Struktura SQLCA zawiera między innymi wartości pola typu SERIAL i ROWID, jeżeli ostatnio wykonaną instrukcją był INSERT CREATE TABLE sample ( c1 SERIAL, c2 DATETIME YEAR TO FRACTION(3), c3 INTERVAL HOUR TO MINUTE ) Uwaga! W przypadku wykorzystywania typów danych użytkownika w tablicach tymczasowych typy te muszą być zdefiniowane w bazie danych tempdb SERIAL – autoinkrementowany INTEGER. Kolumny typu SERIAL są często wykorzystywane jako PRIMARY KEY ROWID – pseudokolumna w każdej tablicy, oznacza adres wiersza w tablicy Z typami danych DATE, DATETIME, INTERVAL związanych jest szereg funkcji niedostępnych w MS-SQLu, np.. EXTEND, MDY Po komendzie INSERT można odczytać ze struktury SQLCA wartość pola typu SERIAL oraz wartość pseudokolumny ROWID ostatnio insertowanego rządka. Wykorzystano: Typy danych użytkownika Funkcje definiowane przez użytkownika Zewnętrzne procedury składowane Kolumny Identity Triggery (SERIAL i ROWID)
Rozwiązania dla MS-SQL cz. III SELECT f.a[10,13] FROM database@server.table f INTO TEMP ttable Tablice tymczasowe Nazewnictwo obiektów Substringi SELECT substring(f.a,10,13-10+1) INTO #ttable FROM server.database..table f Tablice tymczasowe: Klauzula INTO TEMP <tbname> w Informixie jest na końcu instrukcji, w MS-SQLu zaraz po klauzuli SELECT i brak jest słowa SELECT. Nazewnictwo obiektów: Nazwami obiektów nie mogą być słowa kluczowe i nazwy funkcji Dobrym wynalazkiem jest możliwość podawania identyfikatorów na nawiasach kwadratowych Substringi: a[x,y] -> substring(a,x,y-x+1) a[x] -> substring(a,x,1) WEEKDAY( <date> ) -> DATEPART( weekday, <date> ) i pamiętać o ustawieniu na początku ‘SET DATEFIRST 1’ Operator LIKE w MS-SQLu jest rozszerzony w stosunku do standardu SQL-92. Przykłady funkcji: WEEKDAY tłumaczona na DATEPART LOGN tłumaczony na LOG POW tłumaczony na POWER Operatory Katenacja stringów (|| tłumaczony na +) MOD tłumaczony na % MATCHES tłumaczony na LIKE
Rozwiązania dla MS-SQL cz. IV SELECT ... OUTER JOIN SELECT f.*,g.* FROM T1, OUTER T2 WHERE T1.pole1=T2.pole1 AND T2.poleX MATCHES ‘A*’ SELECT f.*,g.* FROM T1 LEFT OUTER JOIN T2 ON T1.pole1=T2.pole1 AND T2.poleX LIKE ‘A%’ Poziomy izolacji: Informix: DIRTY READ, COMMITTED READ, CUSROR STABILITY, REPEATABLE READ MS-SQL: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE DIRTY READ = READ UNCOMMITTED COMMITTED READ = READ COMMITTED Transakcje Poziomy izolacji LOCK TABLE DELETE FROM T1 WITH (TABLOCK/TABLOCKX,HOLDLOCK) WHERE 0=1
Rozwiązania dla MS-SQL cz. V Podsumowanie: przydatne mechanizmy Tablice/Widoki systemowe Triggery Zewnętrzne procedury składowane i ODS Typy danych użytkownika Funkcje definiowane przez użytkownika Kolumny Identity O wydajności Wydajność naszego rozwiązania Wydajność MS-SQLa ODS = Open Data Services Na pewno zachodzi pewien spadek wydajności, ale nie jest on drastyczny. Wydajność Microsoft SQL jest porównywalna z każdym innym silnikiem na rynku Informix,Oracle, DB2 itd.
Migracja bibliotek runtime i terminali Biblioteki runtime Terminal – znakowy Terminal – graficzny Biblioteki runtime: Kod generowany przez kompilator 4GLa generuje (między innymi) wywołania funkcji, których definicje znajdują się w tych właśnie bibliotekach. Obsługują one: Typy danych i operacje na nich (stringi, czyli łańcuchy znakowe, liczby dziesiętne), wyrażenia (logiczne, arytmentyczne) Ekran (patrz terminal znakowy) Raporty Terminal tekstowy: Klasyczny UNIXowy terminal Telnet bądź ssh U nas realizowany przy użyciu biblioteki curses Terminal graficzny: koncepcja opisana w dalszym toku prezentacji
Migracja bibliotek runtime Biblioteki runtime napisane w C/C++ Jak to robić Stworzyć projekt - bibliotekę/biblioteki klas (Class Library .NET) Włączyć istniejące źródła Dodać bibliotekę msvcrt.lib W przypadku kodu C musimy dodać klasy, których metody wywołują funkcje w C Tak przygotowaną bibliotekę można użyć w C# na zasadach ogólnych Biblioteki runtime dla 4GLa działającego pod UNIXem są napisane w języku C/C++ (ok. 100 tysięcy linii kodu). Wykorzystaliśmy tryb unsafe (unsafe context). Trzeba zmodyfikować odrobinę żródła: Nagłówki funkcji Pamiętać o ‘extern ‘C’’ Pamiętać o __declspec( dllimport ) declarator __declspec( dllexport ) declarator W przypadku deklaracji w parametrach funkcji char *[] pojawia się problem Dodać bibliotekę msvcrt.lib (CRT: C Run-Time) w ‘Project Properties’/Linker/’Additional Dependencies’ Problemy z CRT dla bibliotek runtime (!!!) Trzy biblioteki: LIBC – single-threaded, static link LIBCMT – multithreaded, static link MSVCRT – multithreaded, dynamic link
Migracja terminala tekstowego Biblioteka pdcurses Biblioteka pdcurses: Implementacja curses (czyli ) pod Windows na licencji GNU Kompilacja bezproblemowa Przy kompilacji pod .NET problem z funkcjami o nieokreślonej liczbie parametrów
Terminal graficzny – koncepcja TSD Schemat komunikacji: Terminal Graficzny (TG) jest uruchomiony przez użytkownika TG komunikuje się z TSD (Terminal Server Daemon) TSD pobiera listę dostępnych aplikacji i zwraca ją TG Użytkownik wybiera aplikację i ta informacja jest zwracana TSD TSD uruchamia wybraną aplikację przekazując jej kanał komunikacyjny Od tej pory TG komunikuje się bezpośrednio z aplikacją Charakterystyka rozwiązania: Terminal graficzny komunikuje się z aplikacją przy użyciu opracowanego przez nas protokołu komunikacyjnego. Komunikacja po TCP/IP Pierwotnie zrealizowany w Javie Terminal można uruchomić z przeglądarki Internetowej 4GL ma własny język służący do definiowania formularzy i własny sposób ich obsługi Aplikacja Serwer
Przykład terminala graficznego Pierwszy obrazek, to terminal tekstowy (telnet lub ssh). Charakterystyka terminala graficznego (w porównaniu do terminala tekstowego): Wielookienkowość Wykorzystanie myszy Możliwość korzystania z obiektów okienkowych (buttons, comboboxes) Możliwość integracji z Word/Excel
Nowy terminal C# Napisany w C# Zachowana architektura Zachowany protokół komunikacyjny Wykorzystano możliwość łatwej integracji z C/C++
Porównanie Java z C# SWING vs. Windows Forms Model View Listener vs. Event handler Zwalnianie zasobów systemowych Integracja z C/C++ JNI vs. (safe/unsafe mode) Garbage Collector Różnice w wywoływaniu konstruktorów dziedziczonych Na pierwszy rzut oka Java jak i C# są bardzo podobne, jest to jednak mylące. Ad 1a) SWING jest dużo wolniejszy od .NET Windows Forms (pytanie: jak się ma Model View Listener to SWINGa) Musieliśmy częściowo zrezygnować ze SWINGa i użyć AWT aby zwiększyć szybkość (IBM: SWT - Standard Widget Toolkit) Ad 1b) Event handler – przeciążanie metod (np.. OnMouseDown) Model View Listener – implementacja interfejsów i rejestrowanie metod obsługujących Ad 1c) Trzeba o tym pamiętać zarówno w Javie jak i C# (czcionki, deskryptory plików, bitmapy) Ad 2) JNI – Java Native Interface Dużo lepiej jest to zrobione w .NET, dlatego że kod w C/C++ jest również kompilowany do CLR Ad 3) W dalszym ciągu mamy problemy z C#. W Javie zachowuje się różnie w zależności o wersji Javy (testowaliśmy na 1.2, 1.3 , 1.4) Jak widać koncepcja jest piękna, ale bez ścisłego stosowania się do reguł określonych przez producenta są z tym kłopoty. Ad 4) Problem li tylko składniowy (W Javie tepiej rozwiązane) Java – jawne wywołanie w treści kontruktora C# - deklarowanie w nagłówku konstruktora
Integracja z VS.NET - VSIP Mechanizmy rozszerzania VS Macro Add-In Pakiety VSIP VSIP – Visual Studio Integration Program Tworzenie nowego typu projektu Tworzenie edytora dla nowego języka Tworzenie projektanta Włączenie kompilatora od procesu kompilacji i konsolidacji projektu Tworzenie debuggera Makro umożliwia automatyzację zadań (‘manipulate the IDE Object Model’) i tworzenie okien narzędziowych Add-In dodatkowo umożliwia dodawanie nowych opcji w menu i właściwości w oknach dialogowych Pakiety VSIP oferują największe możliwości pozwalając dodatkowo dodać nowe języki programowania (a dla nich nowy typ projektu, edytor(y), debugger, designer itd) VSIP – Visual Studio Integration Program Pozwala na integrację kodu z pakietem Visual Studio Swojego czasu było płatne (i to słono), w chwili obecnej przystąpienie jest darmowe.
Technologia Integracji Architektura Visual Studio Powłoka Visual Studio (shell) Pakiety (komponenty COM) Pakiety VSIP są buduje się tak samo jak pakiety dostarczane z Visual Studio Dostarczane jest wiele pakietów przykładowych, z których większość można wykorzystać jako podstawę własnych rozwiązań „Serwisy językowe” (BABEL) Projekty (HierUtil7) Każdy z pakietów jest zbiorem serwisów udostępnionych na zewnątrz, czyli środowisku Visual Studio. Pakiety VSIP pisze się w unmanaged code Pakiety dostarczane z VS są również napisane w tej technologii, a więc pakiety napisane przez nas są traktowane na równi z dostarczonymi przez Microsoft. Pakiety integruje powłoka VS, która koordynuje ich działanie: powłoka ładuje pakiety, pakiety „wystawiają” usługi poprzez tę powłokę, tak aby powłoka lub inne pakiety mogły z nich korzystać. Jeżeli pakiet potrzebuje wywołać usługę z powłoki lub innego pakietu, „prosi” powłokę o wykonanie tej usługi. BABEL: implementuje się interfejs IBabelService
BABEL (Language Service) Co można uzyskać Podświetlanie składni (wybrany zestaw kolorów) Sprawdzanie poprawności składni Dopełnienie słów kluczowych, instrukcji i nazw funkcji Sprawdzanie nawiasów Szybkie podpowiedzi Komentarze Jak to zrobić (BABEL SDK) Korzysta się przygotowanego szablonu Definicja symboli (atomów) leksykalnych (LEX) Definicja gramatyki języka (YACC) Za edycję odpowiada standardowy edytor VS.NET (który oczywiście również jest pakietem), ale korzysta on z odpowiedniego serwisu językowego (w zależności od rozszerzenia pliku), aby prawidłowo obsłużyć podświetlanie składni itd. Oczywiście można również napisać od nowa własny edytor, ale nie ma to większego sensu, jako że za większość akcji związanych z konkretnym językiem programowania i tak odpowiada pakiet serwisu językowego. Realizacja IntelliSense. Istnieje whitepaper (Babel: Langage Integration into Visual Studio.NET), który zawiera kompletny przepis na utworzenie własnego serwisu językowego przy użyciu Babel SDK. Lex odpowiada za podświetlanie składni. Lexer przekształca tekst na tokeny, które przyporządkowujemy do określonych klas, np.. LINE COMMENT, NUMBER, KEYWORD, IDENT etc. Każdej klasie możemy przyporządkować własny kolor. Parser tworzy drzewo programu, które używane jest sprawdzania poprawności składni (np. sprawdzania poprawności nawiasów), generowania komunikatów o błędach. Jeżeli mamy dwa różne języki (tak jak w naszym przypadku .4GL i .PER), to każdy z nich musi być stworzony osobny language service. Language Service nie ma nic wspólnego z kompilacją (tylko edycja) – za tę część odpowiada Projekt.
A tak to wygląda Tu przykładowy/we ekrany z edytora 4GL Dodać opisy poszczególnych opcji.
Integracja 4GLa z C# C#: 4GL: Osoby.pracownik.imienazwisko( numer, ref imie, ref nazwisko ) 4GL: NAMESPACE osoby CLASS pracownik DATABASE osoby FUNCTION imienazwisko( numer ) DEFINE numer INTEGER DEFINE imie LIKE osoby.imie, nazwisko LIKE osoby.nazwisko SELECT f.imie, f.nazwisko INTO imie, nazwisko FROM osoby f WHERE f.id=numer RETURN imie, nazwisko END FUNCTION Obrazek pokazujący fragment kodu w C# z jednej strony i 4GL z drugiej
Podsumowanie: Co mamy z 4GL.NET Migracja naszego systemu ERP (ASIMS+) do Microsoft Windows Migrację aplikacji UNIX do technologii .NET Nowy język programowania dla platformy .NET
Bibliografia Inside Microsoft SQL Server 2000 Migrating Informix Databases to Microsoft SQL Server 2000 (Microsoft) UNIX Application Migration Guide (Microsoft) Inside Microsoft .NET IL ASSEMBLER (Serge Lidin) Programming Microsoft .NET (Jeff Prosise) http://www.vsipdev.com Mastering Visual Studio .NET (Jon Flanders, Ian Griffiths & Chris Sells (O’Reilly)) Whitepaper – Babel: Langugane Integration into Visual Studio.NET Dokumentacja MSDN