Partager via


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.

https://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

https://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 aBITMAPINFOHEADER 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

  • Anonymous
    August 13, 2007
    PingBack from http://msdnrss.thecoderblogs.com/2007/08/13/

  • Anonymous
    August 13, 2007
    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 !

  • Anonymous
    August 13, 2007
    Ca serait pas plutot "IntPtr lpvBits" tout court plutot?

  • Anonymous
    August 13, 2007
    mm, tu veux une valeur nulle ou zéro ?  (ahaha). Le IntPtr.Zero mets à 0 et pas à null non ? ( :D ). Je ferai comme Simon autrement pour la variable que j'initialiserai a IntPtr.Zero !

  • Anonymous
    August 13, 2007
    The comment has been removed

  • Anonymous
    August 13, 2007
    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.

  • Anonymous
    August 13, 2007
    (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 ^^.

  • Anonymous
    August 13, 2007
    The comment has been removed

  • Anonymous
    August 13, 2007
    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.

  • Anonymous
    August 14, 2007
    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...

  • Anonymous
    August 14, 2007
    The comment has been removed

  • Anonymous
    August 14, 2007
    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

  • Anonymous
    August 14, 2007
    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^^.

  • Anonymous
    August 15, 2007
    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.

  • Anonymous
    August 15, 2007
    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 ;)

  • Anonymous
    August 16, 2007
    En voici une très courte (à énoncer en tout cas): Comment empêcher l'instantiation d'une class T sans