Copier des blocs de mémoire en c# et VB.NET
Comment faîtes-vous actuellement ? Il y a de fortes chances que vous utilisiez le Marshal.copy pour la mémoire non-managée. Vous utilisez peut-être une dll via l'interop
C'est en étudiant le langage IL (intermediate language) que je me suis aperçu qu'une instruction IL permettait de le faire : cpblk
L'idée de me faire une petite dll pour me permettre de réaliser des copies rapides de blocs sous c# ou Vb.net m'est vite venu. Démonstration rapide :
Copiez le code suivant dans notepad :
.assembly extern mscorlib
{
.ver 2:0:0:0
}
.assembly Memoire
{
.ver 1:0:0:0
}
.module Memoire.dll
.class public abstract auto ansi sealed beforefieldinit Memoire.Memory
extends [mscorlib]System.Object
{
.method public hidebysig static void MemCpy(int32 destination,
int32 source,
int32 nboctet) cil managed
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
cpblk
ret
}
.method public hidebysig static void MemCpy(native int destination,
native int source,
int32 nboctet) cil managed
{
.maxstack 3
ldarg.0
ldarg.1
ldarg.2
cpblk
ret
}
}
A noter que j'ai laissé les noms comme destination, source et nboctet afin qu'il puisse apparaître dans l'éditeur visual studio après le référencement de la dll. Ces noms d'argument ne sont pas obligatoires en IL. De même l'instruction hidebysig ne sert à rien, si ce n'est pour préciser à c# ou vb.net que la méthode
possède une autre signature semblable.
ldarg.0, ldarg1, ldarg2 sont des instructions permettant de déposer les arguments de la méthode sur la pile.
L'instruction cpblk s'excute à partir des valeurs de la pile. A noter que celle-ci est classé comme invérifiable, c'est à dire que cela correspond au contexte unsafe du c#. Ce qui correspond au SkipVerification des permissions .NET
Sauvegardez le code IL sous Memoire.IL
ouvrez une invite DOS et tappez : ILASM Memoire.il /dll
N'hésitez pas à regardez les options de ilasm /?
Memoire.dll a été généré. Il vous suffit maintenant d'ajouter comme référence dans votre projet c# ou VB.NET.
Effectuons un test pour voir si ceci est concluant :
public static void CopyGDI(Bitmap destination, Bitmap source)
{
Graphics g = Graphics.FromImage(destination);
g.DrawImage(source, 0, 0, source.Width, source.Height);
g.Dispose();
}
public static void CopyPointer(Bitmap destination, Bitmap source)
{
int width = source.Width;
int height = source.Height;
unsafe
{
BitmapData bmpData = source.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
BitmapData bmpDataMirror = destination.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
//image source
int* newPixel = (int*)(void*)bmpData.Scan0;
//image destination
int* mPixel = (int*)(void*)bmpDataMirror.Scan0;
for (int y = 0; y < height; y++)
{
//on copie ligne à ligne comme doit le faire sans doute le DrawImage
Memoire.Memory.MemCpy((int)mPixel, (int)newPixel, width * 4);
newPixel += width;
mPixel += width;
}
source.UnlockBits(bmpData);
destination.UnlockBits(bmpDataMirror);
}
}
public static void CopyIntPtr(Bitmap destination, Bitmap source)
{
int width = source.Width;
int height = source.Height;
BitmapData bmpData = source.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
BitmapData bmpDataMirror = destination.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
Memoire.Memory.MemCpy(bmpDataMirror.Scan0, bmpData.Scan0, width * height * 4);
source.UnlockBits(bmpData);
destination.UnlockBits(bmpDataMirror);
}
Résultat des tests : les méthodes copyIntPtr et copyPointer, une fois la première initialisation faîte, sont 6 à 7 fois plus rapide que la méthode CopyGDI.
Je vous détaillerai bientôt sous forme d'un article ce que je viens de vous présenter succintement dans ce post. J'espère que ceux qui recherchaient un peu plus de performance en c# et vb.net, trouveront ici leur bonheur.
ps : l'exemple ci-dessus n'est peut-être pas le plus adapté pour la pédagogie mais je n'ai pas résisté à vous présenter cette perspective.
ps2 : Si vous faîtes un coup de reflector sur la dll, il vous mettra Memcpy aussi bien en c# qu'en VB.NET car il n'a pas d'équivalent pour le c# ou le VB.NET. En fait, il s'agit d'une instruction de c++.
ps3 : Ce post est un peu brute, je vous le présenterai mieux dans un prochain article.
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :