Wrapper für Command Line Tool

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von jvbsl.

    Wrapper für Command Line Tool

    Moin zusammen,

    ich möchte ein Programm erstellen, dass CBR und CBZ Dateien (das sind Comic Formate) komprimiert.
    Diese Formate sind im Wesentlichen Rar und Zip Archive, die jede Comicseite als JPG beinhalten.
    Jetzt habe ich nach einer Kompressionsbibliothek gesucht, aber keine gefunden. Dafür aber festgestellt,
    dass Caesium ein Command Line Tool hat. github.com/Lymphatus/caesium-clt

    Meine Frage ist jetzt, wie kann ich in C# einen Wrapper für das Tool schreiben?
    Also, dass ich in der C# Anwendung dem CLI Daten übergeben kann, und mitbekomme wenn der Prozess fertig ist.

    Danke im Voraus und Grüße
    Vainamo
    github.com/Lymphatus/libcaesium

    Ist die zugehörige Library, ich denke es ist sinnvoller dafür einen Wrapper zu schreiben. Und selbst dann eigt. eher unnötig, denn es sieht mir danach aus, als macht das nichts anderes als PNG/JPEG Komprimierung.

    msdn.microsoft.com/de-de/library/bb882583(v=vs.110).aspx
    Ob das für PNG auch geht weiß ich nicht, sieht aber eher nicht danach aus. Jedoch scheint WPF da was zu haben.
    Ansonsten gibt es bestimmt schon wrapper für libpng.

    Und eine Zip-Datei erstellen ist auch so einfach, dass ich dafür keine native Library verwenden würde, vorallem keine die so unbekannt ist.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hallo!
    Wenn ich das richtig verstanden habe, dann meinst du mit Wrapper eine Oberfläche für die Konsolenanwendung.
    Das ist kein Problem, wenn du die Konsolenanwendung bedienen kannst. Mit "Bedienen" meine ich natürlich, wenn du die Parameter kennst und weißt was sie tun. In der Regel bieten Befehlszeilen aber einen Hilfeparameter an, der dir alle Parameter und ihre Bedeutung auflistet.
    Vielleicht ist das sogar irgendwo in der von dir referenzierten Github Repo dokumentiert.

    Kannst du mit der Befehlszeile umgehen, dann bleiben dir wohl mehrere Möglichkeiten offen, wie du mit der Anwendung kommunizierst. Du kannst mittels Process.Start(ProcessStartInfo) das Tool starten, Parameter übergeben und auf die Beendigung des Prozesses warten.

    Folgender Code würde lediglich die Anwendung mit den angegebenen Parametern starten und auf die Beendigung des Prozesses warten:

    C#-Quellcode

    1. Process.Start(new ProcessStartInfo(filePath, parameters)).WaitForExit();


    Das darin erzeugte ProcessStartInfo-Objekt hat eine Menge Eigenschaften, die du dir zu Nutze machen kannst. Aktuell würde mit dem Code einfach das Konsolenfenster auftauchen, Fortschritt und andere Meldungen (wie bspw. Fehler) werden nicht übermittelt.
    Die ProcessStartInfo-Klasse stellt aber Eigenschaften bereit, die dir ermöglichen In -und Output der Befehlszeile an deine Anwendung weiterleiten und umgekehrt. Das kann man mit den Eigenschaften RedirectStandardInput, RedirectStandardOutput und RedirectStandardError erreichen.
    So kannst du dann die Ausgaben der Konsole interpretieren und darauf reagieren.

    Edit:
    Wenn @jvbsl recht hat und es eine Library dazu gibt, macht es natürlich mehr Sinn dafür einen Wrapper zu schreiben anstatt ein Frontend für eine Konsolenanwendung zu bauen.

    MfG Tim
    @Fortender Perfekt, das ist genau das was ich gesucht habe. Ich hätte mir eigentlich auch denken können, dass das über die Process Klasse läuft :rolleyes:
    Ich habe eben einen Ordner voller JPGs und möchte das Tool starten, damit es diese komprimiert und mein Programm mitbekommt, wann der Vorgang vorbei ist, damit ich die ganzen JPGs wieder packen kann. Danke!

    @jvbsl Ich habe weder die Zeit, noch das Wissen um eine ganze C Library zu wrappen. Aber vielleicht setze ich mich da mal später dran. Edit: Okay, das sieht wirklich spannend aus. Wenn ich mehr Zeit habe, probiere ich mich da mal dran!

    Natürlich macht die Library nur PNG und JPG Kompression, darum geht es ja. Ich weiß auch, dass das Framework dafür Funktionen hat, aber bei minimaler akzeptabler Qualität, ist die Dateigröße noch zu groß verglichen mit Caesium.

    Aber wie kommst du auf die Geschichte mit der ZIP-Erstellung? Abgesehen davon, dass ich die Erstellung (geschweige die Library dafür) mit keiner Silbe erwähne, benutze ich dafür keine unbekannte native Library sondern 7-ZIP.

    Grüße
    Vainamo

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Vainamo“ ()

    @Vainamo Ich hab mir gedade für ffmpeg einen Wrapper gemacht, ein wenig komfortabler, WaitForExit() kommt da nicht drin vor, da wird ja die GUI ausgebremst.
    Der Ablauf ist so:

    C#-Quellcode

    1. this.Cmd = new Process();
    2. this.Cmd.Exited += this.ProcessClosed;
    3. this.Cmd.EnableRaisingEvents = true;
    4. this.ProcessClosed(null, EventArgs.Empty);

    C#-Quellcode

    1. private void ProcessClosed(object sender, EventArgs e)
    2. {
    3. if (sender != null)
    4. {
    5. // das nullte Mal nicht
    6. this.Invoke(new Action(this.FileReady));
    7. }
    8. // ...
    9. this.Cmd.Start();
    10. System.Threading.Thread.Sleep(250); // warten, bis das Fenster steht
    11. this.SetWindowPos(this.Cmd.MainWindowHandle); // die Console auf meine Position schieben
    12. }
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany Danke für die Antwort, aber in meinem Fall ist das nicht weiter tragisch, da es sich um eine ConsoleApplication handelt und man in der Zeit wo der Prozess läuft eh nichts machen kann.

    Grüße
    Vainamo
    Es ist ja auch kein Muss WaitForExit() zu verwenden. Es sollte lediglich ein Denkanstoß sein, da ich nur ungerne C&P Code in den Raum werfe.
    Wenn du die Ausgaben der Konsole "auswerten" und gleich darstellen willst, und zwar ohne dass deine GUI einfriert und sich erst wieder meldet sobald die Konsolenanwendung geschlossen wird, dann würde ich auf keinen Fall WaitForExit() verwenden. ROD hat ja bereits eine Variante gepostet.

    Ich würde aber trotzdem wie @jvbsl schon schrieb, die Library wrappen. Ist ja immerhin alles dokumentiert.
    @Vainamo Sorry ich dachte, das Zip erstellen wird auch von dem Commandline tool übernommen. Aber wie gesagt Commandline tools find ich unschön.

    Ich finds halt immer gut, wenn man die Dinge die man braucht geschickt mitliefern kann:
    nuget.org/packages/SharpZipLib/1.0.0-alpha2
    über nuget z.B. Und eine library ist halt auch schöner zu verwenden.

    Für den caesium Wrapper brauchst du eigt. nur diese Funktionen:

    C-Quellcode

    1. bool cs_compress(const char *input_path, const char *output_path, cs_image_pars *options);
    2. void initialize_jpeg_parameters(cs_image_pars *options);
    3. void initialize_png_parameters(cs_image_pars *par);
    4. cs_image_pars initialize_parameters();

    github.com/Lymphatus/libcaesium
    Die structs kannst du eigt. kopieren
    brauchen nur das StructLayout:

    C#-Quellcode

    1. [StructLayout(LayoutKind.Sequential)]


    Dann sollte es schon funktionieren. Wenn du dann willst dass es auf 32bit/64bit läuft brauchst du beide libraries(ich mach immer in Unterordner x86/x64)
    und beim Start gibst du dann den jeweilig passenden Unterordner(ausgewählt durch Environment.Is64BitProcess) an:
    pinvoke.net/default.aspx/kernel32.setdlldirectory

    Dann kannste noch einen Schritt weitergehen und SetDllDirectory nur für Windows aufrufen. Zusätzlich:
    mono-project.com/docs/advanced/pinvoke/dllmap/
    mit wordsize="32" oder wordsize="64" zwischen den Architekturen unterscheiden und somit das ganze Programm Linux/Mac kompatibel machen.
    Musst aber natürlich auch die für Linux/Mac die native library extra kompilieren(müsstest du für das CMD-Tool aber auch)
    Edit: hach wenn auch das .Net Framework ne DllMap hätte würde das so vieles einfacher machen :D
    Edit2: Du könntest dann auch aus dem caesium wrapper eine eigene .Net Library machen und an den Entwickler von caesium schicken(evtl. nuget package machen)
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „jvbsl“ ()

    @jvbsl Das klingt schon mal sehr interessant. Ich hatte es vorhin mal ausprobiert, hab es aber komischerweise nur einmal geschafft, ein Bild zu komprimieren. Danach wollte mein DllImport Konstrukt zwar noch, aber die libcaesium nicht mehr. Eventuell lag es an den Options, da ich da einiges vergessen hab. Das schaue ich mir auf jeden Fall an, wenn ich mehr Zeit habe.

    Fortender schrieb:

    Wenn du die Ausgaben der Konsole "auswerten" und gleich darstellen willst, und zwar ohne dass deine GUI einfriert und sich erst wieder meldet sobald die Konsolenanwendung geschlossen wird, dann würde ich auf keinen Fall WaitForExit() verwenden.

    Dann würde ich das auch auf keinen Fall machen, aber hier gibt es eben keine GUI sondern eine reine Konsolenanwendung, da ist das nicht weiter problematisch.

    Das ganze ist auch nur ne Quick and Dirty Solution weil ich bis morgen früh ca. 200 Comics komprimieren muss. Da ist mir GUI und Co. einfach zu aufwendig auf die Schnelle. Aber ich hab schon überlegt, das ganze anständing als Projekt in Angriff zu nehmen, dann wird das natürlich um einiges ordentlicher.

    Grüße
    Vainamo
    @Vainamo Du musst bis morgen 200 Comics komprimieren
    UND
    { bist danach endgültig fertig
    ODER
    musst iwann weitere komprimieren }?
    Im ersten Fall wäre es mir egal und ich würde die Q&D-Variante machen.
    Im zweiten Zall würde ich die Q&D-Variante arbeiten lassen und die GUI-Variante in Ruhe angehen.
    ====
    Zu Linux & Co kann ich nix sagen.
    ====
    Ich hab n Sack voll MP3-Filme und hab mir in Ruhe ne GUI gebaut, um die nach MP4 zu konvertieren, das ist im Augenblick einfacher, als ne neue Festplatte kaufen zu müssen, weil die MP4 nur 30%...50% des Platzes belegen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany Fall 2. Ich bin morgen mit dem Zug unterwegs und dann erstmal ne Woche im Urlaub. Dafür wollte ich halt runde 200 Comics mitnehmen, die mir aber alle zu groß sind. Mein Reader der Wahl hat nicht mehr genug Platz für alle. Daher jetzt halt schnell ne Q&D Variante um das bis morgen fertig zu bekommen und dann in aller Ruhe ne GUI Variante erarbeiten, um die restlichen 2500 Comics auf der Platte zu komprimieren.

    Edit: @jvbsl Das mit den Architekturen hab ich jetzt noch nicht so wirklich verstanden. Warum würde mein Wrapper nicht auf beiden laufen? Und was müsste ich tun, damit er es tut?
    Und die Pointer übergebe ich mit ref, richtig?

    Grüße
    Vainamo

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Vainamo“ ()

    Vainamo schrieb:

    @jvbsl Das mit den Architekturen hab ich jetzt noch nicht so wirklich verstanden. Warum würde mein Wrapper nicht auf beiden laufen? Und was müsste ich tun, damit er es tut?

    Ich habe das wie folgt verstanden.
    @jvbsl geht es wohl um Cross-Platform Support, sodass man deinen Wrapper nicht nur unter Windows, sondern z.B. über Mono auch auf Linux verwenden kann. Mit den DLLs kommt Mono bzw. Linux aber nicht zurecht und erwartet stattdessen .so-Files. Es muss also in einer Konfigurationsdatei festgelegt werden, wie die DLLs unter Linux zu den .so-Files gemappt werden müssen.
    @Fortender Aber was spielt die Architektur dann für eine Rolle?

    jvbsl schrieb:

    Wenn du dann willst dass es auf 32bit/64bit läuft brauchst du beide libraries(ich mach immer in Unterordner x86/x64)
    und beim Start gibst du dann den jeweilig passenden Unterordner(ausgewählt durch Environment.Is64BitProcess) an


    @jvbsl Ich hab dann bis jetzt folgenden Code. Könntest du einen Blick drüber werfen, ob das so passt?

    C#-Quellcode

    1. #pragma warning disable IDE1006
    2. using System.Runtime.InteropServices;
    3. // ReSharper disable FieldCanBeMadeReadOnly.Local
    4. // ReSharper disable InconsistentNaming
    5. namespace Sodium.Native
    6. {
    7. public static class Caesium
    8. {
    9. [DllImport("libcaesium.dll", CallingConvention = CallingConvention.Cdecl)]
    10. public static extern bool cs_compress(ref string input_path, ref string output_path, ref cs_image_pars options);
    11. [DllImport("libcaesium.dll", CallingConvention = CallingConvention.Cdecl)]
    12. public static extern void initialize_jpeg_parameters(ref cs_image_pars options);
    13. [DllImport("libcaesium.dll", CallingConvention = CallingConvention.Cdecl)]
    14. public static extern void initialize_png_parameters(ref cs_image_pars par);
    15. [DllImport("libcaesium.dll", CallingConvention = CallingConvention.Cdecl)]
    16. public static extern cs_image_pars initialize_parameters();
    17. }
    18. [StructLayout(LayoutKind.Sequential)]
    19. public struct cs_image_pars
    20. {
    21. private cs_jpeg_pars jpeg;
    22. private cs_png_pars png;
    23. }
    24. [StructLayout(LayoutKind.Sequential)]
    25. public struct cs_jpeg_pars
    26. {
    27. private int quality;
    28. private bool exif_copy;
    29. private int dct_method;
    30. private int color_space;
    31. private int subsample;
    32. private int width;
    33. private int height;
    34. }
    35. [StructLayout(LayoutKind.Sequential)]
    36. public struct cs_png_pars
    37. {
    38. private int iterations;
    39. private int iterations_large;
    40. private int block_split_strategy;
    41. private bool lossy_8;
    42. private bool transparent;
    43. private int auto_filter_strategy;
    44. }
    45. }


    Grüße
    Vainamo

    Vainamo schrieb:

    ob das so passt?
    Bei den Ref-Aufrufen der Strukturen bin ich mir nicht sicher, ob das so funktioniert, ich würde die eher Marshallen, vom Prinzip her gugst Du Austausch von Daten zwischen einer VB.NET-exe und einer C-DLL, 32 und 64 Bit
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    also die strings nicht als ref für die struct-fields würd ich public verwenden, willst ja bestimmt abändern :D
    Für die strings musst evtl. marshallen, könnte aber so bereits gehen.

    Was 32/64 Bit anbelangt ist dies tatsächlich ein Vorteil von einem Command line tool, da ein 32Bit tool auf 64Bit ebenfalls läuft. Jedoch kann ein 64Bit Prozess keine 32 Bit dlls laden, das ist nur über COM möglich(im Hintergrund läuft dort aber tatsächlich immer ein 32 und 64 Bit prozess, die dann jeweils die Dlls laden und über IPC die Funktionen mappen).

    Ansonsten hat man für DLLs 2 Möglichkeiten:
    1. du stellst fest auf x86 oder x64 in den Projekteinstellungen ein und verwendest entsprechend eine native DLL
    2. du lässt auf AnyCPU und lässt die Applikation zur Laufzeit entscheiden welche DLL geladen werden muss und legst eben die DLLs für beide Architekturen bei.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „jvbsl“ ()