Johann Weiher

>Johann Weiher

About Johann Weiher

Gründer, Seitenbetreiber, Hausmeister

NamedPipeStream InOut Async

Ich habe gerade nen Anwendungsfall da müssen sich auf nem Rechner zwei Applikationen austauschen.
Microsoft hat dafür die Named Pipes oder die Anonymous Pipes.

Die Anonymous Pipes haben paar Nachteile, diese hab ich dann mal nicht verwendet:

  • Bidirektional nicht möglich (InOut)
  • Async nicht möglich

Stackoverflow & Google hat irgendwie nicht das passende für mich, somit hab ich mir mal ne kleine Klasse geschrieben welche mir dann den Server & Client bereitstellt. Derzeit geht das dann nur lokal und nicht mit einem Netzwerkprozess. Aber ggf. pass ich das noch an. Angewendet wird das ganze wie folgt:

var pipeServer = new PipeBidirectional(PipeType.Server);
pipeServer.PipeMessage += PipeServerOnPipeMessage;
pipeServer.Start("MyPipe";
pipeServer.ListenAsync();
pipeServer.WriteAsync("Hello from Server");

private void PipeServerOnPipeMessage(string args)
{
  // Do stuff
}

 

var pipeClient = new PipeBidirectional(PipeType.Client);
pipeClient.PipeMessage += PipeServerOnPipeMessage;
pipeClient.Start("MyPipe");
pipeClient.ListenAsync();
pipeClient.WriteAsync("Hello from Client");

private static void PipeServerOnPipeMessage(string args)
{
  // Do stuff
}

 

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;

namespace Suplanus.NamedPipes
{
  public class PipeBidirectional : IDisposable
  {
    public int MaxNumberOfServerInstances { get; set; } = 1;
    public int InBufferSize { get; set; } = 4096;
    public int OutBufferSize { get; set; } = 4096;

    public event DelegateMessage PipeMessage;

    private readonly PipeType _pipeType;
    private NamedPipeServerStream _serverPipe;
    private NamedPipeClientStream _clientPipe;
    private Process _clientProcess;
    private StreamWriter _streamWriter;
    private StreamReader _streamReader;

    public PipeBidirectional(PipeType pipeType)
    {
      _pipeType = pipeType;
    }

    public void Start(string pipeName, string clientProcessPath = null)
    {
      switch (_pipeType)
      {
        case PipeType.Server:
          _serverPipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut,
                                                  MaxNumberOfServerInstances,
                                                  PipeTransmissionMode.Message, PipeOptions.Asynchronous, InBufferSize,
                                                  OutBufferSize);

          // Start client process
          if (!string.IsNullOrEmpty(clientProcessPath))
          {
            _clientProcess = Process.GetProcesses()
                                    .FirstOrDefault(obj => clientProcessPath.Contains(obj.ProcessName) &&
                                                           obj.MainModule != null &&
                                                           obj.MainModule.FileName.Equals(clientProcessPath));
            if (_clientProcess == null)
            {
              _clientProcess = Process.Start(new ProcessStartInfo
              {
                CreateNoWindow = true,
                FileName = clientProcessPath,
                UseShellExecute = false,
              });
            }
          }
      
          // Start server
          _serverPipe.WaitForConnection();
          _streamWriter = new StreamWriter(_serverPipe);
          _streamWriter.AutoFlush = true;
          _streamReader = new StreamReader(_serverPipe);
          break;

        // Start client
        case PipeType.Client:
          _clientPipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut,
                                                  PipeOptions.Asynchronous);
          _clientPipe.Connect();
          _streamWriter = new StreamWriter(_clientPipe);
          _streamWriter.AutoFlush = true;
          _streamReader = new StreamReader(_clientPipe);
          break;
      }
    }

    public async void ListenAsync()
    {
      do
      {
        if (_streamReader != null)
        {
          string line = await _streamReader.ReadLineAsync();
          if (!string.IsNullOrEmpty(line))
          {
            PipeMessage?.Invoke(line);
          }
        }
      }
      while (true);

      // ReSharper disable once FunctionNeverReturns
    }

    public async void WriteAsync(string message)
    {
      await _streamWriter.WriteLineAsync(message);
    }

    public void Dispose()
    {
      _streamWriter?.Dispose();
      _streamReader?.Dispose();

      if (_serverPipe != null && _serverPipe.IsConnected)
      {
        _serverPipe?.Disconnect();  
      }
      
      _serverPipe?.Dispose();
      _clientPipe?.Dispose();
      _clientProcess?.Kill();
      _clientProcess?.Dispose();
    }
  }

  public delegate void DelegateMessage(string args);
}

Ich hab das hier mal als Gist gespeichert. Paar Sachen sind noch nicht so schön, wie z.B. dass der optionale Process einfach so gekillt wird. Hier könnte man diese per Message einfach schön runterfahren. In meinem Fall ist das aber so OK 🦄

By |2019-08-28T08:11:19+02:002019-08-28|C#|

SVGExportAction

Es handelt sich um eine interne Action von EPLAN, welche offiziell nicht supported wird!

SVG ist ein tolles Format… Hab den Anwendungsfall dass ich SVG aus EPLAN brauche. Darum habe ich mir mal einen Helfer-Klasse geschrieben, damit der Zugriff einfacher ist:

public class SvgExportUtility
{
  public static void ExportProject(Project project, string exportPath, bool isFrameVisible = true)
  {
    if (Directory.Exists(exportPath))
    {
      Directory.Delete(exportPath);
    }

    ActionCallingContext acc = new ActionCallingContext();
    acc.AddParameter("DatabaseId", project.DatabaseIdentifier.ToString());
    acc.AddParameter("ExportPath", exportPath);
    acc.AddParameter("DrawFrame", isFrameVisible.ToString());
    acc.AddParameter("WriteGroupIds", false.ToString());
    new CommandLineInterpreter().Execute("SVGExportAction", acc);
  }

  public static void ExportPage(Page page, string fullFilename, bool isFrameVisible = true)
  {
    if (File.Exists(fullFilename))
    {
      File.Delete(fullFilename);
    }

    ActionCallingContext acc = new ActionCallingContext();
    acc.AddParameter("ExportPath", Path.GetDirectoryName(fullFilename));
    acc.AddParameter("PageObjId", page.ToStringIdentifier());
    acc.AddParameter("Filename", Path.GetFileNameWithoutExtension(fullFilename)); // only name needed
    acc.AddParameter("DrawFrame", isFrameVisible.ToString());
    acc.AddParameter("WriteGroupIds", false.ToString());
    new CommandLineInterpreter().Execute("SVGExportAction", acc);
  }

  public static void ExportPageMacro(Project project, string pageMacroFile, string fullFilename, bool isFrameVisible = true)
  {
    using (PageMacro pageMacro = new PageMacro())
    {
      // Have to insert pages into project because its not working with pageMacro.Pages.First()
      pageMacro.Open(pageMacroFile, project);

      // Set temp structure
      for (var index = 0; index < pageMacro.Pages.Length; index++)
      {
        var pageMacroPage = pageMacro.Pages[index];
        pageMacroPage.NameParts[Properties.Page.PAGE_COUNTER] = "SvgExportUtility" + index;
      }

      var storableObjects = new Insert().PageMacro(pageMacro, project, null, PageMacro.Enums.NumerationMode.None);
      var newPages = storableObjects.OfType().ToList();

      for (var index = 0; index < newPages.Count; index++)
      {
        var newPage = newPages[index];
        var path = Path.GetDirectoryName(fullFilename);
        var filename = Path.GetFileNameWithoutExtension(fullFilename) + "_" + (index + 1) + ".svg";

        // ReSharper disable once AssignNullToNotNullAttribute
        filename = Path.Combine(path, filename);

        if (File.Exists(fullFilename))
        {
          File.Delete(fullFilename);
        }

        ExportPage(newPage, filename, isFrameVisible);

        // Remove pages after export
        newPage.Remove();
      }
    }
  }

  public static void ExportMacro(string macroFile, string fullFilename, int variant, WindowMacro.Enums.RepresentationType representationType)
  {
    if (File.Exists(fullFilename))
    {
      File.Delete(fullFilename);
    }

    ActionCallingContext acc = new ActionCallingContext();
    acc.AddParameter("MacroFile", macroFile);
    acc.AddParameter("Filename1", fullFilename); // Full path needed
    acc.AddParameter("Variant1", variant.ToString());
    acc.AddParameter("RepType1", ((int)representationType).ToString());
    acc.AddParameter("WriteGroupIds", false.ToString());
    new CommandLineInterpreter().Execute("SVGExportAction", acc);
  }
}

Das exportieren von Symbol- & Fenstermakros funktioniert auch im Scripting!

Ich habe auch mal ein kleines Beispiel-Addin geschrieben um die Funktionalitäten zu testen.

By |2019-08-23T08:20:21+02:002019-08-23|EPLAN, EPLAN-API, EPLAN-Scripts|

EPLAN 2.9: Scripting Neuerungen

Folgende Actions sind laut eplan.help hinzugekommen:

XPamSelectPart

Startet die Artikelauswahl.

 

XSettingsRegisterAction

Ermöglicht das Registrieren von Add-ons.

 

XSettingsUnregisterAction

Ermöglicht das Deregistrieren von Add-ons.

 

XTranslateSourceLanguage

Ermöglicht das Übersetzen von Texten in einer bestimmten Quellsprache.

 

Größte Neuerung meiner Meinung nach im Scripting-Bereich ist, dass man nun den ActionCallingContext der Action übergeben kann. Somit ist man nicht mehr auf fixe Parameter eingeschränkt.

By |2019-08-06T08:55:13+02:002019-08-08|EPLAN, EPLAN-Scripts|