Partager via


Création d’une application de conversation de groupe

Cette rubrique fournit des exemples de code pertinents pour les principales étapes du développement d’une application de conversation avec l’API De regroupement d’homologues, en fournissant un contexte pour chaque appel d’API. Les comportements de l’interface utilisateur et la structure globale de l’application ne sont pas inclus.

Notes

L’exemple complet d’application Peer Group Chat est fourni dans le Kit de développement logiciel (SDK) peer. Cette rubrique fait référence aux fonctions fournies dans l’exemple.

 

Initialisation d’un groupe

La première étape lors de la construction d’une application de conversation consiste à initialiser l’infrastructure de regroupement d’homologues en appelant PeerGroupStartup avec la version prise en charge la plus élevée. Dans ce cas, PEER_GROUP_VERSION sera défini dans le fichier d’en-tête de l’application. La version prise en charge par l’infrastructure est retournée dans peerVersion.

    PEER_VERSION_DATA peerVersion;

    hr = PeerGroupStartup(PEER_GROUP_VERSION, &peerVersion);
    if (FAILED(hr))
    {
        return hr;
    }

Création d’un groupe

Une application de conversation doit être en mesure de créer un groupe d’homologues si aucun groupe n’est disponible pour rejoindre ou si l’utilisateur de l’application souhaite en créer un nouveau. Pour ce faire, vous devez créer une structure PEER_GROUP_PROPERTIES et la remplir avec les paramètres initiaux du groupe, y compris le classifieur pour le groupe d’homologues, le nom convivial, le nom d’homologue du créateur et la durée de vie de présence. Une fois cette structure remplie, vous la passez à PeerGroupCreate.

//-----------------------------------------------------------------------------
// Function: CreateGroup
//
// Purpose:  Creates a new group with the friendly name.
//
// Returns:  HRESULT
//
HRESULT CreateGroup(PCWSTR pwzName, PCWSTR pwzIdentity)
{
    HRESULT hr = S_OK;
    PEER_GROUP_PROPERTIES props = {0};

    if (SUCCEEDED(hr))
    {
        if ((NULL == pwzName) || (0 == *pwzName))
        {
            hr = E_INVALIDARG;
            DisplayHrError(L"Please enter a group name.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        CleanupGroup( );

        props.dwSize = sizeof(props);
        props.pwzClassifier = L"SampleChatGroup";
        props.pwzFriendlyName = (PWSTR) pwzName;
        props.pwzCreatorPeerName = (PWSTR) pwzIdentity;

        hr = PeerGroupCreate(&props, &g_hGroup);
        if (FAILED(hr))
        {
            DisplayHrError(L"Failed to create a new group.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = PrepareToChat( );
    }

    return hr;
}

Émission d’invitations

Lors de l’émission d’une invitation, les GMC des invités doivent être obtenus. Ceux-ci peuvent être obtenus en appelant PeerIdentityGetXML sur l’invité avec son nom d’identité. En cas de réussite, les informations d’identité (le code XML qui contient le certificat encodé en base 64 avec la clé publique RSA) sont écrites dans un emplacement externe (un fichier, dans cet exemple) où l’invité peut les obtenir et les utiliser pour créer une invitation.

À ce stade, un mécanisme de remise de l’invitation à l’invité doit être établi. Il peut s’agir d’un e-mail ou d’une autre méthode sécurisée d’échange de fichiers. Dans l’exemple ci-dessous, l’invitation est écrite dans un fichier qui peut ensuite être transféré sur l’ordinateur de l’invité.

//-----------------------------------------------------------------------------
// Function: SaveIdentityInfo
//
// Purpose:  Saves the information for an identity to a file.
//           Displays a message if there was an error.
//
// Returns:  HRESULT
//
HRESULT SaveIdentityInfo(PCWSTR pwzIdentity, PCWSTR pwzFile)
{
    PWSTR pwzXML = NULL;
    HRESULT hr = PeerIdentityGetXML(pwzIdentity, &pwzXML);

    if (FAILED(hr))
    {
        DisplayHrError(L"Unable to retrieve the XML data for the identity.", hr);
    }
    else
    {
        FILE *fp = NULL;
        errno_t err = 0;

        err = _wfopen_s(&fp, pwzFile, L"wb");
        if (err != 0)
        {
            hr = E_FAIL;
            DisplayHrError(L"Please choose a valid path", hr);
        }
        else
        {
            if (fputws(pwzXML, fp) == WEOF)
            {
                hr = E_FAIL;
                DisplayHrError(L"End of file error.", hr);
            }
            fclose(fp);
        }

        PeerFreeData(pwzXML);
    }

    return hr;
}

Les invitations, comme les identités, sont également émises en externe. Dans cet exemple, l’invitation est écrite dans un fichier avec des fputws où l’invité peut l’obtenir et l’utiliser lorsqu’il appelle PeerGroupJoin.

//-----------------------------------------------------------------------------
// Function: CreateInvitation
//
// Purpose:  Creates an invitation file for an identity.
//           Displays a message if there was an error.
//
// Returns:  HRESULT
//
HRESULT CreateInvitation(PCWSTR wzIdentityInfoPath, PCWSTR wzInvitationPath)
{
    HRESULT hr = S_OK;
    WCHAR wzIdentityInfo[MAX_INVITATION] = {0};
    PWSTR pwzInvitation = NULL;
    errno_t  err  = 0;
    FILE *file = NULL;
        
    err = _wfopen_s(&file, wzIdentityInfoPath, L"rb");
    if (err != 0)
    {
        hr = E_FAIL;
        DisplayHrError(L"Please choose a valid path to the identity information file.", hr);
    }
    else
    {
        fread(wzIdentityInfo, sizeof(WCHAR), MAX_INVITATION, file);
        if (ferror(file))
        {
            hr = E_FAIL;
            DisplayHrError(L"File read error occurred.", hr);
        }
        fclose(file);
    }

    if (SUCCEEDED(hr))
    {
        ULONGLONG ulExpire; // adjust time using this structure
        GetSystemTimeAsFileTime((FILETIME *)&ulExpire);

        // 15days in 100 nanoseconds resolution
        ulExpire += ((ULONGLONG) (60 * 60 * 24 * 15)) * ((ULONGLONG)1000*1000*10);

        hr = PeerGroupCreateInvitation(g_hGroup, wzIdentityInfo, (FILETIME*)&ulExpire, 1, (PEER_ROLE_ID*) &PEER_GROUP_ROLE_MEMBER, &pwzInvitation);
        if (FAILED(hr))
        {
            DisplayHrError(L"Failed to create the invitation.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        err = _wfopen_s(&file, wzInvitationPath, L"wb");
        if (err != 0)
        {
            hr = E_FAIL;
            DisplayHrError(L"Please choose a valid path to the invitation file.", hr);
        }
        else
        {
            if (fputws(pwzInvitation, file) == WEOF)
            {
                hr = E_FAIL;
                DisplayHrError(L"End of file error.", hr);
            }
            fclose(file);
        }
    }

    PeerFreeData(pwzInvitation);
    return hr;
}

Jonction d’un groupe d’homologues

Si l’homologue tente de rejoindre un groupe d’homologues créé par un autre homologue, il aura besoin d’une invitation de cet homologue. Les invitations sont remises par un processus externe ou une application à l’invité et enregistrées dans un fichier local, spécifié dans l’exemple ci-dessous sous la forme pwzFileName. L’objet blob XML d’invitation est lu à partir du fichier dans wzInvitation et passé à PeerGroupJoin.

//-----------------------------------------------------------------------------
// Function: JoinGroup
//
// Purpose:  Uses the invitation to join a group with a specific identity.
//           Displays a message if there was an error.
//
// Returns:  HRESULT
//
HRESULT JoinGroup(PCWSTR pwzIdentity, PCWSTR pwzFileName)
{
    HRESULT hr = S_OK;
    WCHAR wzInvitation[MAX_INVITATION] = {0};
    FILE        *file = NULL;
    errno_t     err;

    err = _wfopen_s(&file, pwzFileName, L"rb");
    if (err !=  0)
    {
        hr = E_FAIL;
        DisplayHrError(L"Error opening group invitation file", hr);
        return hr;
    }
    else
    {
        fread(wzInvitation, sizeof(WCHAR), MAX_INVITATION, file);
        if (ferror(file))
        {
            hr = E_FAIL;
            DisplayHrError(L"File read error occurred.", hr);
        }
        fclose(file);

        hr = PeerGroupJoin(pwzIdentity, wzInvitation, NULL, &g_hGroup);
        if (FAILED(hr))
        {
            DisplayHrError(L"Failed to join group.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = PrepareToChat( );
    }

    return hr;
}

S’inscrire aux événements d’homologue

Avant de vous connecter, vous devez vous inscrire à chaque événement d’homologue pertinent pour l’application. Dans l’exemple ci-dessous, vous vous inscrivez pour les événements suivants :

  • PEER_GROUP_EVENT_RECORD_CHANGED. Étant donné que les enregistrements sont utilisés pour contenir des messages de conversation publics, l’application doit être avertie chaque fois qu’un nouveau est ajouté. Lorsque cet événement d’homologue est reçu, les données d’événement exposent l’enregistrement avec le message de conversation. Les applications ne doivent s’inscrire que pour les types d’enregistrements qu’elles ont l’intention de gérer directement.
  • PEER_GROUP_EVENT_MEMBER_CHANGED. L’application doit être avertie lorsque des membres rejoignent ou quittent le groupe d’homologues afin que la liste des participants puisse être mise à jour en conséquence.
  • PEER_GROUP_EVENT_STATUS_CHANGED. Les modifications apportées au groupe d’homologues status doivent être transmises à l’application. Un membre du groupe d’homologues n’est considéré comme disponible au sein du groupe d’homologues que lorsque son status indique qu’il est connecté au groupe, synchronisé avec la base de données d’enregistrements du groupe d’homologues et qu’il écoute activement les mises à jour des enregistrements.
  • PEER_GROUP_EVENT_DIRECT_CONNECTION. Les messages privés entre deux membres et les échanges de données doivent être effectués via une connexion directe, de sorte que l’application doit être en mesure de gérer les demandes de connexion directe.
  • PEER_GROUP_EVENT_INCOMING_DATA. Après le lancement d’une connexion directe, cet événement d’homologue avertit l’application qu’un message privé a été reçu.
//-----------------------------------------------------------------------------
// Function: RegisterForEvents
//
// Purpose:  Registers the EventCallback function so it will be called for only
//           those events that are specified.
//
// Returns:  HRESULT
//
HRESULT RegisterForEvents(void)
{
    HRESULT hr = S_OK;
    PEER_GROUP_EVENT_REGISTRATION regs[] = {
        { PEER_GROUP_EVENT_RECORD_CHANGED, &RECORD_TYPE_CHAT_MESSAGE },
        { PEER_GROUP_EVENT_MEMBER_CHANGED, 0 },
        { PEER_GROUP_EVENT_STATUS_CHANGED, 0 },
        { PEER_GROUP_EVENT_DIRECT_CONNECTION, &DATA_TYPE_WHISPER_MESSAGE },
        { PEER_GROUP_EVENT_INCOMING_DATA, 0 },
    };

    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (g_hEvent == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
    else
    {
        hr = PeerGroupRegisterEvent(g_hGroup, g_hEvent, celems(regs), regs,  &g_hPeerEvent);
    }

    if (SUCCEEDED(hr))
    {
        if (!RegisterWaitForSingleObject(&g_hWait, g_hEvent, EventCallback, NULL, INFINITE, WT_EXECUTEDEFAULT))
        {
            hr = E_UNEXPECTED;
        }
    }

    return hr;
}

Connexion à un groupe d’homologues

Après avoir créé ou rejoint le groupe et inscrit pour les événements appropriés, il est temps d’accéder en ligne et de commencer une session de conversation active. Pour ce faire, vous appelez PeerGroupConnect et transmettez le handle de groupe obtenu à partir de PeerGroupCreate ou PeerGroupJoin. Après l’appel de ce message, les messages de conversation sont reçus en tant qu’événements PEER_GROUP_EVENT_RECORD_CHANGED.

Obtention d’une liste de membres du groupe d’homologues

L’obtention d’une liste de membres connectés au groupe d’homologues est simple : appelez PeerGroupEnumMembers pour récupérer la liste des membres du groupe, puis appelez de manière itérative PeerGetNextItem jusqu’à ce que tous les membres soient récupérés. Vous devez appeler PeerFreeData après avoir traité chaque structure de membre, et vous devez fermer l’énumération en appelant PeerEndEnumeration une fois le traitement terminé.

//-----------------------------------------------------------------------------
// Function: UpdateParticipantList
//
// Purpose:  Update the list of partipants.
//
// Returns:  nothing
//
void UpdateParticipantList(void)
{
    HRESULT          hr = S_OK;
    HPEERENUM        hPeerEnum = NULL;
    PEER_MEMBER   ** ppMember = NULL;

    ClearParticipantList( );
    if (NULL == g_hGroup)
    {
        return;
    }

    // Retrieve only the members currently present in the group.
    hr = PeerGroupEnumMembers(g_hGroup, PEER_MEMBER_PRESENT, NULL, &hPeerEnum);
    if (SUCCEEDED(hr))
    {
        while (SUCCEEDED(hr))
        {
            ULONG cItem = 1;
            hr = PeerGetNextItem(hPeerEnum, &cItem, (PVOID **) &ppMember);
            if (SUCCEEDED(hr))
            {
                if (0 == cItem)
                {
                    PeerFreeData(ppMember);
                    break;
                }
            }

            if (SUCCEEDED(hr))
            {
                if (0 != ((*ppMember)->dwFlags & PEER_MEMBER_PRESENT))
                {
                    AddParticipantName((*ppMember)->pwzIdentity, (*ppMember)->pCredentialInfo->pwzFriendlyName);
                }
                PeerFreeData(ppMember);
            }
        }

        PeerEndEnumeration(hPeerEnum);
    }
}

Envoi d’un message de conversation

Dans cet exemple, un message de conversation est envoyé en le plaçant dans le champ de données d’une structure de PEER_RECORD . L’enregistrement est ajouté aux enregistrements de groupe d’homologues en appelant PeerGroupAddRecord, qui le publiera et déclenchera l’événement PEER_GROUP_EVENT_RECORD_CHANGED sur tous les homologues participant au groupe d’homologues.

//-----------------------------------------------------------------------------
// Function: AddChatRecord
//
// Purpose:  This adds a new chat message record to the group.
//
// Returns:  HRESULT
//
HRESULT AddChatRecord(PCWSTR pwzMessage)
{
    HRESULT     hr = S_OK;
    PEER_RECORD record = {0};
    GUID        idRecord;
    ULONGLONG   ulExpire;
    ULONG cch = (ULONG) wcslen(pwzMessage);

    GetSystemTimeAsFileTime((FILETIME *) &ulExpire);

    // Calculate a 2 minute expiration time in 100 nanosecond resolution
    ulExpire += ((ULONGLONG) 60 * 2) * ((ULONGLONG)1000*1000*10);

    // Set up the record
    record.dwSize = sizeof(record);
    record.data.cbData = (cch+1) * sizeof(WCHAR);
    record.data.pbData = (PBYTE) pwzMessage;
    memcpy(&record.ftExpiration, &ulExpire, sizeof(ulExpire));

    PeerGroupUniversalTimeToPeerTime(g_hGroup, &record.ftExpiration, &record.ftExpiration);

    // Set the record type GUID
    record.type = RECORD_TYPE_CHAT_MESSAGE;

    // Add the record to the database
    hr = PeerGroupAddRecord(g_hGroup, &record, &idRecord);
    if (FAILED(hr))
    {
        DisplayHrError(L"Failed to add a chat record to the group.", hr);
    }

    return hr;
}

Réception d’un message de conversation

Pour recevoir le message de conversation, créez une fonction de rappel pour l’événement PEER_GROUP_EVENT_RECORD_CHANGED qui appelle une fonction similaire à celle ci-dessous. L’enregistrement est obtenu en appelant PeerGroupGetRecord sur les données d’événement reçues par un appel précédent à PeerGroupGetEventData dans la fonction de rappel. Le message de conversation est stocké dans le champ de données de cet enregistrement.

//-----------------------------------------------------------------------------
// Function: ProcessRecordChanged
//
// Purpose:  Processes the PEER_GROUP_EVENT_RECORD_CHANGED event.
//
// Returns:  nothing
//
void ProcessRecordChanged(PEER_EVENT_RECORD_CHANGE_DATA * pData)
{
    switch (pData->changeType)
    {
        case PEER_RECORD_ADDED:
            if (IsEqualGUID(&pData->recordType, &RECORD_TYPE_CHAT_MESSAGE))
            {
                PEER_RECORD * pRecord = {0};
                HRESULT hr = PeerGroupGetRecord(g_hGroup, &pData->recordId, &pRecord);
                if (SUCCEEDED(hr))
                {
                    DisplayChatMessage(pRecord->pwzCreatorId, (PCWSTR) pRecord->data.pbData);
                    PeerFreeData(pRecord);
                }
            }
            break;

        case PEER_RECORD_UPDATED:
        case PEER_RECORD_DELETED:
        case PEER_RECORD_EXPIRED:
            break;

        default:
            break;
    }
}