Archive for the ‘.NET’ Category

Einschränkungen beim Debuggen mit mixed moded Anwendungen im 64-Bit Modus

Sunday, June 27th, 2010

Hallo,

kürzlich bekam ich vom Visual Studio 2010 Debugger beim Starten einer Anwendung folgende Fehlermeldung:

—————————
Microsoft Visual Studio
—————————
Fehler beim Ausführen des Projekts: Das Programm “…\unittest\bin\Debug\unittest.exe” kann nicht gestartet werden.

Das Debuggen im gemischten Modus für x64-Prozesse wird nur bei Verwendung von Microsoft .NET Framework vor Version 4 unterstützt.
—————————
OK   Hilfe
————————–

Die Konstellation war dabei folgende:

  • Ein reines C#-Programm referenziert eine Assembly, die mit managed C++ erstellt war. Diese Assembly bindet auch native code ein (mixed mode).
  • Als Compiler wurde der vc90 verwendet.
  • Als Targetversion für das .NET-Framework wurde für alle Komponenten 2.0 verwendet.
  • Alle Anwendungen waren als 32-Bit-Anwendungen in Visual Studio 2008 erstellt und wurden von Visual Studio 2010 zur Version 2010 konvertiert.

Für die in C# erstellte (reine .NET) Anwendung war in den Einstellungen als Zielplattform “Any CPU” eingetragen, wie bereits in der Vorgängerversion:

AnyCPU

Nach der Umstellung auf X86 konnte die Anwendung erfolgreich im Debugger gestartet werden.

X86

Die Fehlermeldung ist grundsätzlich ziemlich irreführend. Alle beteiligten Komponenten waren ja explizit für .NET 2.0 konfiguriert.

Oder ist sie gar falsch?

Auf dieser Seite findet man folgendes Zitat:

Debugging im gemischten Modus (Aufrufe von systemeigenem Code bis zu verwalteten Codes, oder umgekehrt) wird für x64-Prozesse unterstützt, wenn der verwaltete Code Microsoft .NET Framework, Version 4 oder höher, verwendet.

Debugging im gemischten Modus wird nicht für IA64-Prozesse oder x64-Prozesse unterstützt, die frühere .NET Framework-Versionen als 4 verwenden.

was ja ein klarer Gegensatz zur oben zitieren Fehlermeldung ist.

Grundsätzlich ist es schon etwas unschön, wenn man für die reine .NET-Komponent nicht mehr “Any CPU” wählen kann, nur weil sie eine mixed mode Assembly nutzt, die im 32-Bit Modus erstellt wurde.

Eine Rolle könnte auch spielen, dass ich inzwischen Windows 7 x64 benutze, während ich zuvor Windows XP X86 verwendet habe.

Viele Grüße,
Andreas

Technorati Tags: , ,


DoDragDrop und SelectedNode in WinForms TreeViews

Monday, November 30th, 2009

Hallo,

kürzlich fiel mir ein überraschender Effekt im Zusammenhang mit WinForms TreeViews und drag and drop auf. Fall man im MouseDown-Event eine DragDrop-Operation mit der Methode DoDragDrop() startet, wird der selektierte Knoten nicht auf den angeklickten Knoten gesetzt.

In folgendem Beispiel wurde der Knoten “BasicActivities” angeklickt und damit zum selected node. Anschließend wurde der Knoten ActivityFinalNode angeklickt. Der Focus-ähnliche Rahmen um den Knoten “BasicActivities” blieb aber erhalten:

SelektierterKnotenFalsch

Das hat zur Konsequenz, dass der zuvor selektierte Knoten “BasicActivities” nach einer anschließenden Mousebewegung ohne click wieder selektiert wird.

Dieses Verhalten tritt nicht auf, wenn der Aufruf der Methode DoDragDrop() unterbleibt.

Wenn man den Knoten im MouseDown-Event explizit selektiert (wie hier beschrieben), tritt dieser Effekt nicht auf und das TreeView verhält sich wie gewohnt.

theTreeView.SelectedNode = nodeAt;
DoDragDrop(nodeAt.TheClass, DragDropEffects.Copy);

Viele Grüße,
Andreas

Technorati Tags: ,

Wie kann man native Methoden in managed C++ Klassen nutzen?

Sunday, October 4th, 2009

Hallo,

Microsoft hat mit der Einführung des .NET-Frameworks auch C++-Erweiterungen (C++/CLI) definiert, mit denen vorhandener C++ source code für .NET-Anwendungen verfügbar gemacht werden kann.

Die Intention dabei war die Steigerung der Akzeptanz des .NET-Frameworks, da es relativ einfach gemacht wird, bereits vorhandenen C++ code in .NET-Anwendungen zu integrieren. Meiner persönlichen Erfahrung nach geht das sehr gut, d.h. es ist ziemlich einfach vorhanden C++ code mit C++/CLI .NET-tauglich zu machen und die entstandenen Assemblys haben keine nennenswerten Einschränkungen (abgesehen davon, dass sie nur für die Plattform geeignet sind, für die der native code kompiliert wurde).

Der übliche Weg besteht darin eine neue Assembly zu erstellen, die C++/CLI Klassen (und ggf. native Klassen) enthält. Diese Klassen rufen dann den eigentlichen legacy code, typischerweise in bereits vorhandenen Libraries/DLLs.

Folgende Frage stellt sich bei der Erstellung eines Managed C++ Wrappers um vorhanden C++ code schnell:

Wie kann ich innerhalb des .NET-Wrappers native Datenstrukturen zwischen managed Klassen austauschen?

Dies ist grundsätzlich nicht ohne weiteres möglich, da in der öffentlichen Schnittstelle nur CLR-kompatible Datentypen verwendet werden dürfen (ansonsten könnten nicht alle Elemente der Schnittstelle von anderen .NET kompatiblen Komponenten konsumiert werden).

Es ist allerdings möglich, private Methoden zu definieren, die nicht CLR-konforme Datentypen konsumieren oder zurückgeben dürfen. Damit solche Methoden von anderen Klassen in der C++/CLI Wrapper-Assembly gerufen werden dürfen, müsste man alle diese Klassen als friend definieren, was eine etwas unbefriedigende Lösung ist.

Diese Überlegung führt aber zur optimalen Lösung: die Deklaration der entsprechenden Methoden als public private. public private ist in C++/CLI die Entsprechung zu internal in C# und bedeutet “sichtbar innerhalb der Assembly”.
Da die entsprechenden Methoden nur von den C++-Klassen innerhalb der Wrapper-Assembly gerufen werden, gibt es keinen Grund sie zu verbieten (was der Compiler dann auch nicht macht).

Viele Grüße,
Andreas

Technorati Tags: ,

Prüfen auf Schreibrechte für eine Datei in C#

Saturday, February 28th, 2009

Hallo,

manchmal möchte man wissen, ob ein bestimmter Benutzer bestimmte Rechte (z.B. Schreibrechte) für eine bestimmte Datei besitzt. Wie sich herausstellt, ist diese Anforderung alles andere als leicht umzusetzen, weil dabei viele verschiedene Aspekte eine Rolle spielen. Die Vorgehensweise sieht prinzipiell so aus:

  • Abfragen der Access Control Listen für die Datei
  • Itererieren über alle Einträge und prüfen ob man für die Kennung oder die Gruppe des gewünschten Benutzers Einträge findet
  • Dabei die Präzedenz der Einträge beachten (explizite kommen vor vererbten Regeln, “Deny”-Regeln kommen for “Allow”-Regeln)

Den nicht unerheblichen Code hat dankenswerterweise Bruce Hatt in einem Artikel auf CodeProject.com veröffentlicht.

Man sollte bedenken, dass die Möglichkeit in eine Datei zu schreiben zusätzlich durch das “ReadOnly-flag” und die .NET Code Access Security eingeschränkt sein könnte. Beides wird in dieser Klasse nicht berücksichtigt.

Die Prüfung auf das ReadOnly-flag könnte man so schreiben:

FileAttributes attrs = File.GetAttributes(fileName);
 
if ((attrs & FileAttributes.ReadOnly) != 0)
{
    readOnly = true;
}

Und für die Prüfung im Rahmen der CAS könnte man folgenden Code verwenden:

FileIOPermission fp = new FileIOPermission(FileIOPermissionAccess.Write, fileName);
try
{
    fp.Demand();
}
 
catch (SecurityException)
{
    readOnly = true;
}

Wenn es nur um die Prüfung auf Schreibrechte geht, hört man oft den Vorschlag, einfach den Schreibvorgang auszuführen und ggf. die geworfene Exception zu fangen. Dieser Vorschlag ist aber nicht immer geeignet. In meinem Fall war es so, dass ich an einer Anwendung arbeite, in der Dateien editiert werden können. Falls Dateien nicht geschrieben werden können, soll dies visualisiert werden. Man möchte aber vermeiden, dazu bei jedem Start der Anwendung die zu editierenden Texte zu speichern, da damit der Timestamp verändert wäre, obwohl der Benutzer die Datei gar nicht geändert hat und auch nicht speichern wollte.

Viele Grüße,
Andreas

Technorati Tags: ,

.NET Assembly-Ladevorgang loggen

Monday, January 26th, 2009

Hallo,

kürzlich hatte ich folgende Situation:

In einer C#-Solution wurde eine in Managed C++ erstellte .NET Assembly referenziert. Sobald die C#-Anwendung eine Klasse aus der referenzierten Library benutzen wollte, gab es eine Exception der Form:


System.IO.FileNotFoundException: Die Datei oder Assembly "etocl, Version=1.0.3306.20332, Culture=de, PublicKeyToken=null" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.
Dateiname: "etocl, Version=1.0.3306.20332, Culture=de, PublicKeyToken=null"

Die referenzierte Assembly fand sich aber wie es sein sollte im Arbeitsverzeichnis des C#-Programms. Um ein Protokoll des Ladevorgangs zu bekommen kann man nun folgenden Registry-Eintrag anlegen:


[HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) = 1

Danach enthält die Fehlermeldung eine Aufstellung der Pfade, die der Loader durchsucht hat:


LOG: Download von neuem URL file:///K:/Develop/et/etroot/src/apps/etoclide/bin/Debug/de/etocl.DLL.
LOG: Download von neuem URL file:///K:/Develop/et/etroot/src/apps/etoclide/bin/Debug/de/etocl/etocl.DLL.
LOG: Download von neuem URL file:///K:/Develop/et/etroot/src/apps/etoclide/bin/Debug/de/etocl.EXE.
LOG: Download von neuem URL file:///K:/Develop/et/etroot/src/apps/etoclide/bin/Debug/de/etocl/etocl.EXE.

und das Problem wird schnell sichtbar: der loader sucht im Unterverzeichnis “de”. Mit diesem Hinweis ausgerüstet sieht man schnell, dass das AssemblyInfo.cpp-file der referenzierten Library einen expliziten Eintrag für das locale enthielt:


[assembly:AssemblyCultureAttribute("de")]

Nach Entfernung dieses Eintrags und erneuter Erstellung der library  funktionierte dann auch alles wieder.

Fairerweise muss ich sagen, dass bereits die ursprüngliche Meldung einen Hinweis auf das Problem enthielt, allerdings nicht so deutlich:


System.IO.FileNotFoundException: Die Datei oder Assembly "etocl, Version=1.0.3306.20332, Culture=de, PublicKeyToken=null" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.
Dateiname: "etocl, Version=1.0.3306.20332, Culture=de, PublicKeyToken=null"

Microsoft empfiehlt, den Registry-Eintrag wieder zu löschen da er zu einer Performance-Einbusse führen könnte.

Viele Grüße,
Andreas

Technorati Tags: ,

ClickOnce-Installation und der Fehler “Datei…wurde seit ihrer ersten Veröffentlichung geändert” Fehler

Monday, October 20th, 2008

Hallo,

bei einer ClickOnce-Installation einer Microsoft .NET Software kann bei der Erstellung des setup-Programmes in Visual Studio eine Option gewählt werden, die bewirkt dass vorausgesetzte Software (”redistributable prerequisites” – wie etwas das .NET framework selbst) automatisch als Teil der Installation geladen und installiert werden.

Dabei kann angegeben werden, ob die Komponenten von der Website des Hersteller oder von einer anderen Quelle geladen werden sollen.

Erforderliche Komponenten für ClickOnce Installation Dialog in Visual Studio

Im ersteren Fall kann folgendes Fehlerszenario entstehen:

Der Entwickler hat eines der vorausgesetzten Softwarepakete (z.B. das deutsche language pack vstor_lp_de_30.exe für die VSTO 3.0 runtime) lokal installiert. Beim Erstellen des setup-Programmes wird dieses Paket nicht integriert, wohl aber Information über die verwendete Version.

Nun wird die Installation auf einem beliebigen anderen Rechner gestartet. Der installer sieht dass auf der Website des Komponentenherstellers (also Microsoft für obiges Beispiel) eine andere Version der Komponente verfügbar ist. Genau dann wird diese Fehlermeldung generiert:

Error: Die Datei “C:\DOKUME~1\awa\LOKALE~1\Temp\VSD6.tmp\VSTOR30\vstor_lp_de_30.exe” wurde seit ihrer ersten Veröffentlichung geändert.

Die lokal auf dem Entwicklungsrechner mit Visual Studio 2008 SP1 installierte Version war vom 27.12.2007 während die Version auf der Microsoft Website vom 12.08.2008 war.

Dieses Verhalten ist ziemlich problematisch, kann doch ein update auf der Website des Komponentenherstellers grundsätzlich zu einem beliebigen Zeitpunkt erfolgen. Danach müssten alle ClickOnce-setups, die die aktualisierte Komponente benötigen, aktualisiert werden.

Es wäre sinnvoll, zumindest kleinere Abhängigkeiten direkt in das setup zu integrieren. Leider kann das Verhalten nicht für einzelne Abhängigkeiten individuell eingestellt werden, so dass in diesem Fall auch die .NET runtime in das setup integriert werden müsste, was nicht wirklich attraktiv ist.

Falls prerequisites ausgeschlossen werden bricht die Installation ohne konkrete Fehlermeldung ab, wenn die prerquisites nicht vorhanden sind bzw. nicht in der richtigen Version vorhanden sind.

Insgesamt würde ich empfehlen ausser den .NET runtimes alle erforderlichen prerequistes dem setup mit der Option “Erforderliche Komponenten vom demselben Speicherort wie die Anwendung herunterladen” hinzuzufügen. Die Installationsanleitung muss dann einen deutlichen Hinweis auf die erforderliche .NET runtime bzw. weitere nicht eingeschlossene prerequistes beinhalten.

Vielen Dank an Joe Wirtley für den Hinweis auf die root cause.

Viele Grüße,
Andreas

Technorati Tags: ,

Erstellung eines AddIns für Word 2007 – Teil 5 (Finden der OCL-Fragmente)

Saturday, September 27th, 2008

Hallo,

wie erwartet war dieser Teil einfach zu lösen, da es im Internet genügend Beispiele gibt, wie man mit dem Word-Objektmodell arbeitet. Die OCL-Fragmente werden anhand ihrer Vorlage identifiziert. Die Vorlage muss “OCL” heißen, kann ansonsten aber beliebig gestaltet werden. Die Verwendung eines fonst mit fester Zeichenbreite (”fixed width”) empfiehlt sich.

Ein wenig mehr Zeit als nötig habe ich benötigt, weil mir anfangs nicht klar war, dass der englische Begriff für “Formatvorlage” “Style” ist und nicht “Format”.

Bei der Umsetzung merkt man, dass die Kompatibilität zu Visual Basic for Applications (VBA) kein Designziel für C# war. Ein komfortabler Umgang mit den Office-Objektmodellen setzt einige features voraus, die Visual Basic.NET hat, C# aber nicht (z.B. default parameter). Deshalb sollte man grössere Anbindungen von Office-Objektmodellen in eigenen Visual Basic.NET assemblies erstellen. Für unser kleines Word-AddIn spielt das aber keine Rolle, da wir nur minimal auf die Word-Objekte zugreifen.

Um die OCL-Fragement zu finden, wird das Word “Find”-Objekt benutzt:

IList<TextFragment> FindFragmentsByStyle(Word.Document doc, Object styleName)
{
            List<TextFragment> result = new List<TextFragment>();
 
            Object start = 0;
            Object end = doc.Characters.Count;
            Word.Range range = doc.Range(ref start, ref end);
            Word.Find find = range.Find;
 
            Object style = null;
 
            try
            {
                style = doc.Styles.get_Item(ref styleName);
            }
 
            catch (Exception)
            {
                /*
                 * Style not found.
                 */
                return (result);
            }
 
            find.set_Style(ref style);
            ExecuteFind(find);
            while (find.Found)
            {
                string text = range.Text;
                int linenum = GetRelativeLineNum(doc, range);
                int pagenum =     (int)range.get_Information(Microsoft.Office.Interop.Word.WdInformation.wdActiveEndPageNumber);
 
                result.Add(new TextFragment(text, linenum, pagenum));
                ExecuteFind(find);
            }
 
            return (result);
}

Die Methode ExecuteFind() enthält einige low level Details beim Suchen nach dem nächsten Treffer:

private bool ExecuteFind(Word.Find find, Object wrapFind, Object forwardFind)
{
            // Simple wrapper around Find.Execute:
            Object forward = forwardFind;
            Object wrap = wrapFind;
 
            return find.Execute(ref missing, ref missing, ref missing,
              ref missing, ref missing, ref missing,
              ref forward, ref wrap, ref missing, ref missing, ref missing,
              ref missing, ref missing, ref missing,
              ref missing);
}

Als Ergebnis gibt die Methode FindFragmentsByStyle() eine Liste von Objekten der (selbst erstellen) Klasse TextFragment zurück. Diese Klasse speichert den eigentlichen Text und Informationen zur Position des Textes im Dokument (Seiten- und Zeilennummer).

Viele Grüße,
Andreas

Technorati Tags: , ,

Nette Silverlight Demo

Thursday, September 4th, 2008

Hallo,

Microsofts Silverlight stellt eine Plattform dar, mit der man “Rich Internet Applications” (RIAs) mit Schwerpunkt auf grafische Darstellung erstellen kann.

Wer sich für Silverlight interessiert findet unter http://foidoschess.tv/ eine nette Demonstration der Möglichkeiten von Silverlight. Dummerweise ist eine Registrierung erforderlich, ich habe hierfür eine Email-Adresse @mailinator.com verwendet.

Ausserdem muss man noch die Silverlight Runtime installieren, was aber schnell und unkompliziert geht.

Da ich öfter das Internet nutze um Schachpartien live zu verfolgen (z.B. soweit möglich bei dem im Oktober anstehenden Weltmeisterschaftskampf) kann ich den Mehrwert der Foidos-Plattform durchaus erkennen.

Viele Grüße,
Andreas

Technorati Tags: ,

Erstellung eines AddIns für Word 2007 – Teil 4 (Erweiterung des Ribbons)

Wednesday, September 3rd, 2008

Hallo,

in diesem posting beschreibe ich, wie man dem Word Ribbon eine eigene RibbonGroup hinzufügt, mit dem man die AddIn-spezifische Funktionalität aufrufen kann.

Dazu muss man dem Projekt ein neues Objekt vom Typ “Multifunktionsleiste (Visueller Designer)” hinzufügen:

Add Ribbon 1

oclwordaddin_addribbon2.png

Der visuelle Designer hat gewisse funktionale Einschränkungen gegenüber der Erstellung einer XML-Datei zur Ribbon-Erweiterung aber für unsere Zwecke ist er völlig ausreichend und wir nutzen gerne den erheblichen Produktivitätsvorteil.

Der Designer zeigt uns nun das neue RibbonTab an. Wir setzen noch sein label ( “OCL” – der Anhang “(Integriert)” wird von Visual Studio hinzugefügt und ist nur im design mode sichtbar) und das label der automatisch erstellten RibbonGroup (”Check”):

Ribbon editieren

Projekt erstellen, Starten, und tatsächlich, unser RibbonTab wird angezeigt:

Leeres OCL Ribbon in Word

Jetzt wollen wir die buttons zum Laden einer XMI-Datei und zum anstoßen der Prüfung aller im Dokument enthaltenen OCL-Ausdrücke hinzufügen. VSTO stellt in der Toolbox den eigenen Abschnitt “Steuerelemente für Office-Multifunktionsleiste” bereit, aus dem wir uns bedienen können.

Wie gewohnt lassen sich die buttons mittels drag and drop dem ribbon hinzufügen, konfigurieren und mit einem Handler für das ‘Click’-Ereignis versehen.

An diesem Punkt stellt sich die erste Design-Frage. Die Eventhandler in der von Visual Studio erzeugten OfficeRibbon-Klasse werden zwar gerufen, aber die dabei implizit erzeugte Instanz hat keine Verbindung zu der ebenfalls impliziten Instanz der eigentlichen AddIn-Klasse “ThisAddin”. Es stellt sich also die Frage, wer die diversen events und Informationen orchestriert und wie events und Informationen zwischen den verschiedenen Instanzen ausgetauscht werden.

Hier gibt es sicherlich verschiedenste Wege, hier der den ich gewählt habe:

  • Die Orchestrierung soll durch die Klasse ThisAddIn erfolgen. Diese Klasse hat bereits Zugriff auf das Word-Objektmodell und bietet sich auch als zentrale Steuerklasse an.
  • Um Informationen von der OfficeRibbon-Instanz zur This AddIn-Instanz zu transportieren, werden der OfficeRibbon-Klasse mehrere events hinzugefügt. Weiterhin wird eine statische Property angelegt, die Zugriff auf die Instanz ermöglicht. Damit kann die ThisAddIn-Klasse in ihrer Initialisierung die nötigen eventhandler anmelden.
  • Diese Vorgehen hat den Nachteil, dass man sich von der zeitlichen Reihenfolge der Konstruktion und Initialisierung abhängig macht. Konkret geht man davon aus, dass im ThisAddIn_Startup()-Ereignis die OfficeRibbon-Instanz bereits erzeugt wurde. Es scheint mir legitim, anzunehmen dass alle Objekte die Visual Studio implizit erzeugt, vor dem Aufruf des ersten events konstruiert wurden.

Damit sieht die Initialisierungsroutine des AddIns so aus:

private void ThisAddIn_Startup(object sender, System.EventArgs e)
 {
     Application.DocumentOpen += new Microsoft.Office.Interop.Word.ApplicationEvents4_DocumentOpenEventHandler(application_DocumentOpen);
     ribbonTabOcl.Instance.OnLoadXmi += this.OnLoadXmi;
     ribbonTabOcl.Instance.OnCheckAllExpressions += this.OnCheckAllExpressions;
     ribbonTabOcl.Instance.OnDisplayResultTaskPane += this.OnDisplayResultTaskPane;
     ConfigureLog4Net();
}

Viele Grüße,
Andreas

Technorati Tags: , ,

C# Compiler Kauderwelsch

Tuesday, May 13th, 2008

Hallo,

ich hatte mich ja kürzlich über die Qualität des Microsoft C#-compilers positiv geäußert.

Leider habe ich nun auch etwas schlechtere Erfahrungen gemacht. Folgender code

using System;
using System.Collections.Generic;
using System.Text;
 
namespace ConsoleApplication1
{
class Program
{
    static void Main(string[] args)
    {
           List aList = new List();
           foreach (string str in aList)
           {
                Console.WriteLine(str);
           }
 
           string str = "Hallo";
    }
}
}

erzeugt folgende Fehlermeldung:


Fehler 1 Eine lokale Variable mit dem Namen "str" kann in diesem Bereich nicht deklariert werden, weil dadurch "str" eine andere Bedeutung erhalten würde, was bereits im Bereich "untergeordnet" in anderer Bedeutung verwendet wird. c:\Temp\ConsoleApplication1\Program.cs 18 20 ConsoleApplication1

Alles klar?

Man könnte nun annehmen, dass der C#-Compiler anders als ein ANSI-konformer C++ Compiler den scope der ersten Deklaration von str nicht auf die foreach-Schleife beschränkt. Dann müsste man aber bei der Zuweisung an str nach der foreach-Schleife einfach die Deklaration weglassen können, was aber nicht der Fall ist.

Fehler 1 Der Name "str" ist im aktuellen Kontext nicht vorhanden. c:\Temp\ConsoleApplication1\Program.cs 18 13 ConsoleApplication1

Dieser Forumsbeitrag legt nahe, dass dieses Verhalten Absicht ist, die Dokumentation auf der MSDN website beschreibt aber einen wesentlich nachvollziehbareren Fall. Meiner Meinung nach ist das ein klarer Compiler-bug.

Liebe Leser, was meinen Sie dazu?

Viele Grüsse,
Andreas

Technorati Tags: ,