Visual Studio 2013 Preview

La preview de VS2013 est disponible en téléchargement à cette adresse : VS 2013 Express

Il y a aussi les autres version (Desktop, Web, …) et le framework 4.5.1

Tagué

Partage de code Windows Desktop/WinRT

J’ai des librairies existantes  pour manipuler des éléments métier, utilisées dans des applications desktop ou ligne de commande. Ces librairies sont écrites en C# avec le framework .Net 4.5.

Je développe une application pour tablette Surface (WinRT), qui manipule le même genre de données. Le hic est que les classes ne se trouvent pas forcément dans le même namespace entre .Net et WinRT, mais j’aimerai partager les sources pour ne pas avoir à dupliquer tout le code qui fonctionne dans les deux environnements. Et si ça pouvais aussi marcher pour Windows Phone, ça serait génial.

J’ai essayé de créer plusieurs projets, mais les fichiers sont dupliqués entre les projets, on ne peut pas inclure un fichier déjà existant…avec Visual Studio Express.

Dans Visual Studio Pro, il y a une extension : “Project Linker”, téléchargeable dans la gallery Visual Studio. L’extension était prévue pour partager du code entre WPF et Silverlight, mais on peut l’utiliser pour WinRT.

A noter la page suivante qui donne aussi quelques méthodes pour partager le code (partial class, partial methods, #if, …) : Sharing code between Silverlight and WPF

Lectures (2)

Mon livre du moment : Code Complete

C’est plein de règles pour concevoir du code “joli”. ça va de la conception d’architecture générale, jusqu’à la conception des classes, les règles de nommage des fonctions et des variables, … ça a vraiment changé ma façon d’aborder certains aspects de mon code.

C’est plein de checklists faciles à isoler et à garder sous la main.

Il y en a aussi une version traduite (Mais la traduction n’est pas excellente d’après les commentaires)

Vous pouvez le commander sur amazon en cliquant sur le titre ou les images.

RoutedCOMMAND et Contextmenu

J’ai fait une petite appli avec des raccourcis vers les programmes que j’utilise tout le temps. C’est une petite fenêtre avec une icone par application, ça ne prend pas beaucoup de place, ça n’encombre pas le bureau ni la barre des tâches. C’est juste ce qu’il me fallait.

image

Jusqu’à maintenant, je me contentais d’ajouter les raccourcis, sans jamais les enlever. J’ajoute un ContextMenu pour associer une command “Delete”, le CommandBinding dans la fenêtre pour traiter le Executed et le CanExecute, mais la command n’est jamais active…

La réponse est sur ce blog : http://www.wpftutorial.net/RoutedCommandsInContextMenu.html

Le ContextMenu est dans un VisualTree autre que la fenêtre principale, il faut donc spécifier la cible de la commande :

        <ContextMenu x:Key="ShortcutMenu">
            <MenuItem Header="Delete" Command="{x:Static local:Command.DeleteCommand}" CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" />
        </ContextMenu>

Optimisation de boucle for

En parcourant le forum de développez, je suis tombé sur ce morceau de code.

private void button1_Click(object sender, EventArgs e)
{
    Client[] clients;
    clients = this.GetClients();
 
    for (int i = 0; i < clients.Length; i++)
    {
        clients[i].ChiffreAffaire = 0;
    }
}

Naturellement, je ne l’aurais pas écrit de cette façon à cause de C++.

 

Mémoire de cours de C++

Prenons le code C++ suivant :

    for(int i=0; i<NB_CLIENTS;i++)
    {
        client[i].ChiffreAffaire = 0;
    }

Dans mon esprit, une boucle dont l’ordre d’initialisation des éléments n’est pas important devrait plutôt être écrite :

    for(int i=NB_CLIENTS-1; i>=0;i++)
    {
        client[i].ChiffreAffaire = 0;
    }

Parce qu’en assembleur, une boucle de la forme de la première utilise la variable i, et ça donne le code suivant :

; 20   :     for(int i=0; i<NB_CLIENTS;i++)

    mov    DWORD PTR _i$4[ebp], 0
    jmp    SHORT $LN3@wmain
$LN2@wmain:
    mov    eax, DWORD PTR _i$4[ebp]
    add    eax, 1
    mov    DWORD PTR _i$4[ebp], eax
$LN3@wmain:
    cmp    DWORD PTR _i$4[ebp], 1000        ; 000003e8H
    jge    SHORT $LN4@wmain

; 21   :     {
; 22   :         client[i].ChiffreAffaire = 0;

    mov    eax, DWORD PTR _i$4[ebp]
    mov    ecx, DWORD PTR _client$[ebp]
    mov    DWORD PTR [ecx+eax*4], 0

; 23   :     }

    jmp    SHORT $LN2@wmain

Alors qu’une boucle de la forme de la seconde va utiliser le registre ecx, qui est dédié à ce genre d’opérations, pour produire le code assembleur suivant :

; 20   :     for(int i=NB_CLIENTS-1; i>=0;i++)

    lea    edi, DWORD PTR [eax+3996]
    add    esp, 4

; 21   :     {
; 22   :         client[i].ChiffreAffaire = 0;

    mov    ecx, 1073740825                ; 3ffffc19H
    xor    eax, eax
    rep stosd
    pop    edi

Le second code est bien plus rapide, on n’a pas de lecture/écriture de la variable d’index, pas de calcul d’index ([ecx+eax*4])tout est géré par le registre ecx.

En réalité, le compilateur C++ de Visual sait reconnaitre ce genre de boucle et va automatiquement générer le code utilisant ecx (il faut compiler en release).

 

Et en .Net ?

Maintenant, qu’en est-il du code IL généré pour ce genre de boucle ?

            Client[] client = new Client[1000];

            for (int i = 0; i < client.Length; i++)
            {
                client[i].ChiffreAffaire = 0;
            }

On obtient le code IL suivant :

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       35 (0x23)
  .maxstack  2
  .locals init ([0] class BoucleFor2.Client[] client,
           [1] int32 i)
  IL_0000:  ldc.i4     0x3e8
  IL_0005:  newarr     BoucleFor2.Client
  IL_000a:  stloc.0
  IL_000b:  ldc.i4.0
  IL_000c:  stloc.1
  IL_000d:  br.s       IL_001c
  IL_000f:  ldloc.0
  IL_0010:  ldloc.1
  IL_0011:  ldelem.ref
  IL_0012:  ldc.i4.0
  IL_0013:  callvirt   instance void BoucleFor2.Client::set_ChiffreAffaire(int32)
  IL_0018:  ldloc.1
  IL_0019:  ldc.i4.1
  IL_001a:  add
  IL_001b:  stloc.1
  IL_001c:  ldloc.1
  IL_001d:  ldloc.0
  IL_001e:  ldlen
  IL_001f:  conv.i4
  IL_0020:  blt.s      IL_000f
  IL_0022:  ret
} // end of method Program::Main

Et pour le code

            for (int i = client.Length-1; i >=0 ; i--)
            {
                client[i].ChiffreAffaire = 0;
            }

on obtient l’IL suivant :

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       37 (0x25)
  .maxstack  2
  .locals init ([0] class BoucleFor2.Client[] client,
           [1] int32 i)
  IL_0000:  ldc.i4     0x3e8
  IL_0005:  newarr     BoucleFor2.Client
  IL_000a:  stloc.0
  IL_000b:  ldloc.0
  IL_000c:  ldlen
  IL_000d:  conv.i4
  IL_000e:  ldc.i4.1
  IL_000f:  sub
  IL_0010:  stloc.1
  IL_0011:  br.s       IL_0020
  IL_0013:  ldloc.0
  IL_0014:  ldloc.1
  IL_0015:  ldelem.ref
  IL_0016:  ldc.i4.0
  IL_0017:  callvirt   instance void BoucleFor2.Client::set_ChiffreAffaire(int32)
  IL_001c:  ldloc.1
  IL_001d:  ldc.i4.1
  IL_001e:  sub
  IL_001f:  stloc.1
  IL_0020:  ldloc.1
  IL_0021:  ldc.i4.0
  IL_0022:  bge.s      IL_0013
  IL_0024:  ret
} // end of method Program::Main


Le code est plus long ! (37 octets au lieu de 35), et on ne gagne rien, dans les deux cas, le compilateur est assez intelligent pour n’évaluer qu’une fois le client.Length au début (l’instruction ldc.i4 0x3e8), on retombe sur une boucle comme la version optimisée de C++.

Et encore, en .Net, on aurait même plutôt tendance à écrire :

            foreach (Client current in client)
            {
                current.ChiffreAffaire = 0;
            }

Ce qui donne l’IL suivant :

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       39 (0x27)
  .maxstack  2
  .locals init ([0] class BoucleFor2.Client[] client,
           [1] class BoucleFor2.Client current,
           [2] class BoucleFor2.Client[] CS$6$0000,
           [3] int32 CS$7$0001)
  IL_0000:  ldc.i4     0x3e8
  IL_0005:  newarr     BoucleFor2.Client
  IL_000a:  stloc.0
  IL_000b:  ldloc.0
  IL_000c:  stloc.2
  IL_000d:  ldc.i4.0
  IL_000e:  stloc.3
  IL_000f:  br.s       IL_0020
  IL_0011:  ldloc.2
  IL_0012:  ldloc.3
  IL_0013:  ldelem.ref
  IL_0014:  stloc.1
  IL_0015:  ldloc.1
  IL_0016:  ldc.i4.0
  IL_0017:  callvirt   instance void BoucleFor2.Client::set_ChiffreAffaire(int32)
  IL_001c:  ldloc.3
  IL_001d:  ldc.i4.1
  IL_001e:  add
  IL_001f:  stloc.3
  IL_0020:  ldloc.3
  IL_0021:  ldloc.2
  IL_0022:  ldlen
  IL_0023:  conv.i4
  IL_0024:  blt.s      IL_0011
  IL_0026:  ret
} // end of method Program::Main


C’est encore plus long (39 octets), et on voit que la taille du tableau est calculée encore une seule fois au début.

Il faudrait quelques tests de performance pour voir quelle solution est la plus rapide.

Lectures

Mon livre du moment : CLR Via C#.

Comment ça marche dans la CLR, c’est plein d’astuces pour optimiser vos développements. C’est plutôt bon (même si je trouve que c’est encore un peu trop haut niveau quelquefois).

Vous pouvez le commander sur amazon en cliquant sur le lien ou l’image.

J’ai testé Linq To SQL et azure

Grand fan de Donjons et Dragons, j’a un site perso (D&D Collection) que j’avais fait quand je développais un peu en php. C’est du php/mysql, ça marche très bien.

L’exercice, c’est de porter tout ça sur SQL Server, refaire un site un peu plus sympa en ASP.Net, et que ce soit aussi consultable sur un PC, un windows phone, et une tablette (RT).

J’ai commencé par installer un SQL Server Express sur mon PC, porter la base mysql vers SQL Server, et développer une petite appli minimaliste en WPF avec VS2012 Express pour visualiser les données.

Par exemple, la base contient une table avec les différentes éditions existantes. Pour chaque édition, il y a un identifiant unique et un nom. En utilisant LinqToSql, la classe pour accéder à cette table se défini de la façon suivante :

using System;
using System.Data.Linq.Mapping;
using System.Windows.Documents;

namespace DDCore
{
    [Table(Name="Editions")]
    public class Edition
    {
        [Column(IsPrimaryKey=true, IsDbGenerated=false, DbType="INT NOT NULL")]
        public int Id { get; set; }

        [Column(Name="Edition")]
        public String Name { get; set; }
    }
}

Il faut ensuite définir une autre classe pour la source de données. Bien entendu, dans la vrai vie, on met la chaine de connexion dans un fichier de configuration.

using System.Data.Linq;

namespace DDCore
{
    public class DDCollectionDataContext : DataContext
    {
        public static String ConnectionString = "***************";

        public DDCollectionDataContext(string connectionString)
            : base(connectionString)
        { }

        public Table<Edition> Editions;
    }
}

Et c’est tout ! On peut maintenant accéder aux éditions contenues dans la base en utilisant simplement la propriété Editions dans la classe DDCollectionDataContext.

Maintenant, il faudrait que cette base soit accessible de partout. Pas question d’acheter un PC dédié avec un windows server pour faire tourner tout ça. J’avais loué une dedibox avec un windows server, installé SQL Server Express, IIS, … mais c’est quand même très long, ça coute 25 euros par mois, et il faut dire ce qui est : j’y connais pas grand chose en sécurité web et BD.

Donc hop, création d’un compte Azure, (il y a une offre gratuite de 90 jours), instanciation d’une base SQL Azure (de 1 Go par défaut, bien assez pour un site perso). L’accès et la configuration Azure se fait à partir d’un navigateur, c’est du Silverlight, tout est fait avec des wizards, c’est super simple à mettre en œuvre, on a une base de données en ligne en quelques minutes (secondes).

L’interface de gestion de la base est minimale, mais suffisante :

image

l’onglet ‘Conception’ permet d’accéder à une page avec la liste de tables, des vues et des procédures stockées. On peut bien sûr ouvrir un fichier local contenant les requêtes pour la création de la base et l’insertion des données.

image

Une fois la base recréée sur Azure, le changement à faire pour accéder à la base Azure, c’est uniquement de changer la ConnectionString. Tout le reste fonctionne comme avant !

Pour l’application windows Desktop, c’est super simple. Prochaine étape, le portage de l’application sur windows Phone et Windows RT.

Tagué

Hoster une fenêtre (Win32) dans une application WPF

J’ai une application qui peut présenter des documents (des pdf, des avi, …). Pour ce faire, j’utilise des contrôles ActiveX dans des onglets.

Maintenant, on voudrait pouvoir afficher dans un onglet une ‘application’ (un .exe).

L’idée, c’est de lancer l’application normalement, puis de capturer la fenêtre qui se trouve sur le desktop de Windows, et de la replacer dans un onglet de mon appli.

Première recherche, comment hoster une fenêtre Win32 dans du WPF ? On tombe assez vite sur cette page sur MSDN. Le principe est assez simple, on dérive d’une classe HwndHost, on implémente deux méthode pour la construction et destruction du contrôle win32, et le tour est joué.

On ne va pas construire un nouveau contrôle, mais chercher une fenêtre, récupérer son handle, et changer son parent. Pour cela, on a besoin de deux fonctions win32 : FindWindow, et SetParent. On les importe au début de ma classe. Pour les déclarations des fonctions, il y a le site pinvoke.Net. Il y a les signatures en C# et VB pour la plupart des fonctions win32.

On fait le test avec la calculatrice de windows (calc.exe). Elle crée une fenêtre nommé ‘”Calculatrice”. Il faut prendre soin de la lancer avant d’exécuter le programme. Pour trouver les noms des fenêtres, il y a un utilitaire : spy++. Il faisait partie des anciens SDK Windows, mais je ne l’ai pas retrouvé dans les nouveaux.

Le code initial pour la class CalcHost est le suivant :

class CalcHost : System.Windows.Interop.HwndHost
{
    [DllImport("user32.dll")]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

    protected override System.Runtime.InteropServices.HandleRef BuildWindowCore(System.Runtime.InteropServices.HandleRef hwndParent)
    {
        IntPtr handle = FindWindow(null, "Calculatrice");
        if (handle.ToInt32() != 0)
        {

            IntPtr oldParent = SetParent(handle, hwndParent.Handle);
            if (oldParent.ToInt32() == 0)
            {
                System.Diagnostics.Debug.WriteLine(Marshal.GetLastWin32Error());
            }
        }

        return new HandleRef(this, handle);
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
        IntPtr oldParent = SetParent(hwnd.Handle, new IntPtr());
        if (oldParent.ToInt32() == 0)
        {
            System.Diagnostics.Debug.WriteLine(Marshal.GetLastWin32Error());
        }
    }

}

Dans la fenêtre principale de l’application, on ajoute le contrôle :

<Window x:Class="HostWindow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:HostWindow"
        Title="MainWindow" Height="350" Width="525">
    <TabControl>
        <TabItem Header="Calc">
            <local:CalcHost />
        </TabItem>
    </TabControl>
</Window>

Et voilà !

Et si on exécute ça, “cha malche pas…cha m’énèlve”

On a une erreur parce qu’on ne peut pas devenir parent de la fenêtre. En fait, c’est à cause du style de la fenêtre, il faut le modifier pour dire que c’est une fenêtre fille, et retirer d’autres flags qui disent que c’est une fenêtre principale, ou une popup. On va aussi en profiter pour retirer la barre de titre, avec les boutons pour redimensionner et fermer. Il faut deux autres API pour ça :

[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern uint SetWindowLong32(IntPtr hWnd, int nIndex, uint dwNewLong);

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowLong(IntPtr hWnd, int nIndex);

Les signatures sur pinvoke.Net donnent des int pour les valeurs de retour des fonctions et pour le troisième paramètre de SetWindowLong. L’important, c’est surtout que ça fasse 32 bits, et comme ce sont des flags, il vaut mieux utiliser un uint.

On ajoute les constantes utiles pour les styles qu’on veut changer, et ça donne le code suivant :

public class CalcHost : System.Windows.Interop.HwndHost
{
    [DllImport("user32.dll")]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

    [DllImport("user32.dll", EntryPoint = "SetWindowLong")]
    private static extern uint SetWindowLong32(IntPtr hWnd, int nIndex, uint dwNewLong);

    [DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowLong(IntPtr hWnd, int nIndex);

    const int GWL_STYLE = (-16);

    // Window Styles 
    const UInt32 WS_OVERLAPPED = 0;
    const UInt32 WS_POPUP = 0x80000000;
    const UInt32 WS_CHILD = 0x40000000;
    const UInt32 WS_CAPTION = 0xC00000;      // WS_BORDER or WS_DLGFRAME  

    protected override System.Runtime.InteropServices.HandleRef BuildWindowCore(System.Runtime.InteropServices.HandleRef hwndParent)
    {
        IntPtr handle = FindWindow(null, "Calculatrice");
        if (handle.ToInt32() != 0)
        {
            uint Styles = GetWindowLong(handle, GWL_STYLE);
            Styles |= WS_CHILD;
            Styles &= ~WS_CAPTION;
            SetWindowLong32(handle, GWL_STYLE, Styles);

            IntPtr oldParent = SetParent(handle, hwndParent.Handle);
            if (oldParent.ToInt32() == 0)
            {
                Console.WriteLine(Marshal.GetLastWin32Error());
            }
        }

        return new HandleRef(this, handle);
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
        IntPtr oldParent = SetParent(hwnd.Handle, new IntPtr());
        if (oldParent.ToInt32() == 0)
        {
            Console.WriteLine(Marshal.GetLastWin32Error());
        }
        uint Styles = GetWindowLong(hwnd.Handle, GWL_STYLE);
        Styles &= ~WS_CHILD;
        Styles |= WS_CAPTION;
        SetWindowLong32(hwnd.Handle, GWL_STYLE, Styles);

    }
}

On lance la calculatrice, puis le programme, et on se retrouve avec la calculatrice dans l’onglet. Elle redevient autonome quand on ferme l’application (mais elle garde les dimensions qu’elle avait dans l’onglet). Il suffirait de sauvegarder/restaurer sa taille.

De beaux outils

Pas de dev sans outils, voici donc ma boite à outils personnelle. Comme je fais beaucoup de dev perso, juste pour le plaisir, ils sont gratuits :

Crimson Editor :  Un petit éditeur de texte sympa. Il supporte la coloration syntaxique pour plein de langages, et on peut facilement l’ajouter pour d’autres langages en créant de simples fichiers textes.  Il a un mode d’édition en colonne super que je n’ai retrouvé nulle part ailleurs.

Visual Studio Express : Mon éditeur préféré. Une version express à télécharger pour chaque langage (C++/C#/VB/Web/Phone). Préférez les version 2010 pour le moment, parce que la version 2012 ne permet de faire que des applications (moches) avec WinRT pour Windows 8.

Kaxaml : un notepad pour xaml. Pratique pour tester rapidement des petits trucs.

Tortoise SVN : Le nécessaire pour la gestion de vos sources sous windows. J’utilise aussi la version gratuite de Visual SVN Server pour la maintenance des repositories SVN, la gestion des utilisateurs, …

Winmerge : Pour la comparaison de fichiers. La première chose à faire quand on installe Tortoise, c’est de remplacer le comparateur TortoiseMerge par winmerge !

Paint.Net : Un éditeur d’images. C’est pas photoshop, mais ça suffit largement pour la plupart des tâches courantes. Rien à voir avec Paint. Il en existe aussi une version portable.

Virtual Box : Pour virtualiser des machines. Pratique pour avoir un linux sous la main (pour faire des tests avec Mono, par exemple) ou un windows dans lequel on pourra installer tout et n’importe quoi. Pour un linux, ça marche bien mieux que le Virtual PC de Windows 7.

Opéra : Un navigateur internet. C’est surement pas le meilleur, mais je l’ai adopté. Indispensable pour poser les questions à Google.

Voilà, on a des bonnes bases pour commencer.