sobota, 31 grudnia 2011

Uruchamianie zewnętrznego programu

Przy okazji chciałbym złożyć wszystkim, którzy czasami zerkają na mojego bloga najserdeczniejsze życzenia noworoczne, żeby wasz kod się kompilował szybko i bezbugowo, żebyśmy w tym nowym roku dalej poszerzali swoje zainteresowania, rozwijali się, ale też znaleźli czas na zabawę w fajnym towarzystwie. Szczęśliwego i udanego Sylwestra.


Święta, święta i po świętach, jeszcze Sylwester-Nowy rok i powrót na studia. Semestr się powoli kończy, a więc studenci informatyki zaczynają pisać projekty na niektóre przedmioty. Jak się okazało, może to czasami być przyczynkiem do rozpoznania jakiegoś zagadnienia.

Moim zdaniem dość ciekawy projekt trafił się nam na Grafice Komputerowej i Multimediach. Mamy napisać własny format pliku graficznego z kompresją - wybór padł na kompresję LZW oraz dwa programy: konwertujący plik BMP na nasz format i drugi, który konwertuje w drugą stronę. Podczas dyskusji z członkami zespołu projektowego, doszliśmy do wniosku, że napiszemy to w C++ jako aplikacje konsolowe. Jeżeli starczy nam czasu, to stworzymy programy - "nakładki graficzne". Będą to programy okienkowe zapewniające przyjazny sposób wyboru paru opcji np. tryb koloru (RGB, CMYK i coś tam jeszcze ma być). Programy te będą uruchamiać programy konsolowe napisane w C++ i wybrane opcje przekazywać poprzez listę argumentów wywołania. Pomyślałem sobie, że fajnie by było takie coś napisać przy użyciu Swinga - dzięki temu usiadłem i zacząłem badać temat: Jak uruchomić z poziomu Javy program napisany w C++?


Odpaliłem Code::Blocks, zrobiłem prosty programik, który zapisuje listę przekazanych do niego parametrów do pliku o takiej samej nazwie jak ten program, ale z rozszerzeniem .txt.
#include <iostream>
#include <fstream>

using namespace std;

int main(int argc, char** argv)
{
    string str = argv[0]; // Zawsze pierwszy argument to ścieżka do tego programu
    str += ".txt";
    ofstream file(str.c_str());
    file << "argc = " << argc << endl;
    file << "argv:" << endl;
    for(int i=0; i < argc; i++)
    {
        file << argv[i] << endl;
    }
    file.close();
    return 0;
}
Zapewne patrzysz na ten kod i zastanawiasz się: nie mogłeś po prostu zrobić ofstream file("out.txt") ? Pod Windowsem pewnie nie ma takich problemów, natomiast pod Linuxem się dzieje coś złego.Jeżeli uruchamiamy ten program C++ bezpośrednio, to plik out.txt tworzony jest w tym samym katalogu. Jeżeli natomiast uruchomiony zostanie przez program napisany w Javie (np. udostępniany w postaci JARa), to wtedy plik out.txt ląduje w katalogu domowym użytkownika. Jeśli uruchamiamy program javowy w IDE, to wtedy sprawa wygląda fajnie, bo current directory jest ustalone na katalog naszego projektu. Ale przecież nie każemy użytkownikowi uruchamiać naszej aplikacji z poziomu np. Eclipsa. Tak więc rozwiązaniem na które wpadłem, jest użycie pierwszego argumentu, który przechowuje ścieżkę do uruchomionego programu. Dobra, ale aplikacja javy, np. wpakowana do JARa musi wiedzieć, gdzie ten JAR się znajduje. Kolejne pytanie: Jak pobrać ścieżkę do uruchomionego pliku JAR? Aplikacja javowa nie posiada GUI, bo to tylko proste testy, ale nic nie stoi na przeszkodzie, żeby zaprzęgnąć Swinga do działania - na razie tylko wywalam sobie nim komunikat dla testu. Kod prezentuje się następująco:
package com.blogspot.pgoralik.testexec;

import java.io.File;
import java.io.IOException;
import javax.swing.*;

public class Test {

    /**
    * @param args
    */
    public static void main(String[] args) {
        try {
            File jarPath = new File(System.getProperty("java.class.path"));
   
            JOptionPane.showMessageDialog(null, jarPath.getParentFile());
            
            // Przygotuj argumenty - Test to plik wykonywalny położony obok JARa
            String[] arguments = new String[3];
            arguments[0] = jarPath.getParentFile() + "/Test";
            arguments[1] = "RGB";
            arguments[2] = "Filtr";
   
            Process p = Runtime.getRuntime().exec(arguments);
            try {
                p.waitFor();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            p.destroy();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
}
System.getProperty(String) zwraca nam wybraną właściwość podaną jako parametr. Więcej właściwości znajdziesz tutaj. Dzięki java.class.path możemy pobrać ścieżkę, skąd uruchomiony został program. W przypadku JARa jest to położenie pliku JAR (/cośtam/cośtam/nazwa.jar). Na podstawie tej ścieżki tworzymy obiekt File, następnie w prosty sposób pobieramy rodzica tego obiektu metodą getParentFile() (katalog w którym się znajduje jeśli jest to plik, albo katalog nadrzędny, gdy obiekt File jest katalogiem). Mamy to co chcieliśmy!

Tworzymy listę argumentów i przekazujemy ją w Runtime.getRuntime().exec(String[]). Tworzony jest nowy proces na którym uruchomiony zostaje nasz program C++'owy o nazwie Test. Metodą waitFor() mówimy, że proces rodzic ma zaczekać, aż ten proces na rzecz którego wywołaliśmy tą metodę się zakończy (chyba dobrze to zrozumiałem?).

Efekt jest taki, że mając program w pliku JAR możemy  zidentyfikować jego położenie i używać zewnętrznych zasobów. Nie wiem czy jest to najlepsze podejście, albo czy przynajmniej dobre, ale działa :).

Brak komentarzy:

Prześlij komentarz