Utiliser une librairie C/C++ non managée dans un code c# (suite)

Comme nous avons pu le voir dans un article précédent, le wrapping de code C++ en C# peut être très simple, malheureusement les choses peuvent rapidement se compliquer !

En effet, continuons de travailler avec la dll GNU Scientific Library, et essayons d’utiliser une gls_matrix, la déclaration dans la documentation est la suivante :

typedef struct
{
    size_t size1;
    size_t size2;
    size_t tda;
    double * data;
    gsl_block * block;
    int owner;
} gsl_matrix;

La documentation nous explique aussi qu’un gls_block est la structure décrite ci dessous et que l’allocation d’une gsl matrix se fait via la fonction suivante :

typedef struct
{
    size_t size;
    double * data;
} gsl_block;

Function: gsl_matrix * gsl_matrix_alloc (size_t n1, size_t n2)

La première chose à faire est de déclarer une structure de type gsl_block en C#

[StructLayout(LayoutKind.Sequential)]
internal class gsl_block
{
    public uint size = 0;
    public IntPtr data = IntPtr.Zero;
}

On commence par déclarer un type struct et indiquer comment le ‘layout’ doit être effectué puis on déclare la class avec ses attributs. On peut constater la présence d’un type IntPrt , c’est la manière C# de gérer les pointeurs! (vue simplifiée mais bien pratique ^^). On peut maintenant déclarer le type gls_matrix comme suit !

    [StructLayout(LayoutKind.Sequential)]
    internal class gsl_vector
    {
        public uint size = 0;
        public uint stride = 0;
        public IntPtr data = IntPtr.Zero;
        public gsl_block block;
        public int owner = 0;
    }

Il ne reste maintenant plus qu’à compléter la fonction d’allocation gls_matrix_alloc. Là malheureusement il faut vraiment se plonger dans le code de la GSL pour trouver les sources et les adapter. En faisant ça on constate que l’on aura aussi besoin de la fonction gls_block_alloc. Le code des deux fonctions nécessaires sont si dessous :

        internal static gsl_block gsl_block_alloc(uint n)
        {
            gsl_block b = new gsl_block();
            b.data = Marshal.AllocCoTaskMem((int)n * sizeof(double));
            b.size = n;
            return b;
        }

        internal static gsl_matrix gsl_matrix_alloc(uint n1, uint n2)
        {
            gsl_matrix b = new gsl_matrix();
            b.size1 = n1;
            b.size2 = n2;
            b.tda = n2;
            b.block = gsl_block_alloc(n1 * n2);
            b.data = b.block.data;
            b.owner = 1;
            return b;
        }

Le point sensible l’implémentation est de bien comprendre ou est allouée la mémoire, en fait toutes les données sont stockées dans le gls_block c’est donc là qu’il faut alloquer la mémoire, le reste n’est qu’un jeu de pointeur. Bien sur une fois la matrice obtenu vous n’êtes pas encore au bout de vos peines, il faut maintenant coder les fonctions capables de libérer la mémoire allouée :

        internal static void gsl_block_free(gsl_block b)
        {
            if (b != null && b.data != null)
            {
                Marshal.FreeCoTaskMem(b.data);
                b.data = IntPtr.Zero;
            }
            b.size = 0;
        }
        internal static void gsl_matrix_free(gsl_matrix m)
        {
            if (m == null)
                return;
            gsl_block_free(m.block);
            m.block = null;
            m.data = IntPtr.Zero;
            m.size1 = 0;
            m.size2 = 0;
            m.tda = 0;
            m.owner = 0;
        }

Là encore, il faut libérer la mémoire marshallée dans la gsl_block, pour le reste on pourrait se contente de tout remettre à zéro. Dernière étape, allouer et récupérer des valeurs aux éléments de la matrice :

        [DllImport("libgsl.dll")]
        internal static extern void gsl_matrix_set(gsl_matrix m, uint i, uint j, double x);
        [DllImport("libgsl.dll")]
        internal static extern double gsl_matrix_get(gsl_matrix m, uint i, uint j);

Vous pouvez constater que cette fois je ne définis pas d’entry point dans la dll car le nom C# et le nom C++ sont identiques! Bien sur une gsl_matrix toute seule ne sert pas à grand chose, mais en ajoutant les fonctions de la GSL permettant de faire une décomposition de chloesky ou tout autre opération matricielle …

Création d'une base SQLServer Compact Edition en C#

Des fois certains programmes nécessitent des bases de données qui ne contiendront pas des terras de données, qui ne doivent pas traiter 10 000 000 de tuples à la secondes et qui surtout doivent être légères et faciles d’utilisation ! SQL Server Compact Edition n’est pas un candidat évident de par sa naissance : destiné de base aux applications pour systèmes embarqués (téléphones mobiles, PDA..) un programmeur classique ne pensera pas forcément à l’utiliser dans une de ses applications …. et pourtant les avantages sont multiples !

  • La légèreté est le premier avantage :  Il suffit d’ajouter une référence à son projet C# : System.Data.SqlServerCE et une bonne partie du travail est fait.
  • Lors du déploiement de l’application pas d’installation supplémentaires (types une base mysql…)
  • Création de fichiers (*.sdf) facilement lisibles, modifiables, voir même commitables sur svn (finit les dump !)
  • j’en passe !

Évidement il ne faudrait pas non plus oublier les défauts de SqlServerCE :

  • Technologie Microsoft (ce qui n’est pas un défaut en soit mais limite la portabilité … évidement quand on code en C# ce n’est pas forcément ce que l’on recherche le plus :))
  • Performances bien inférieures à des bases “pro” type SqlServer, Mysql5 ou oracle 9
  • Limitation de taille à 4 Go
  • j’en passe aussi !

Vous l’aurez compris, son utilisation n’est pas à généraliser mais dans des cas précis (application de traitement de statistiques sur quelques millions de données, gestion de configurations par stockage en base …) l’utilisation peut valoir le détour. Comment faire alors ? Comme avec beaucoup d’outils maintenant on peut faire çà “à la souris” en utilisant Visual Studio 2008 … Ce n’est pas vraiment ce que j’aime fait, préférant comprendre ce qui se passe plutôt que faire des drag & drop (oui parce que le jour ou le bouton change de place on à pas l’air malin :)) Bref, une créons une base dont le fichier source sera ref.sdf , commençons par vérifier si on peut le créer !

Bref, une créons une base dont le fichier source sera ref.sdf , commençons par vérifier si on peut le créer !


if (System.IO.File.Exists("ref.sdf")){
System.IO.File.Delete("ref.sdf");}

Maintenant que la place est libre, créons notre base, pour cela utilisons les appels suivants:


String str = “Data Source = ref.sdf; Max Database Size = 4000;”
SqlCeEngine engine = new SqlCeEngine(str);
engine.CreateDatabase();
engine.Dispose();

Dans la création string (str) on peut rajouter d’autres paramètres, tel un password … le Database Size ne représente pas la place réservée par le fichier mais la taille maximale autorisée pour le fichier base. Évidement sur un PC classique ça peut sembler étrange mais rappelez vous que SqlServerCE est avant tout utile pour les applications mobiles ! Maintenant la base est créée mais elle est vide, il faut donc s’y connecter puis créer une table.


private SqlCeConnection myConnection = null;

//Connexion à la base de données

try {
myConnection = new SqlCeConnection(“Data Source = C:\\Documents and Settings\\Nicolas\\Mes documents\\ref.sdf;”);
myConnection.Open();
}catch (Exception e) {
Console.WriteLine(“Erreur lors de la connection a la base de donnees. Message d’erreur:”);
Console.WriteLine(e.Message);
throw (e);
}

//Création des tables si on passe ici, il n’y a pas eu de throw -> pas de test sur myConnection

SqlCeCommand cmd = myConnection.CreateCommand();

string s =

“CREATE TABLE  test (Date datetime, ”Cotation float, Rendement float, Primary Key (Date))”;
cmd.CommandText = s;

//Exécution de la requête.
try{
cmd.ExecuteNonQuery();
}catch (Exception e){
Exception f = new Exception(“Erreur lors de la creation de la table: ” + e.Message);
throw (f);
}

Vous disposez maintenant d’une base enregistrée dans le fichier ref.sdf, la base contient une table ‘test’ qui à 3 attributs : Date de type timedate et qui est la clé primaire de la table, Rendement et Cotation qui sont de type float.

Dans un prochain article des informations sur l’insertion / la récupération de données dans une base sql!

Utiliser une librairie C/C++ non managée dans un code c#

Quiconque à déjà programmé en C, C++ et C# connaît les qualités et défauts de chacun de ses langages, le C rapide mais très bas niveau, le C# s’exécutant via une plateforme (.net) donc plus lent, mais par contre extrêmement pratique lorsque les sorties de ce que l’on peut appeler calculateur doivent être interfacée avec une Interface graphique, qu’il s’agisse de winform ou de sites ASP et le C++ qui permet une programmation objet tout en restant assez bas niveau (ami puristes ne vous offusquez pas il s’agit d’une description sommaire, mon but n’est pas de lister qualités défauts et implémentations de chacun de ses langages !).

Seulement voilà, des fois il est nécessaire d’utiliser du cote C++ dans une application codée en C#… si il se trouve que votre librairie est non managée là c’est le drame ! Enfin le drame, pas tout à fait: C# implémente des fonctions qui permettent d’adapter , ‘wrapper’ pour utiliser le mot en usage les fonctions de la dll et de les réutiliser dans votre application!

Par exemple j’ai du réutiliser la librairie GSL dans un projet devant être réalisé en C#. Problème numéro 1 : les sources telles quelles ne peuvent pas être utilisées dans un environnement Windows, heureusement de fiers codeurs en ont développé une version dll . Seulement voilà, une fois que j’ai la dll, que faut-il faire pour réutiliser les fonctions ?

Plusieurs solutions existent, en particulier utiliser du code « unsafe » autorisant les pointeurs en C#, mais je préfère ‘Marshaller » les types de données, c’est à dire indiquer comment doivent être gérés les passages de paramètres par rapport à leur type. Par exemple essayons d’utiliser la fonctions gsl_stat_mean décrite dans la documentation GSL comme : double

gsl_stats_mean (const double data[], size_t stride, size_t n)

This function returns the arithmetic mean of data, a dataset of length n with stride stride.

Il est dit : la fonction retourne un double, prend un paramètre un tableau de double et deux paramètre de type size_t, quelques recherches dans la documentation  et sur google nous apprennent que les paramètre size_t peuvent être assimilés à des int, on connaît donc toutes les informations sur la fonction, il ne reste plus qu’à indiquer ces données au code C#

[DllImport("libgsl.dll", EntryPoint = "gsl_stats_mean")]
internal static extern double mean(
[In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] data,
[In] int stride,
[In] int n
);

Décomposons un peu cette fonction.

  1. [DllImport(« libgsl.dll », EntryPoint = « gsl_stats_mean »)] : on utilise une dll qui s’appelle « libgsl.dll » et le point d’entrée, c’est à dire la fonction de la dll que l’on appelle est : « gsl_stats_mean »
  2. internal static extern double mean( : internal , cette fonction ne peut être appelée que dans ce projet, static pas de soucis, extern : le code de la fonction n’est pas présent mais vient d’une source externe. double c’est le type qui est retourné par la fonction et mean correspond au nom C# de la fonction.
  3. [In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] data, : premier paramètre de la fonction mean. [In] signifie que le paramètre doit être Marshaller du C# a la dll C++ mais qu’il ne sera pas modifié par la fonction C++ (sinon ou utilise [In, Out] voir [Out] tout seul! MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3) : le type du paramètre est un tableau (LPArray) et c’est le troisième paramètre de la fonction qui en indique la taille (ceci est facultatif). double[] data : type c# du paramètre et nom du paramètre dans la fonction C++
  4. Idem pour les paramètres      [In] int stride et   [In] int n sauf que le type int n’a pas à être marshallé.

Pour appeler la fonction rien de plus simple :

Main(){
double [] test = {1,2,3};
Console.WriteLine(ClassGsl.mean(test,1,3));
Console.Read();
}

et on obtient : 2!

Il n’y a plus qu’à reproduire ce schéma pour toutes les fonctions que l’on souhaite appeler, évidement quand des structures s’en mêlent tout devient un peu plus compliqué… un prochain article sur le sujet dans très peu de temps !