Pokazywanie postów oznaczonych etykietą nsis. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą nsis. Pokaż wszystkie posty

niedziela, 28 czerwca 2009

NSIS - Ekslibrisowa ekwilibrystyka czyli piszemy własną bibliotekę

Sława!

Dziś chciałbym jakże szanownym Czytelniczkom i Czytelnikom zaprezentować parę wskazówek jak pisać własne biblioteki/pliki nagłówkowe dla NSIS. Po cóż, a po coż to się męczyć z czymś takim? - zapyta ktoś bardziej marudny. Ha! Otóż marudo moja kochana, po piersze primo zawsze warto wydzielać sobie wieloużywalne kawałki własnego kodu, żeby potem nie wymyślać koła po raz kolejny. A po drugie primo, w wypadku NSISa główny plik projektu przy większej komplikacji ma tendencje do bardzo szybkiego rozrostu, toteż pogubienie się w kodzie jest tylko kwestią czasu. I właśnie aby czas ten wydłużyć warto jednak powydzielać sobie funkcję do bibliotek.

Od czego zacząć? Wydaje mi się, że od wydzielenia, przynajmniej koncepcyjnie, tego co chcemy sobie zamknąć w funkcję w ramach biblioteki (a może bibliotek), oraz wymyśleniu sobie nazw zarówno dla funkcji, jak i dla biblioteki. Należy pamiętać, że nie powinny być zbyt lakoniczne, ani też za długie, bo na dzień dzisiejszy narzędzia wspierające pisanie w NSIS nie podpowiadają składni zawartej w bibliotekach. Jeżeli już wymyśliliśmy sobie to i owo, możemy przystąpić do dzieła (a może nawet Dzieła).

Załóżmy, że nową bibliotekę nazwaliśmy sobie PNG (od Przebłysk Naszego Geniuszu) i zawierać będzię ona jedną funkcję o nazwie, jakżeby inaczej, Funkcja. Jedną bo ma posłużyć za przykład, kolejne sobie możemy dorobić już siłą rozpędu. Dodajmy także, że będziemy chcieli jej używać zarówno w instalatorach jak i deinstalatorach.

Zaczynamy więc od następujacego szkieletu:

!ifndef PNG
    !define PNG

    !verbose push
    !verbose 3

    !ifndef PNG_DELIM
        !define PNG_DELIM '.'
    !endif
    
    !ifndef PNG_UN
        !define PNG_UN
    !endif

 #Includes
 
    !ifndef NFUPREFIX
        !include nfUtils.nsh
    !endif

#Installer-----------------------------------------------------------------------------------------

#Callers-----------------------------------------

#Bodies------------------------------------------

#Unistaller----------------------------------------------------------------------------------------

#Callers-----------------------------------------

#Bodies------------------------------------------

    !verbose pop
!endif

Gdzie, PNG_DELIM określa delimiter między nazwą biblioteki, a nazwą funckji - przyjąłem założenie, że używać będziemy ich w sposób podobny do nsArray, czyli poprzez zapis ${PNG.Funkcja}. Definicja PNG_UN jest podstawą pewnej sztuczki przy wspieraniu deinstalatorów, o czym później może parę słów (Ale niczego nie obiecuję). W sekcji #Includes dodajemy niezbędne polecenia include dołączające zewnętrzne biblioteki (np. LogicLib). Warto dołączać je tutaj, żeby być pewnym, że żadnej nie zabraknie. Włączona tu została biblioteka nfUtils, wspomagająca tworzenie własnych bibliotek. Wreszcie sekcje #Callers zawierać będą "wołacze" do funkcji, czyli to co umożliwi użycie wspomnianego zapisu ${PNG.Funkcja}, a #Bodies już właściwe ciała funkcji.

Uzupełnijmy więc te sekcje dla cześci "instalacyjnej":

#Callers-----------------------------------------

;Funkcja
;
;parametr
;   parametr
;
;zwraca
; wynik
!macro PNG${PNG_DELIM}Funkcja_Call _parametr _wynik
    Push ${_parametr}
    Call PNG${PNG_DELIM}Funkcja
    Pop ${_wynik}
!macroend

#Bodies------------------------------------------
!macro PNG${PNG_DELIM}Funkcja
    !ifndef ${PNG_UN}PNG${PNG_DELIM}Funkcja
        !define ${PNG_UN}PNG${PNG_DELIM}Funkcja '!insertmacro ${PNG_UN}PNG${PNG_DELIM}Funkcja_Call'

        ;$1 - wynik
        ;$0 - parametr
        ${nfu.Function} ${PNG_UN}PNG${PNG_DELIM}Funkcja in $0 out $1 '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ''
         # Ciało funkcji
   
        ${nfu.FunctionEnd}
    !endif
!macroend

W ciale funkcji w $0 znajdzie się przekazywany parametr, natomiast zawartość $1 przy wyjściu z funkcji będzie umieszczona na stosie. Po zakończeniu funkcji wartości $0 i $1 będą takie same jak przed jej wykonaniem. Jeżeli chcemy używać w ciele innych zmiennych $0-9 należy samemu obsłużyć podobne zachowanie za pomocą operacji na stosie.

Zostało więc uzupełnienie sekcji dla deinstalatora, tu już chyba nie ma większej magii:

#Callers-----------------------------------------
!macro un.PNG${PNG_DELIM}Funkcja_Call _parametr _wynik
    Push ${_parametr}
    Call un.PNG${PNG_DELIM}Funkcja
    Pop ${_wynik}
!macroend

#Bodies------------------------------------------
!macro un.PNG${PNG_DELIM}Funkcja
    !undef ${PNG_UN}
    !define ${PNG_UN} 'un.'
    !insertmacro PNG${PNG_DELIM}Funkcja
    !undef ${PNG_UN}
    !define ${PNG_UN}
!macroend

I to było by na tyle. Wnikliwsza analiza, wykaże, że cała zabawa polega na sprytnym wykorzystaniu poleceń preprocesora NSIS do wpływania na proces generowania "wynikowego" źródła do kompilacji, podkładając mu odpowiedni kod w miejsce używanych definicji. Dlatego też, możemy używać naszej biblioteki podobnie jak LogicLib czy nsArray poprzez ${PNG.Funkcja} 'parametr' $Wynik

czwartek, 2 kwietnia 2009

NSIS - obsługa różnych typów plików konfiguracyjnych

Sława!

Czasami zdarza się, że robiąc instalator do swojej aplikacji, chcemy jednocześnie zawrzeć w nim podstawową konfigurację swojego produktu tak, aby po procesie instalacji nadawał się on w większości przypadków już do użycia. Jeżeli dla tego produktu konfiguracja taka sprowadza się do wypełnienia dwóch, trzech pól i zapisania ich wartości w docelowym pliku, nie nastręcza to zbytnich trudności. Ot, wystarczy zrobić plik *.ini strony, skrobnąć funkcję obsługi i voila. Pikuś, morda, gitara gra. :)

Problemik (ale przecież, jak zawsze w naszym wypadku, tyci, tyciunki ;)) może się zacząć robić wtedy, kiedy tych produktów zaczyna być więcej, albo tych pól do konfiguracji zaczyna się mnożyć ponad jedną stronę, albo...

Wtedy przydałoby się oczywiście jakoś sobie pracę usprawnić. Jednym z możliwych rozwiązań, jest wykorzystanie do tego celu plików... *.ini. Otóż okazuje się, że każdy z elementów strony, jak i strona jako całość może mieć również właściwości niestandardowe, które można wykorzystać do przechowywania informacji potrzebnych dla naszych uniwersalnych funkcji/makr obsługujących konfigurację.

Załóżmy, że plik konfiguracyjny jest plikiem XML, wtedy do pól dodawać możemy informację o XPath skąd i gdzie potrzebujemy pobrać i zapisać wartości. W naszym pliku ini znaleźć się może więc coś takiego.

[Settings]
NumFields=3
TargetFile=config.xml

[Field 3]
Type=GroupBox
Left=3
Top=4
Right=145
Bottom=135
Text=Configuration

[Field 1]
Type=Label
Left=9
Top=15
Right=110
Bottom=24
Text=SMTP server:

[Field 2]
Type=Text
Left=9
Top=26
Right=141
Bottom=36
State=smtp.host
MinLen=1
Flags=
XPath=/mail-module/mail-sender/smtp-host

Natomiast kod odpowiedzialny za inicjalizowanie wyświetlania strony miałby postać mniej więcej taką:

${xml::LoadFile} '$TARGETDIR\$TARGETFILE' $0
${If} $0 == 0
   ReadIniStr $NUMFIELDS $INIFILE "Settings" "NumFields"

   ${For} $R9 1 $NUMFIELDS
      ${xml::RootElement} $0 $1
      ReadIniStr $XP  $INIFILE 'Field $R9' 'XPath'

      ${If} $XP != ""
         ${xml::XPathNode} $XP $0

         ${If} $0 == 0
            ${xml::GetText} $1 $0
         ${EndIf}

         ${If} $0 == 0
            WriteINIStr $INIFILE 'Field $R9' 'State' '$1'
         ${Else}
            MessageBox MB_OK|MB_ICONEXCLAMATION 'Error during reading:$\r$\nFile: $TARGETDIR\$TARGETFILE$\r$\nXpath: $XP'
         ${EndIf}
      ${EndIf}
   ${Next}

   InstallOptions::initDialog /NOUNLOAD $INIFILE
   Pop $HWND

   InstallOptions::show /NOUNLOAD
${Else}
   MessageBox MB_OK "Can't find file: $TARGETDIR\$TARGETFILE"
   Abort
${EndIf}

Na analogicznej zasadzie konstruujemy funkcje zapisujące do pliku XML, używając mniej więcej czegoś takiego:

ReadIniStr $NUMFIELDS $INIFILE "Settings" "NumFields"

${For} $R9 1 $NUMFIELDS
   ReadIniStr $XP  $INIFILE 'Field $R9' 'XPath'

   ${If} $XP != ''
      ReadIniStr $0 $INIFILE "Field $R9" "State"
                  
      ${xml::RootElement} $1 $1
      ${xml::XPathNode} $XP $1
      ${xml::SetText} $0 $1
      ${xml::SaveFile} '$TARGETDIR\$TARGETFILE' $1
   ${EndIf}
${Next}

Dzięki temu tworzenie kolejnych konfiguracji sprowadzi się jedynie do ustawienie odpowiednich wartości w plikach *.ini. Jeżeli jeszcze wydzielimy sobie wieloużywalny kod do plików nagłówkowych, tworzenie instalatorów na pęczki będzie niczym bułka z masłem na śniadanko. :)

niedziela, 15 lutego 2009

NSIS i usługi Windows

Sława!

Dziś w ramach akcji ratowania doświadczenia przed sklerozą własną, zapiszę sobie jak z poziomu NSIS (podbudowa do tworzenia instalatorów dla aplikacji Windows), a nóż widelec i potomność będzie miała z tej notatki pożytek jakowyś.

Takowoż więc do obsługi usług potrzebujemy wtyczki Service Control Management (nsSCM). Wtyczka ta umożliwia na następujące operacje na usługach:

  • Install - zarejstrowanie usługi
  • Start - uruchomienie usługi
  • Stop - zatrzymanie usługi
  • QueryStatus - odpytanie o aktualny stan usługi
  • Remove - wyrejestrowanie usługi

Używanie jest banalnie proste, gdyż poza rejestrowaniem usługi, sprowadza się do wywołania odpowiedniej funkcji wtyczki z podaniem nazwy usługi, a następnie zczytania wyniku operacji. Jakiś przykład? Proszę bardzo, niech będzie nim wyłączenie/włączenie usługi w zależności od jej aktualnego stanu.

Zaczniemy od odpytania o stan usługi poleceniem:

nsSCM::QueryStatus /NOUNLOAD nazwa_usługi

Polecenie to, oprócz informacji o powodzeniu/błędzie wywołania, zwróci nam stan usługi. Możliwe wartości to:

  • 1 - usługa zatrzymana
  • 2 - usługa w trakcie uruchamiania
  • 3 - usługa w trakcie zatrzymywania
  • 4 - usługa uruchomiona
  • 5 - usługa w trakcie wznawiania
  • 6 - usługa w trakcie wstrzymywania
  • 7 - usługa wstrzymana

Załóżmy, że będziemy uruchamiać usługę, jeżeli jest zatrzymana lub wstrzymana, natomiast zatrzymywać jeżeli uruchomiona, pozastałe stany poprostu zignorujemy. Do zatrzymania usługi wykorzystamy kod:

nsSCM::Stop nazwa_usługi

Do uruchomienia natomiast:

nsSCM::Start nazwa_usługi

Tak więc nasz kod powinien uzyskać postać podobną do poniższej:

nsSCM::QueryStatus nazwa_usługi
Pop $0
${If} $0 == "success" Pop $1    ${Switch} $1       ;Usługa jest zatrzymana       ${Case} 1       ;Usługa jest wstrzymana       ${Case} 7          ;Uruchamiamy usługę          nsSCM::Start <nazwa_usługi>          Pop $2          ${If} $2 == "success"             MessageBox MB_OK "Usługa uruchomiona"          ${Else}             MessageBox MB_ICONEXCLAMATION|MB_OK "Błąd uruchamiania"          ${EndIf}       ${Break}       ;Usługa jest uruchomiona       ${Case} 4          ;Wstrzymujemy usługę          nsSCM::Stop <nazwa_usługi>          Pop $2          ${If} $2 == "success"             MessageBox MB_OK "Usługa zatrzymana"          ${Else}             MessageBox MB_ICONEXCLAMATION|MB_OK "Błąd zatrzymywania"          ${EndIf}       ${Break}    ${EndSwitch} ${EndIf}
Widzimy, więc, że usługi nie gryzą (przynajmniej nie za często ;)) i zostało nam tylko napisać aplikację, dzięki której możliwości nsSCM będziemy mogli wykorzystać, a to przecież dla nas pryszcz, co nie? ;)