WPF button in WPF Bing Maps MapItemsControl not reacting to touch events

15 03 2012

Today I a solved problem with buttons placed on a MapItemsControl inside a WPF Bing Maps control not reacting to touch events. The full description of the problem and also the solution (custom TouchButton class with OnTouchDown, OnTouchMove and OnTouchUp overrides) can be found in the Bing Maps support forum.

Here’s the code:

 public class TouchButton : Button
  {
    protected override void OnTouchDown(TouchEventArgs e)
    {
      if (e.TouchDevice.Capture(this))
      {
        IsPressed = true;
        e.Handled = true;
      }
      base.OnTouchDown(e);
    }

    protected override void OnTouchMove(TouchEventArgs e)
    {
      if (e.TouchDevice.Captured == this)
      {
        // Update IsPressed property to indicate if the
        // current touch position is within the element's
        // visuals.
        IsPressed = IsHitTest(e.GetTouchPoint(this).Position);
        e.Handled = true;
      }
      base.OnTouchMove(e);
    }

    protected override void OnTouchUp(TouchEventArgs e)
    {
      if (e.TouchDevice.Captured == this)
      {
        ReleaseTouchCapture(e.TouchDevice);
        IsPressed = false;

        // Raise Click event if touch up occurs within
        // the element's visuals.
        if (IsHitTest(e.GetTouchPoint(this).Position))
        {
          OnClick();
        }

        e.Handled = true;
      }
      base.OnTouchUp(e);
    }

    private bool IsHitTest(Point point)
    {
      var hitTestResult = VisualTreeHelper.HitTest(this, point);
      return hitTestResult != null && hitTestResult.VisualHit != null;
    }




WPF Application Shutdown on Windows 7 x64

3 02 2012

In WPF applications, the Application.Exit event is not (reliably) called – at least on Windows 7 x64 – when the user logs off from or shuts down or restarts the Windows machine. I found that my Application_Exit handler which performs important cleanup was not called.

I tried to remedy this first by hooking into the SystemEvents.SessionEnding event and calling Application.Shutdown from there. After doing this, I found that my Application_Exit handler was called, but then the process seemed to be killed in the middle of the shutdown processing (log output stopped suddenly).

Then I removed the SystemEvents.SessionEnding event handler again and tried to override the Application.OnSessionEnding virtual method. This resulted in the same behavior. The Application_Exit handler was sometimes called, sometimes not, but when it was called, the process died during shutdown processing.

I finally solved this by overwriting the Application.OnSessionEnding method, calling the base class implementation first, then setting SessionEndingCancelEventArgs.Cancel=true to avoid my process being killed by the OS, and finally explicitly calling Application.Shutdown to initiate the shutdown process. The end result is a clean application shutdown while Windows shows the „Application not responding“ screen for several seconds while my application shuts down.

Here’s the code:

public partial class App : Application
{
  protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
  {
    base.OnSessionEnding(e);

    // Cancel application shutdown to prevent Windows 7 killing
    // the process
    e.Cancel = true;

    // Terminate myself
    Shutdown();
  }
}




Programmatically retrieving MAC Address of IP-less Network Adapter

16 03 2011

There are many developers asking how to programmatically retrieve the MAC address of a network adapter on Windows. There are also many answer showing various solutions ranging from parsing ipconfig /ALL output, through using the .NET System.Net.NetworkInformation.NetworkInterface class or using the GetIfTable API.

These solutions work fine as long as the IP protocol is bound to the network adapter in question. But they cannot retrieve the MAC address of a network adapter which is not bound to the IP protocol stack.

One solution to this problem is to use WMI and query the Win32_NetworkAdapter or (better) Win32_NetworkAdapterConfiguration objects. These do also work with IP-less network adapters. One problem with WMI is, that it’s slow. Another problem is, that the Win32_NetworkAdapter objects cannot be retrieved by a service during system startup – not even when there is a service dependency on the winmgmt service. There seems to be some deadlock situation which causes the WMI querying service to hang for approx. 2 minutes. With the Win32_NetworkAdapterConfiguration class this problem does not occur though.

Just for reference, this is how to query the WMI Win32_NetworkAdapterConfiguration objects from the command line:

wmic path Win32_NetworkAdapterConfiguration get * /format:list

So how does WMI retrieve the MAC address?

After digging around with the amazing API Monitor tool, I learned that WMI is using a DeviceIoControl API call to retrieve the MAC address. This is the required call in C:

#include <WinIoCtl.h>
#include <NtDDNdis.h>

unsigned int code = OID_802_3_CURRENT_ADDRESS;
char macAddress[6];
unsigned int bytesReturned;

BOOL res = DeviceIoControl(
  hDevice,
  IOCTL_NDIS_QUERY_GLOBAL_STATS,
  &code,
  sizeof(code),
  macAddress,
  sizeof(macAddress),
  &bytesReturned,
  NULL
  );

Doing the same thing from C# requires some P/Invoke calls. There is an article How to query miniport driver information (802.11 OIDs) using the DeviceIOControl() function on CodeProject that helped to figure out how to get the DeviceIoControl call straight.

using System;
using System.ComponentModel;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using log4net;
using Microsoft.Win32.SafeHandles;

namespace NetworkUtils
{
  /// 
  /// Helper class which returns network adapter information such
  /// as the MAC address.
  /// 
  public static class NetworkAdapterInfo
  {
    private static readonly ILog _log = LogManager.GetLogger(typeof(NetworkAdapterInfo));

    /// 
    /// Contains enum types and native Win32 functions.
    /// 
    private static class SafeNativeMethods
    {
      [Flags]
      public enum FileAccess : uint
      {
        /// 
        /// Read access
        /// 
        GenericRead = 0x80000000,

        ///// 
        ///// Write access
        ///// 
        //GenericWrite = 0x40000000,
        
        ///// 
        ///// Execute access
        ///// 
        //GenericExecute = 0x20000000,

        ///// 
        ///// Read, write, and execute access
        ///// 
        //GenericAll = 0x10000000
      }

      [Flags]
      public enum ShareMode : uint
      {
        ///// 
        ///// Prevents other processes from opening a file or device if they request delete, read, or write access.
        ///// 
        //None = 0x00000000,

        /// 
        /// Enables subsequent open operations on an object to request read access. 
        /// Otherwise, other processes cannot open the object if they request read access. 
        /// If this flag is not specified, but the object has been opened for read access, the function fails.
        /// 
        Read = 0x00000001,

        /// 
        /// Enables subsequent open operations on an object to request write access. 
        /// Otherwise, other processes cannot open the object if they request write access. 
        /// If this flag is not specified, but the object has been opened for write access, the function fails.
        /// 
        Write = 0x00000002,

        ///// 
        ///// Enables subsequent open operations on an object to request delete access. 
        ///// Otherwise, other processes cannot open the object if they request delete access.
        ///// If this flag is not specified, but the object has been opened for delete access, the function fails.
        ///// 
        //Delete = 0x00000004
      }

      public enum CreationDisposition : uint
      {
        ///// 
        ///// Creates a new file. The function fails if a specified file exists.
        ///// 
        //CreateNew = 1,

        ///// 
        ///// Creates a new file, always. 
        ///// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, 
        ///// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
        ///// 
        //CreateAlways = 2,

        /// 
        /// Opens a file. The function fails if the file does not exist. 
        /// 
        OpenExisting = 3,

        ///// 
        ///// Opens a file, always. 
        ///// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
        ///// 
        //OpenAlways = 4,

        ///// 
        ///// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
        ///// The calling process must open the file with the GENERIC_WRITE access right. 
        ///// 
        //TruncateExisting = 5
      }

      [Flags]
      public enum FileAttributes : uint
      {
        None = 0x00000000,
        //Readonly = 0x00000001,
        //Hidden = 0x00000002,
        //System = 0x00000004,
        //Directory = 0x00000010,
        //Archive = 0x00000020,
        //Device = 0x00000040,
        //Normal = 0x00000080,
        //Temporary = 0x00000100,
        //SparseFile = 0x00000200,
        //ReparsePoint = 0x00000400,
        //Compressed = 0x00000800,
        //Offline = 0x00001000,
        //NotContentIndexed = 0x00002000,
        //Encrypted = 0x00004000,
        //Write_Through = 0x80000000,
        //Overlapped = 0x40000000,
        //NoBuffering = 0x20000000,
        //RandomAccess = 0x10000000,
        //SequentialScan = 0x08000000,
        //DeleteOnClose = 0x04000000,
        //BackupSemantics = 0x02000000,
        //PosixSemantics = 0x01000000,
        //OpenReparsePoint = 0x00200000,
        //OpenNoRecall = 0x00100000,
        //FirstPipeInstance = 0x00080000
      }

      [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
      internal static extern SafeFileHandle CreateFile(
            string lpFileName,
            FileAccess dwDesiredAccess,
            ShareMode dwShareMode,
            IntPtr SecurityAttributes,
            CreationDisposition dwCreationDisposition,
            FileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile);

      [Flags]
      private enum DeviceTypes : uint
      {
        PhysicalNetcard = 0x00000017,
      }

      [Flags]
      private enum Methods : uint
      {
        //Buffered = 0,
        //InDirect = 1,
        OutDirect = 2,
        //Neither = 3
      }

      public enum NdisOids : uint
      {
        //OID_802_3_PERMANENT_ADDRESS = 0x01010101,
        OID_802_3_CURRENT_ADDRESS = 0x01010102,
        //OID_802_3_MULTICAST_LIST = 0x01010103,
        //OID_802_3_MAXIMUM_LIST_SIZE = 0x01010104,
      }

      [Flags]
      public enum IOControlCode : uint
      {
        // Physical Netcard
        IOCTL_NDIS_QUERY_GLOBAL_STATS = (DeviceTypes.PhysicalNetcard << 16) | (0x0000 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_QUERY_ALL_STATS = (DeviceTypes.PhysicalNetcard << 16) | (0x0001 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_DO_PNP_OPERATION = (DeviceTypes.PhysicalNetcard << 16) | (0x0002 << 2) | Methods.Buffered,
        //IOCTL_NDIS_QUERY_SELECTED_STATS = (DeviceTypes.PhysicalNetcard << 16) | (0x0003 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_ENUMERATE_INTERFACES = (DeviceTypes.PhysicalNetcard << 16) | (0x0004 << 2) | Methods.Buffered,
        //IOCTL_NDIS_ADD_TDI_DEVICE = (DeviceTypes.PhysicalNetcard << 16) | (0x0005 << 2) | Methods.Buffered,
        //IOCTL_NDIS_GET_LOG_DATA = (DeviceTypes.PhysicalNetcard << 16) | (0x0007 << 2) | Methods.OutDirect,
        //IOCTL_NDIS_GET_VERSION = (DeviceTypes.PhysicalNetcard << 16) | (0x0008 << 2) | Methods.Buffered,
      };

      [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
      public static extern bool DeviceIoControl(
          SafeFileHandle hDevice,
          [MarshalAs(UnmanagedType.U4)] IOControlCode dwIoControlCode,
          IntPtr lpInBuffer,
          [MarshalAs(UnmanagedType.U4)]uint nInBufferSize,
          IntPtr lpOutBuffer,
          [MarshalAs(UnmanagedType.U4)] uint nOutputBufferSize,
          [MarshalAs(UnmanagedType.U4)] out uint pBytesReturned,
          IntPtr pOverlapped);
    }

    /// 
    /// Gets the MAC address of the specified network adapter instance.
    /// 
    /// The network adapter guid.
    /// The network adapter MAC address.
    /// 
    /// 
    /// This implementation uses low level system calls. Though there are
    /// many other options available (like e. g. )
    /// these do only work with network adapters which are bound to the IP stack.
    /// The implementation provided here also works with IP less network adapters.
    /// It uses a DeviceIoControl system call to the network adapter's driver instance.
    /// 
    /// 
    /// An alternate solution could use WMI and query the Win32_NetworkAdapterConfiguration
    /// object, but this is slow and introduces a dependency on the WMI infrastructure.
    /// This in turn causes delays during system startup.
    /// 
    /// 
    public static PhysicalAddress GetMacAddress(string guid)
    {
      _log.DebugFormat("Retrieving MAC address for network adapter: {0}", guid);
      SafeFileHandle hFile = null;
      try
      {
        // Open device driver
        var fileName = string.Format(@"\\.\{0}", guid);
        _log.DebugFormat("Opening network adapter driver: {0}", fileName);
        hFile = SafeNativeMethods.CreateFile(
          fileName,
          SafeNativeMethods.FileAccess.GenericRead,
          SafeNativeMethods.ShareMode.Read | SafeNativeMethods.ShareMode.Write,
          IntPtr.Zero,
          SafeNativeMethods.CreationDisposition.OpenExisting,
          SafeNativeMethods.FileAttributes.None,
          IntPtr.Zero);
        if (hFile.IsInvalid)
        {
          var errorCode = Marshal.GetLastWin32Error();
          var msg = string.Format("CreateFile('{0}') failed with error code {1}", fileName, errorCode);
          _log.Error(msg);
          throw new Win32Exception(errorCode, msg);
        }

        // Execute DeviceIoControl
        _log.DebugFormat("Calling DeviceIoControl on network adapter driver: {0}", fileName);
        var handle = GCHandle.Alloc((uint)SafeNativeMethods.NdisOids.OID_802_3_CURRENT_ADDRESS, GCHandleType.Pinned);
        var ptrInput = handle.AddrOfPinnedObject();
        var address = new byte[6];
        var ptrOutput = Marshal.AllocHGlobal(address.Length);
        uint bytesReturned;
        var res = SafeNativeMethods.DeviceIoControl(
          hFile,
          SafeNativeMethods.IOControlCode.IOCTL_NDIS_QUERY_GLOBAL_STATS,
          ptrInput,
          (uint)Marshal.SizeOf(typeof(uint)),
          ptrOutput,
          (uint)address.Length,
          out bytesReturned,
          IntPtr.Zero);
        handle.Free();
        if (!res)
        {
          Marshal.FreeHGlobal(ptrOutput);
          var errorCode = Marshal.GetLastWin32Error();
          var msg = string.Format("DeviceIoControl failed with error code {0}", errorCode);
          _log.Error(msg);
          throw new Win32Exception(errorCode, msg);
        }
        
        // Validate length of returned value
        if (bytesReturned != address.Length)
        {
          Marshal.FreeHGlobal(ptrOutput);
          var msg = string.Format("DeviceIoControl() returned unexpected size: {0} (expected was: {1}", bytesReturned, address.Length);
          _log.Error(msg);
          throw new InvalidOperationException(msg);
        }

        Marshal.Copy(ptrOutput, address, 0, address.Length);
        Marshal.FreeHGlobal(ptrOutput);

        var macAddress = new PhysicalAddress(address);
        _log.InfoFormat("Retrieved MAC address {0} for network adapter: {1}", macAddress, guid);
        return macAddress;
      }
      catch (Exception ex)
      {
        var msg = string.Format("Failed to retrieve MAC address for network adapter: {0}", guid);
        _log.Error(msg, ex);
        throw;
      }
      finally
      {
        if (hFile != null)
        {
          hFile.Close();
        }
      }
    }
  }
}




ASP.NET Compilerfehlermeldung CS0016

27 02 2010

Heute hatte ich folgende Fehlermeldung beim Zugriff auf eine alte ASP.NET 1.1 Anwendung aus dem Jahre 2004 auf einem Windows Server 2003 SP2.

Compilerfehlermeldung: CS0016: In die Ausgabedatei ‚c:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files\adward_common_clientstatistics\xxxxxxxx\xxxxxxxx\xxxxxxxx.dll‘ konnte nicht geschrieben werden — ‚Der Verzeichnisname ist ungültig. ‚

Quellfehler:
[Keine relevanten Quellzeilen]

Quelldatei:    Zeile: 0

Der selbe Fehler ist bereits in einem Blogeintrag vom 18.08.2006 von Thomas Kötzing beschrieben. Der dort als Lösung angegebene Microsoft KB-Eintrag http://support.microsoft.com/default.aspx?scid=kb;en-us;825791 konnte das Problem auch in meinem Fall beheben.

Es fehlten die Zugriffsrechte für den “Netzwerkdienst” für den Ordner C:\Temp. Auf diesen Ordner zeigen die System-Umgebungsvariablen TEMP und TMP.

Warum das Problem plötzlich aufgetreten ist, nachdem die Anwendung seit über 6 Jahren problemlos lief, wird wohl ein Rätsel bleiben…





.NET Framework Source Code unter Windows 7 RC

13 05 2009

Ich habe gerade versucht, unter Windows 7 RC (Build 7100) mit VS2008 SP1 in den .NET Framework Source Code zu debugging. Ich habe dazu alles eingerichtet wie in Shawn Burke’s Blog beschrieben. Auch den Visual Studio 2008 QFE KB944899 habe ich installiert. Die Symbole (.pdb Dateien) wurden auch herunter geladen, allerdings fehlte der Quellcode.

Anhand der Datei PESymbolList.xml konnte ich feststellen, dass unter Windows 7 RC eine neuere Version des .NET Frameworks installiert ist, z. B. System.dll, Version 2.0.50727.4918 (NetFXspW7.050727-4900). Für diese Version steht offensichtlich (noch) keine Quellcode zur Verfügung.





.NET Remoting TcpChannel, multi-homed Server

8 05 2009

In einem aktuellen Projekt bestand das Problem, dass wir einen .NET Remoting Server implementiert hatten, der den TcpChannel als Transport verwendet. Das Problem entstand dadurch, dass der Server mit mehreren Netzwerkadaptern (LAN und WLAN) ausgestattet war und die IP-Adresse des WLAN-Adapters sich dynamisch ändert weil der Server auf einem mobilen Gerät läuft, das herum getragen wird („roaming users“) und sich mit verschiedenen WLAN-(Sub)netzen verbindet. All das wäre noch kein Problem wenn es im Netzwerk des Kunden einen DNS-Server gäbe. In diesem Fall könnte man den TcpChannel so konfigurieren, dass er den Rechnernamen des mobilen Servers für die Bindung der Proxies verwendet. Da aber kein DNS zur Verfügung steht, mussten wir auf die Option useIpAddress=“true“ ausweichen. Dadurch entstand eine Reihe von Problemen:

  1. Wenn beim Start des Systems (Windows XP Embedded) kein Netzwerkkabel angesteckt ist und kein WLAN verfügbar ist, wird der Maschine keine IP-Adresse zugewiesen. Eine Kommunikation mit dem Gerät über den .NET Remoting TcpChannel ist dann auch nach dem Herstellen einer Netzwerkverbindung nicht möglich (der .NET Remoting Server läuft als Win32 Service und wird beim Systemstart automatisch gestartet).
  2. Wenn sich die IP-Adresse des WLAN-Adapters zur Laufzeit des .NET Remoting Service ändert, dann war keine Kommunikation mit dem .NET Remoting Service möglich. Selbst dann nicht, wenn der Client neu gestartet wurde. Der Client kennt zwar die neue IP-Adresse und kann den Server anpingen und sogar eine TCP Verbindung zum .NET Remoting Interface herstellen, eine Kommunikation über das .NET Remoting Protokoll war jedoch nicht möglich.

Ursächlich hierfür ist die Implementierung des TcpChannels im .NET Framework (2.0 SP1, neuere Versionen sind aber auch nicht besser). Bereits beim Registrieren des TcpChannels (z. B. via RemotingConfiguration.Configure) werden die IP-Adressen des Hosts ermittelt und der TcpChannel wird an die erstbeste(!) IP-Adresse (und zwar nur an diese!) gebunden. Diese Bindung bleibt bis zum Ende des Prozesses bestehen. Sie passt sich nicht dynamisch an. Ein nachträgliches Umkonfigurieren ist nicht möglich da die Klasse TcpChannel hierfür keine Properties/Methoden zur Verfügung stellt. Selbst ein explizites Deregistrieren (ChannelServices.UnregisterChannel) mit anschließender Neuinstanziierung und Neuregistrierung (ChannelServices.RegisterChannel) des TcpChannels ist nicht möglich weil das .NET Framework die Registrierung eines TcpChannels nur ein einziges Mal zulässt (warum?).

Zur Lösung des Problems haben wir eine eigene Implementierung des TcpChannels erstellt. Als Vorlage diente der Sourcecode von mono 2.4. Damit war es möglich, den TcpChannel zu de- und anschließend wieder zu registrieren. Im Gegensatz zum Standard-TcpChannel aus dem .NET Framework liefert unser TcpChannel nicht nur eine einzige URI für ein Objekt, sondern eine Liste von URIs, die einen Eintrag für jede dem System zugewiesene IP-Adresse (gefiltert auf IPv4) liefert. Parallel dazu haben wir einen „TcpAddressMonitor“ implementiert, der die dem System aktuell zugewiesenen IP-Adressen überwacht (mittels Polling im 5 s Takt) und bei einer Änderung den TcpChannel deregistriert, eine neue Instanz erstellt und diese wieder registriert. Damit waren sowohl das Problem mit nicht vorhandenen IP-Adressen beim Systemstart als auch das Roaming-Problem gelöst.

Allerdings wurden nun Exceptions, die beim Aufruf von Server-Methoden auftraten, nicht mehr an den Client durchgereicht. Die Option RemotingConfiguration.CustomErrorsMode=CustomErrorsMode.Off wurde ignoriert. Clientseitig kam lediglich eine RemotingException mit der Message „Server encountered an internal error“ an. Die Lösung für dieses Problem fand sich hier: Es muss im TcpChannel der RequestHeader __CustomErrorsEnabled=false gesetzt werden. Auf diese Lösung bin ich nur dadurch gekommen, dass ich mir den gesamten verfügbaren .NET Sourcecode über den NetMassDownloader heruntergeladen und nach „customErrors“ durchsucht habe.

Update, 13.05.2009: So ganz hat der oben beschriebene Weg doch nicht funktioniert. Wie sich gezeigt hat, gibt es Probleme mit bereits gemarshalten Objekt-Referenzen (ObjRef) wenn der TcpChannel neu registriert wird. Die Referenzen werden dann ungültig. Da hilft nicht einmal ein Neustart der Clients. Anhand eines Analyse des Quellcodes habe ich folgendes gelernt:

1. Jeder Channel enthält eine Liste von URIs (beim TcpChannel wird die Liste von der internen Klasse TcpServerChannel verwaltet).

2. Jede Objekt-Referenz enthält eine Liste von URIs (z. B. „tcp://hostname:port/xxxxxx.rem“).

Wird eine Referenz zum ersten Mal gemarshalt, wird ein ObjRef Objekt erzeugt. Dabei wird die Liste aller URIs aller in .NET Remoting registrierten Channels in das ObjRef Objekt kopiert.

Um unser Problem der sich ändernden IP-Adressen zu lösen, war nun folgendes nötig sobald sich die IP-Adressliste des Hosts geändert hat:

1. Die URI-Liste jedes TcpChannels muss aktualisiert werden.

2. Die URI-Liste sämtlicher Objekt-Referenzen muss aktualisiert werden.

Das ließ sich folgendermaßen implementieren:

1. Die Liste aller registrierten TcpChannels erhält man über ChannelServices.RegisteredChannels. Mit Hilfe von „… is TcpChannel“ wird überprüft, ob es sich um einen Channel vom Typ TcpChannel handelt. Um den TcpChannel aktualisieren zu können, wurde eine public Methode UpdateChannelData() hinzugefügt. Diese delegiert einfach an eine ebenfalls neue Methode UpdateChannelData() des internen TcpServerChannels. Damit waren die URIs des TcpChannels aktualisiert, was allerdings nur Auswirkungen auf zukünftige gemarshalte Objekt-Referenzen hat.

2. Die bereits gemarshalten Objekt-Referenzen können aktualisiert werden, indem die Schnittstelle ITrackingHandler von einer Klasse TrackingHandler implementiert wurde, und eine entsprechede Instanz beim TrackingService registriert wurde. ITrackingHandler wird benachrichtigt, wann immer eine Objekt-Referenz gemarshalt, unmarshalt oder disconnected wird. Beim Marshaling vermerkt unsere Klasse die erzeugte Objekt-Referenz in einer Liste, beim Disconnect wird die Objekt-Referenz aus der Liste entfernt. Anhand dieser Liste lässt sich leicht über alle bereits gemarshalten Objekt-Referenzen iterieren und deren URIs aktualisieren.





Buffer.BlockCopy

6 04 2009

Nette .NET Funktion um als Array organisierte Daten hochperformant als binären Block zu kopieren. Kann u. a. dazu verwendet werden, um ein byte[] in ein ushort[] Array zu konvertieren.