Geek Quizz X: un peu d’interop ?


Interopérer avec win32 n’est pas toujours aisé. La notion de pointeur n’existant pas en .Net, quelques petits problèmes peuvent arriver…


Prenons le cas de la méthode GetDIBits de gdi32.dll.


http://msdn2.microsoft.com/en-us/library/ms532334.aspx


int GetDIBits( HDC hdc, // handle to DC HBITMAP hbmp, // handle to bitmap UINT uStartScan, // first scan line to set UINT cScanLines, // number of scan lines to copy LPVOID lpvBits, // array for bitmap bits LPBITMAPINFO lpbi, // bitmap data buffer UINT uUsage // RGB or palette index );

Nous pouvons la mapper en .Net grâce à un

http://pinvoke.net/default.aspx/gdi32/GetDIBits.html


[DllImport(gdi32.dll)]
static extern int GetDIBits(IntPtr hdc, IntPtr hbmp,
uint uStartScan, uint cScanLines,
out byte[] lpvBits,
ref BITMAPINFO lpbmi, uint uUsage);

“If lpvBits is NULL, GetDIBits examines the first member of the first structure pointed to by lpbi. This member must specify the size, in bytes, of a BITMAPCOREHEADER or a BITMAPINFOHEADER structure. The function uses the specified size to determine how the remaining members should be initialized.”

Cette technique est souvent utilisée par les APIs de windows: avant d’appeler une méthode qui remplit un buffer, on l’appelle une première fois avec la valeur nulle (et non pas un pointeur sur null !) et on récupère la taille du buffer à allouer en retour. Ensuite on alloue le buffer et on réappelle la même méthode en passant cette fois-ci le buffer.

Si vous ne comprenez pas le contexte, ce n’est pas très grave. Voici ma question: comment passer la valeur nulle (0) dans la référence out byte[] lpvBits ?


[Update] Quizz suivant : Geek Quizz XI: reflection ?

Comments (16)

  1. Simon says:

    Tu déclares une 2e méthode externe avec à la place de "out byte[] lpvBits": "ref IntPtr lpvBits". Et tu l’appelles en passant une variable initializée à IntPtr.Zero.

    Bouh, c’est moche !

  2. Miiitch says:

    Ca serait pas plutot "IntPtr lpvBits" tout court plutot?

  3. Danuz says:

    mm, tu veux une valeur nulle ou zéro ?  (ahaha). Le IntPtr.Zero mets à 0 et pas à null non ? ( 😀 ). Je ferai comme Simon autrement pour la variable que j’initialiserai a IntPtr.Zero !

  4. Simon says:

    Attention, ici on ne veut pas avoir un pointeur null, mais un pointeur qui pointe vers 0.

    quand tu as un PInvoke vers une méthode C++ qui prends un int * , dans ton P/Invoke, tu trouveras un ref int (ou un out int, si il s’agit d’un paramètre destiné à la sortie).

    Quand je fais ref IntPtr dans mon P/Invoke, j’indique que ma méthode native attends un pointeur vers un pointeur (ce qui revient à avoir un pointeur vers un tableau, comme le prévoir le out byte [], ou à un pointeur vers un entier, car un pointeur n’est rien d’autre qu’un entier).

    Donc, quand je passe une variable de type IntPtr dont la valeur est à IntPtr.Zero, en paramêtre ref, je passe un pointeur vers un pointeur null, donc un pointeur vers 0.

  5. Miiitch says:

    Pourtant la valeur nulle, c’est ce que l’on veut:

    "If lpvBits is NULL, GetDIBits examines the first member of the first structure pointed to by lpb bla bla bla".

    D’ailleurs, vu le prototype, un byte [] serait largement suffisant, et je pense qu’il y a une erreur dans le prototype: j’aurais mis [Out] byte [] plutot que out byte []. En plus avec [Out] je peux directement passer "null"!!

    Simon, pour le ref IntPtr, dans l’idée je suis tout à fait d’accord avec toi, mais je pense que ce n’est pas ce que la méthode attends.

  6. Danuz says:

    (Okaye! J’avais mal lu! Merci pour les précisions, ça faisait longtemps !). Autrement, je ne vois vraiment pas!  Je ne fais pas ça souvent ^^.

  7. Simon says:

    Effectivement, j’ai mal lu le descriptif de la méthode, ce n’est pas un pointeur dont la valeur est null, mais carrément un pointeur null que l’on attends. Du coup, on peut virer le ref, et passer directement IntPtr.Zero

  8. Tetranos says:

    Plus simplement j’aurais déclaré une variable sans l’affecter.

    byte[] lpvBits;

    J’aurais ensuite appelé la méthode externe avec cette variable. Vu que le proto déclare lpvBits en out le compilo ne s’attend pas à ce que la variable soit affectée avant l’appel.

  9. mitsu says:

    Oki, l’astuce était bien l’écriture d’un second prototype.

    Par contre vous ne vous prenez pas un peu la tête ? Quitte à redéfinir pour passer un entier moi j’utilise: Int32 lpvBits et je passe tout simplement 0…

  10. Simon says:

    Bah, ce 0 est censé être un pointeur null (plutôt qu’un pointeur vers le début d’un tableau)… Du coup, d’un point de vue conceptuel, je trouve qu’IntPtr a mieux sa place.

    2e avantage, si un jour tu veux porter ton code en Win64, si tu as mis Int32 pour un pointeur, tu l’as dans l’os… Avec IntPtr, ton paramêtre aura automatiquement la bonne taille.

  11. mitsu says:

    Du point de vue spéc, l’API win32 utilise souvent des pointeurs pour passer des entiers (pas seulement 0 ou null). Du coup d’un point de vue fonctionnel, ça ne me choque pas de mettre un int.

    Pour la version 64bits, ça sera un autre prototype car dans mon exemple je mappe [DllImport("gdi32.dll")] !

    Donc pas de soucis pour y associer un Int32 :p

  12. Simon says:

    Hmmm, il me semble que pour des raisons de facilité de portage de code, les version 64 bits des dll s’appellent aussi "gdi32.dll", "kernel32.dll" etc.

    Sous Windows x86-64, le dossier System32 contient les versions 64 bits, et le dossier SysWOW64 contient les versions 32 bits. Quand tu éxécutes une appli 64 bits, ca va piocher les dlls dans System32, et quand tu éxécute une appli 32 bits, tu passes en mode WOW64 et le système fait une grosse feinte pour que l’appli 32 bits voir le contenu de SysWOW64 à la place de System32.

    Donc à mon avis, ta déclaration extern foire sur un système 64 bits^^.

  13. mitsu says:

    Exact. Ce sont des choix qui sont parfois assez piégeux ! Enfin il suffit de savoir…et de s’en souvenir.

    Merci pour ta remarque Simon.

  14. Gaëtan says:

    Simon 🙂

    Il faut toujours se méfier des noms de répertoire…

    D’ailleurs, cette feinte est l’une des raisons du plantage de pas mal d’appli sous Vista64, quand on voit l’erreur levée.

    Il me semble qu’il y avait un magnifique article sur la MSDN pour expliquer ce fonctionnement et son choix, mais je n’arrive plus à retrouver le lien.

    Ps: Simon, tu fais de plus en plus ton JB maintenant 😉

  15. En voici une très courte (à énoncer en tout cas): Comment empêcher l’instantiation d’une class T sans