Utilisation du Windows Runtime dans une librairie C, pour une application Desktop

Erratum :

Un des mes potes développeurs à la Corporation, Alain Zanchetta pour ne pas le nommer, m’a fait la remarque suivante :

La construction suivante en terme de C++ est incorrecte (même si cela semble marcher)

HSTRING hs = HStringReference(RuntimeClass_Windows_Media_MediaProperties_ImageEncodingProperties).Get();
    hr = ::GetActivationFactory(hs, &ImageEncodingProperties);

En effet la création d’un objet temporaire, n’est valide que jusqu’à la fin de l’instruction dans laquelle il est incorporé. Dans cette exemple la HSTRING n’est valide que jusqu’au Point Virgule suivant (Après le Get()). Le mieux serait de le faire en une seule ligne.

hr = ::GetActivationFactory(HStringReference(RuntimeClass_Windows_Media_MediaProperties_ImageEncodingProperties).Get(), &ImageEncodingProperties);

Et en effet, en regardant de plus prêt la classe HStringReference, (CoreWrappers.h), la HSTRING est créée à l’aide de la méthode WindowsCreateStringReference et maintenue dans la variable hstr_

class HStringReference
{
private:
    void CreateReference(const wchar_t* str, unsigned int bufferLen, unsigned int len)
    {
        __WRL_ASSERT__(len < bufferLen);
        if (len >= bufferLen)
        {
            len = bufferLen - 1;
        }

        HRESULT hr = ::WindowsCreateStringReference(str, len, &header_, &hstr_);
        // Failfast if developers try to create a reference to a non-NUL terminated string
        if (FAILED(hr))
        {
            ::Microsoft::WRL::Details::RaiseException(hr);
        }
    }

Une fois l’instruction Get() terminée qui retourne notre HSTRING

HSTRING Get() const throw()
    {
        return hstr_;
    }

l’appel au destructeur est immédiat.

  ~HStringReference() throw()
    {
        hstr_ = nullptr;
    }

Voulant être le plus didactique possible, j’ai voulu décomposer le code mais j’ai introduit ce bug. D’ailleurs tous les exemples de MSDN utilisent cette seconde construction j’aurai dû me méfier.

J’ai également corrigé quelques problèmes, en utilisant le plus souvent des smarts pointers (ComPtr) pour que le compteur de références soit mis à jour automatiquement, m’évitant ainsi d’appeler explicitement l’instruction Release() sur les objets créés.

La leçon à retenir, c’est que nous devons toujours être attentif lorsque nous Copions/Collons du code à partir d’Internet comme l’a fait Alain. Nous ne sommes jamais à l’abri d’un code partiellement erroné et qu’il faut toujours le prendre à titre d’exemple et autant que faire ce peux l’améliorer si besoin est. Vous connaissez le vieil adage, 100 fois sur le métier remettre son ouvrage, surtout en C++ ;-)

Merci encore à toi Alain de nous avoir ouvert les yeux sur ce point délicat.

retrouverez la nouvelle version du code dans le fichier WRLFromDesktop2.zip.

Téléchargement du code de démo.

Notre David Catuhe national, nous a concocté un superbe article sur la manière de capturer une photo à l’aide des APIS du Windows Runtime dans une application WPF.

How to use specific WinRT API from Desktop apps: capturing a photo using your webcam into a WPF app

Je ne reviens pas dessus, mais en substance, voici le code.

MediaCapture m_mediaCapture = new Windows.Media.Capture.MediaCapture();
        async void CapturePhotoAsync()
        {
            m_mediaCapture.Failed += (s, errorEventArgs) => MessageBox.Show("Impossible de dmarrer la capture :" +
                                                       errorEventArgs.Message, "Erreur", MessageBoxButton.OK);
            await m_mediaCapture.InitializeAsync();
           

            var jpgProperties = Windows.Media.MediaProperties.ImageEncodingProperties.CreateJpeg();
            jpgProperties.Width = (uint)displayImage.ActualWidth;
            jpgProperties.Height = (uint)displayImage.ActualHeight;           
            using (var randomAccessStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
            {
                await m_mediaCapture.CapturePhotoToStreamAsync(jpgProperties, randomAccessStream);
                randomAccessStream.Seek(0);
                using (System.IO.Stream ioStream = randomAccessStream.AsStream())
                {
                    var bitmapImage = new BitmapImage();
                    bitmapImage.BeginInit();
                    bitmapImage.StreamSource = ioStream;
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.EndInit();
                    displayImage.Source = bitmapImage;
                }
            }
        }

Cela fonctionne correctement, mais il y a quelques inconvénients :

  • il faut utiliser au minimum le Framework .NET 4.5.
  • cela ne fonctionne pas dans une application de bureau qui n’utilise pas le Framework .NET.
  • il est fort probable que certaines fonctionnalités ne soient pas disponibles.

Alors comment faire ?

L’idée est donc :

  • De construire une librairie C qui fera appel directement aux librairies du Windows Runtime en utilisant la Window Runtime C++ Template Librairie.
  • De créer une classe C# qui importe via l’attribut DLLImport les APIs de la librairie C.
  • De construire une application Windows Form par exemple, afin de vérifier que tout le bouzin fonctionne.

Création de la librairie C.

Dans Visual Studio 2013, c’est simple, il faut créer un nouveau projet Win32, que vous nommez LIBMediaCapture
image

Ensuite de choisir DLL comme Type d’application et d’exporter les symboles (Important si on souhaite les rendre visibles à l’extérieur de la librairie)

image

Visual Studio crée une coquille vide, comme illustré sur la figure suivante :

image

Dans le fichier LIBMediaCapture.h, nous allons y ajouter nos déclarations comme illustré dans l’extrait de code suivant :

#include <windows.media.devices.h>
#include <windows.foundation.h>
#include <windows.storage.streams.h>
#include <windows.storage.h>
#include <windows.devices.enumeration.h>
#include <windows.media.capture.h>

#include <windows.ui.xaml.controls.h>
#include <Windows.System.Threading.h>
#include <windows.media.mediaproperties.h>
#include <wrl\wrappers\corewrappers.h>
#include <wrl\client.h>
#include <wrl\event.h>
#include <wrl\async.h>
#include <wrl.h>

#include <mfapi.h>
#include <mfcaptureengine.h>

using namespace ABI::Windows::UI::Xaml::Controls;
using namespace ABI::Windows::System::Threading;
using namespace ABI::Windows::Storage;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Devices::Enumeration;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Media::Capture;
using namespace ABI::Windows::Media::MediaProperties;
using namespace ABI::Windows::Storage::Streams;
using namespace ABI::Windows::Media::Devices;
using namespace Microsoft::WRL::Wrappers;
using namespace Microsoft::WRL;

#ifdef LIBMEDIACAPTURE_EXPORTS
#define LIBMEDIACAPTURE_API __declspec(dllexport)
#else
#define LIBMEDIACAPTURE_API __declspec(dllimport)
#endif

typedef void(CALLBACK CBDELEGATE)(LPCWSTR id);
typedef void(CALLBACK CBCOMPLETED)(INT32 completed);
typedef void(CALLBACK CBCAPTURECOMPLETED)(BYTE *buffer,INT32 length);

extern "C" LIBMEDIACAPTURE_API BOOL InitWindowsRuntime(int threadingmodel);

extern "C" LIBMEDIACAPTURE_API void UnInitWindowsRuntime(void);

extern "C" LIBMEDIACAPTURE_API BOOL EnumDevices(int deviceClass, CBDELEGATE *cb);

extern "C" LIBMEDIACAPTURE_API BOOL InitializeAsync(CBCOMPLETED *cb);
extern "C" LIBMEDIACAPTURE_API BOOL GetDefaultVideoCapture();
extern "C" LIBMEDIACAPTURE_API BOOL CapturePhotoToStreamAsync(UINT32 width, UINT32 height, CBCAPTURECOMPLETED*cb);

ComPtr<IMediaCapture> m_MediaCapture;

EventRegistrationToken m_token;

extern "C" LIBMEDIACAPTURE_API BOOL StartPreviewAsync();

extern "C" LIBMEDIACAPTURE_API BOOL StopAsyncPreview();

extern "C" LIBMEDIACAPTURE_API BOOL StartPreviewAsync();

extern "C" LIBMEDIACAPTURE_API BOOL StopPreviewAsync();

extern "C" LIBMEDIACAPTURE_API BOOL StartRecordToStorageFileAsync();

extern "C" LIBMEDIACAPTURE_API BOOL StopRecordAsync();

Un petit mot s'impose.

Tout d’abord, nous avons besoin d’utiliser des librairies qui vont nous aider à instancier le Windows Runtime, à activer et instancier les différentes APIs du Windows Runtime. Pour ce faire nous y incluons les fichiers d’entêtes , wrl\wrappers\corewrappers.h, wrl\client.h, wrl\event.h etc..

Ensuite nous aurons besoin d’accéder aux classes du Windows Runtime telles que les classes MediaCapture, InMemoryStream, etc.. (Comme dans l’exemple de David) pour cela, nous incluons tous les fichiers d’entêtes nécessaires tels que windows.media.capture.h, windows.storage.streams.h etc.

De toute manière ces fichiers d’entêtes sont la seule documentation qui vous servira pour comprendre comment ça marche. Vous les retrouverez d’ailleurs dans le répertoire C:\Program Files (x86)\Windows Kits\8.1\Include\winrt.

La ligne #define LIBMEDIACAPTURE_API __declspec(dllexport) permet d’exporter les fonctions de la librairie et d’éviter de créer manuellement un fichier de définition .Def.

Ensuite, nous définissons deux fonctions de rappels (Callback) qui nous servirons dans notre projet C#.

   
typedef void(CALLBACK CBCOMPLETED)(INT32 completed);
typedef void(CALLBACK CBCAPTURECOMPLETED)(BYTE *buffer,INT32 length);

La fonction de rappel CBCOMPLETED nous permettra de savoir lorsque la caméra a fini d’être initialisée.

La fonction CBCAPTURECOMPLETED qui prend en paramètre un pointeur sur un tableau de bytes, tableau qui contiendra la photo capturée, et en second paramètre, la taille de la photo.

Note : Nous avons besoin de définir ces fonctions de rappel, car il faut bien garder à l’esprit que la pattern async/await, ni le mécanisme d’asynchronisme du Windows Runtime (IAsyncAction et IAsyncOperation), ne sont supportés par le Framework .NET 3.5.

Ensuite nous définissons nos différentes fonctions avec comme suffixe extern “C” afin que le nom de la fonction, ne soit pas décorée par un mangling superflu. (Ceci aura une importance lors de l’utilisation de l’attribut DLLImport avec C#)

image

Exemple de Mangling superflu si on oublie de mettre extern “C”

Note : Vérifiez également que votre librairie définisse également une convention d’appel __cdecl (/Gd) et non pas __stdcall. Enfin peut importe la convention que vous utilisez en faite, il faut simplement en être conscient lors de l’utilisation de l’attribut DllImport. Un outil tel que Dependency Walker pourra vous aider dans vos différents essais.

image

Exemple de Mangling en convention d’appel __stdcall

image

Exemple avec la convention d’appel __cdecl

Initialisation du Windows Runtime

Avant de pouvoir utiliser une classe du Windows Runtime, il faut initialiser le Windows Runtime, il est donc important d’ajouter une dépendance à la librairie runtimeobject.lib.

image

Puis d’utiliser la méthode RoInitialize avec le bon paramètre

LIBMEDIACAPTURE_API BOOL InitWindowsRuntime(int threadingmodel)
{
    HRESULT hr = S_OK;
    hr = RoInitialize((RO_INIT_TYPE) threadingmodel);
    if (FAILED(hr))
    {
        return FALSE;
    }
    return TRUE;
}

La méthode RoInitialize() prend comme paramètre 0 ou 1 en fonction du modèle de thread que vous souhaitez mettre en place. Comme on le verra par la suite si on veut être en multithread (RO_INIT_MULTITHREADED) il faudra changer un attribut dans notre application Windows Form.

RO_INIT_SINGLETHREADED = 0, // Single-threaded application
RO_INIT_MULTITHREADED = 1, // // COM calls objects on any thread.
Dans le code de David, ce n’est pas nécessaire, car le CLR (.NET) le fait automatiquement pour nous.

Release le Windows Runtime

LIBMEDIACAPTURE_API void UnInitWindowsRuntime()
{
    ::RoUninitialize();
}

Création de l’objet MediaCapture

Dans le code de David, nous avons la ligne :

MediaCapture m_mediaCapture = new Windows.Media.Capture.MediaCapture();

Qui se traduit par :

Code Snippet

  1. LIBMEDIACAPTURE_API BOOL GetDefaultVideoCapture()
  2. {
  3.     HRESULT hr = S_OK;
  4.     
  5.     //HSTRING hs = HStringReference(RuntimeClass_Windows_Media_Capture_MediaCapture).Get(); //Mauvaise construction     
  6.     hr = ::RoActivateInstance(HStringReference(RuntimeClass_Windows_Media_Capture_MediaCapture).Get(), &m_MediaCapture);
  7.  
  8.     if (FAILED(hr))
  9.     {
  10.         return FALSE;
  11.     }    
  12.     
  13.     m_MediaCapture->add_Failed(Callback<IMediaCaptureFailedEventHandler>([](IMediaCapture *sender, IMediaCaptureFailedEventArgs *e) ->HRESULT
  14.     {
  15.         HSTRING hs;
  16.           returne->get_Message(&hs);
  17.     }).Get(),&m_token);
  18.  
  19.     return TRUE;
  20. }

Tout d’abord, il faut récupérer la chaine définissant le activeTableClassId de notre classe MediaCapture. En gros une chaine de caractères du style "Windows.Media.Capture.MediaCapture" et la convertir dans le type HSTRING (en utilisant la class HStringReference) . Le Windows Runtime n’utilise que ce type de chaine de caractères qui n’est pas le type String de C# ni le type String^ de C++/CX. Pour ce faire

Ensuite nous activons une instance de la classe en lui passant un pointeur sur l’interface IMediaCapture qui doit dériver forcement de l’interface IInspectable. Vous noterez que nous utilisons ComPtr qui est un smart pointer sur la variable m_MediaCapture, et permettra de maintenir automatiquement un compteur de référence sur le pointeur.

             Une fois que nous avons activé notre MediaCapture, il faut initialiser le périphérique. 

Dans le code de David, nous avons : 

await m_mediaCapture.InitializeAsync();

                

  Qui se traduit par :

LIBMEDIACAPTURE_API BOOL InitializeAsync(CBCOMPLETED *cb)
{
    HRESULT hr = S_OK;
    if (nullptr == m_MediaCapture) return FALSE;
    ComPtr<ABI::Windows::Foundation::IAsyncAction> asyncOp;
    hr = m_MediaCapture->InitializeAsync(&asyncOp);
    if (FAILED(hr)) return FALSE;
    hr=asyncOp->put_Completed(Callback<IAsyncActionCompletedHandler>([cb](IAsyncAction *asyncOp,
                                                                   AsyncStatus statusFile) ->HRESULT
    {
        HRESULT hr = S_OK;        
        if (cb) cb(static_cast<INT32>(statusFile));
        
        return hr;        
    }).Get());    
    if (FAILED(hr)) return FALSE;
    
    return TRUE;
}

La fonction InitializeAsync de notre librairie, prend comme paramètre un pointeur sur une méthode de rappel, car nous aurons besoin de savoir quand l’initialisation est terminée. Rappelez-vous pas de pattern async/await en .NET 3.5.

Ensuite nous appelons la méthode InitializeAsync() de l’interface IMediaCapture par l’intermédiaire du pointeur m_MediaCapture, en lui passant comme paramètre l’adresse d’un pointeur de type IAsyncAction ( asyncOp ) qui nous permettra de savoir quand l’opération est terminée.

Pour ce faire, nous allons lui pousser à l’aide de la méthode put_Completed() de l’interface IAsyncAction, une méthode de rappel. Merci à la classe Callback<> qui permet de simplifier l’écriture, enfin je crois Sourire.

Ici j’utilise une expression lambda qui sera appelée lorsque l’initialisation de la caméra sera terminée. Vous noterez que dans cette expression lambda, je fait appel à ma méthode CBCOMPLETED *cb qui permettra d’avertir le client en lui passant le statut de l’appel.

Une fois initialisé, nous pouvons capturer une photo.

Dans le code de David nous avons :

var jpgProperties = Windows.Media.MediaProperties.ImageEncodingProperties.CreateJpeg();
            jpgProperties.Width = (uint)displayImage.ActualWidth;
            jpgProperties.Height = (uint)displayImage.ActualHeight;           
            using (var randomAccessStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
            {
                await m_mediaCapture.CapturePhotoToStreamAsync(jpgProperties, randomAccessStream);
                randomAccessStream.Seek(0);

Qui se traduit par :

LIBMEDIACAPTURE_API BOOL CapturePhotoToStreamAsync(UINT32 width,
                                                   UINT32 height,
                                                   CBCAPTURECOMPLETED*cb)
{
    if (nullptr == m_MediaCapture) return FALSE;
    HRESULT hr = S_OK;
    
    ComPtr<IImageEncodingPropertiesStatics> ImageEncodingProperties;

    hr = ::GetActivationFactory(HStringReference(RuntimeClass_Windows_Media_MediaProperties_ImageEncodingProperties).Get(), &ImageEncodingProperties);
    
    if (FAILED(hr)) return FALSE;
    ComPtr<IImageEncodingProperties> MediaProperties;
    hr=ImageEncodingProperties->CreateJpeg(&MediaProperties);
    
    if (FAILED(hr)) return FALSE;
    hr=MediaProperties->put_Height(height);
    if (FAILED(hr)) return FALSE;
    hr=MediaProperties->put_Width(width);
    if (FAILED(hr)) return FALSE;

    
    ComPtr<IRandomAccessStream> InMemoryRandomStreams;
    hr = ::RoActivateInstance(HStringReference(RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream).Get(), &InMemoryRandomStreams);
    if (FAILED(hr)) return FALSE;    
    ComPtr<IAsyncAction> asyncOp;
    hr = m_MediaCapture->CapturePhotoToStreamAsync(MediaProperties.Get(), InMemoryRandomStreams.Get(), &asyncOp);
    if (FAILED(hr)) return FALSE;

    hr=asyncOp->put_Completed(Callback<IAsyncActionCompletedHandler>([InMemoryRandomStreams,cb](IAsyncAction *asyncOp, AsyncStatus statusFile) ->HRESULT
    {
        HRESULT hr = S_OK;
        //ULONG count = asyncOp->Release();
        hr = InMemoryRandomStreams->Seek(0);
        if (FAILED(hr)) return hr;
        UINT64 size;
        hr = InMemoryRandomStreams->get_Size(&size);
        if (FAILED(hr)) return hr;
        boolean CanRead;
        hr=InMemoryRandomStreams->get_CanRead(&CanRead);
        if (FAILED(hr)) return hr;
        
        ComPtr<IInputStream> InputStream;
                
        hr = InMemoryRandomStreams->GetInputStreamAt(0, &InputStream);
        if (FAILED(hr)) return hr;
        ComPtr<IDataReaderFactory> DataReaderFactory;
        ComPtr<IDataReader> DataReader;
        
        hr = ::GetActivationFactory(HStringReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(), &DataReaderFactory);
        if (FAILED(hr)) return hr;
        hr=DataReaderFactory->CreateDataReader(InputStream.Get(), &DataReader);
        if (FAILED(hr)) return hr;
        ComPtr<IAsyncOperation<UINT32>> op;
        hr = DataReader->LoadAsync(static_cast<UINT32>(size), &op);
        if (FAILED(hr)) return hr;

        hr=op->put_Completed(Callback<IAsyncOperationCompletedHandler<UINT32>>([DataReader,size,cb](IAsyncOperation<UINT32>*ops, AsyncStatus status)->HRESULT
        {
            HRESULT hr = S_OK;
            //ULONG count = ops->Release();
            BYTE *buffer = new BYTE[(UINT32) size]();
            
            hr = DataReader->ReadBytes(static_cast<UINT32>(size), buffer);
            if (FAILED(hr)) return hr;
            if (cb) cb(buffer,size);
            delete [] buffer;
            return hr;
        }).Get());
        
        return hr;
    }).Get());

    if (FAILED(hr)) return FALSE;
    
    return TRUE;
}

Je ne vais pas tout détailler car le principe reste le même, mais la méthode CapturePhotoToStreamAsync() prend comme paramètre entre autre un pointeur sur une méthode de rappel typedef void(CALLBACK CBCAPTURECOMPLETED)(BYTE *buffer,INT32 length) qui retourne un pointeur sur un tableau de bytes, et que je retourne à l’appelant (voir dans l’extrait de code ci-dessus l’appel op->put_Completed(Callback<>…..).

Voila en gros le principe et la manière d’instancier des classes du Windows Runtime.

Passons maintenant à la classe C# qui permet d’importer les fonctions de la librairie.

Classe C# MediaCapture.

class MediaCapture
    {
       
       
        public delegate void EnumHDevicesHandler([Out, MarshalAs(UnmanagedType.LPWStr)]String  s);
        public delegate void InitializeCompletedHandler(int completed);
        public delegate void CaptureCompletedHandler(IntPtr ptrBuffer, int length);

        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Boolean StartPreviewAsync();
        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Boolean StopPreviewAsync();

        //[DllImport("LIBMediaCapture.dll", EntryPoint="_StopRecordAsync@0", CallingConvention = CallingConvention.StdCall)]
        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Boolean StopRecordAsync();
        //[DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.StdCall)]
        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Boolean StartRecordToStorageFileAsync();

        //[DllImport("LIBMediaCapture.dll", EntryPoint = "_CapturePhotoToStreamAsync@12", CallingConvention = CallingConvention.StdCall)]
        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Boolean CapturePhotoToStreamAsync(uint width,uint height,IntPtr ptr);

        //[DllImport("LIBMediaCapture.dll", EntryPoint = "_InitializeAsync@4", CallingConvention = CallingConvention.StdCall)]
        [DllImport("LIBMediaCapture.dll",EntryPoint="InitializeAsync", CallingConvention = CallingConvention.Cdecl)]       
        public static extern Boolean InitializeAsync(IntPtr cb) ;

        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Boolean EnumDevices(int deviceClass, IntPtr cb);

        //[DllImport("LIBMediaCapture.dll", EntryPoint = "_GetDefaultVideoCapture@0", CallingConvention = CallingConvention.StdCall)]
        [DllImport("LIBMediaCapture.dll", EntryPoint = "GetDefaultVideoCapture", CallingConvention = CallingConvention.Cdecl)]       
        public static extern Boolean GetDefaultVideoCapture();

        //[DllImport("LIBMediaCapture.dll", EntryPoint="_InitWindowsRuntime@4", CallingConvention=CallingConvention.StdCall)]
        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern Boolean InitWindowsRuntime(int threadingmodel);

        //[DllImport("LIBMediaCapture.dll",EntryPoint="_UnInitWindowsRuntime@0", CallingConvention = CallingConvention.StdCall)]
        [DllImport("LIBMediaCapture.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void UnInitWindowsRuntime();   
    }

Rien de bien méchant, si ce n’est qu’il faut bien penser :

- à la convention d’appel, (il faut que cela soit la même entre la librairie C et .NET).

- déclarer des délégués qui doivent avoir la même signature que nos fonctions de rappel définies dans la librairie.
Comme par exemple :
public delegate void InitializeCompletedHandler(int completed);
public delegate void CaptureCompletedHandler(IntPtr ptrBuffer, int length)

- que pour un pointeur de fonction de rappel, il faut utiliser le type IntPtr.

Attaquons nous maintenant au client Windows Form en C#

Dans un nouveau projet Windows Form, ajoutez-y un bouton et une PictureBox.

puis sur l’évènement click du bouton, ajoutez le code suivant :

private void cmdInitMediaCapture_Click(object sender, EventArgs e)
        {
           
            try
            {
                if (!MediaCapture.InitWindowsRuntime((int)RoThreadingModel.ROINITMULTITHREADED))
                {
                    MessageBox.Show("Impossible d'intialiser le Windows Runtime");
                    return;
                }
                               
              
                var b = MediaCapture.GetDefaultVideoCapture();
                if (b)
                {
                    IntPtr ptr = Marshal.GetFunctionPointerForDelegate(new CSWF.MediaCapture.InitializeCompletedHandler((int statusInfo) =>
                        {
                            //Synchronisation du thread avec l'objet SynchronizationContext
                            m_context.Post(new System.Threading.SendOrPostCallback((o) =>
                            {
                               
                                if ((AsyncStatus)statusInfo==AsyncStatus.Completed)
                                {
                                    IntPtr ptrCapture = Marshal.GetFunctionPointerForDelegate(new CSWF.MediaCapture.CaptureCompletedHandler(
                                        (IntPtr ptrBuffer,int length) =>
                                        {
                                       
                                            Byte[] destination=new Byte[length];
                                            Marshal.Copy(ptrBuffer, destination, 0, length);
                                          
                                            MemoryStream ms = new MemoryStream(destination);
                                            ms.Position = 0;
                                            Bitmap bmp = new Bitmap(ms);
                                            pictureBox1.Image = bmp;

                                        }));

                                    var result = MediaCapture.CapturePhotoToStreamAsync((uint)pictureBox1.Width,(uint)pictureBox1.Height,ptrCapture);

                                
                                }

                            }), null);
                        }));
                  b = MediaCapture.InitializeAsync(ptr);                   
                }

            }
            catch (Exception ex)
            {

                MessageBox.Show(ex.Message);
            }
        }

Ici nous initialisons le Windows Runtime en mode multi-thread, il est donc impératif de modifier le modèle de thread de démarrage de l’application Windows Form et de le passer du mode STAThread en MTAThread, comme illustré dans l’extrait de code suivant :

[MTAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }

Ensuite, l’astuce consiste à utiliser la méthode statique GetFunctionPointeurForDelegate de la classe Marshal (Espace de nom System.Runtime.InteropServices), afin de convertir un délégué en IntPtr, pointeur que l’on pourra passer à notre fonction, car le CLR sera l’utiliser à bon escient.

Ne pas oublier aussi de synchroniser le thread de l’appelé (la librairie C) avec le thread principal, celui qui a crée l’objet Image (pictureBox1) avec l’objet SynchronizationContext et le tour est joué. Vous avez le droit à une vraie tête de vainqueur !!!

image

Téléchargement du code de démo.

Vous noterez que dans le code de démo, il y a une méthode StartRecordToStorageFileAsync() , qui permet de déclencher l’enregistrement d’une vidéo et qui fonctionne, elle sauvegarde une vidéo (Movie.mp4) dans la librairie Vidéo.

Pour être complet, reste à implémenter maintenant la capture et l’affichage de la vidéo ce que fait automatiquement l’objet XAML CaptureElement du Windows Runtime dans une application moderne, mais cela ne semble pas être très trivial.

Restez à l’écoute je reviendrais vers vous je l’espère prochainement avec de nouveaux éléments.

Eric