> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 实体句柄

> 在 PlayFab C SDK 中管理 PFEntityHandle 凭据、引用计数以及经过身份验证的玩家和游戏调用的实体令牌生存期。

**PFEntityHandle** 是你进行 PlayFab 服务调用的主要凭据。它表示已验证的实体——一个玩家 (title\_player\_account) 或一个游戏——并保存 SDK 授权请求所需的实体令牌。每次登录或身份验证调用都会返回 **PFEntityHandle**,并且每次服务调用都需要它。

## 获取实体句柄

你不能直接创建实体句柄。相反,你可以通过成功的登录或身份验证调用的输出获得句柄。

**玩家登录 (Windows 示例):**

```cpp theme={null}
PFAuthenticationLoginWithXUserRequest request{};
request.createAccount = true;
request.user = userHandle; // XUserHandle from XUserAddAsync

XAsyncBlock async{};
HRESULT hr = PFAuthenticationLoginWithXUserAsync(serviceConfigHandle, &request, &async);
hr = XAsyncGetStatus(&async, true);

PFEntityHandle entityHandle{ nullptr };
size_t bufferSize{};
hr = PFAuthenticationLoginWithXUserGetResultSize(&async, &bufferSize);

std::vector<char> loginResultBuffer(bufferSize);
PFAuthenticationLoginResult const* loginResult{};
hr = PFAuthenticationLoginWithXUserGetResult(
    &async, &entityHandle,
    loginResultBuffer.size(), loginResultBuffer.data(),
    &loginResult, nullptr);
```

**游戏实体 (服务器):**

游戏实体使用密钥而不是用户凭据进行身份验证。返回的 **PFEntityHandle** 工作方式相同,但它表示游戏而不是玩家。有关详细信息,请参阅[使用游戏实体访问 PlayFab](/services/playfab/sdks/c/server)。

## 句柄所有权和引用计数

**PFEntityHandle** 是引用计数的。当你从登录调用接收句柄时,你拥有一个引用。你可以使用 [**PFEntityDuplicateHandle**](/services/playfab/api-references/c/pfentity/functions/pfentityduplicatehandle) 创建其他引用,并使用 [**PFEntityCloseHandle**](/services/playfab/api-references/c/pfentity/functions/pfentityclosehandle) 释放它们。仅当关闭最后一个引用时,底层实体对象才会被销毁。

**规则:**

* 对登录 `GetResult` 函数或 **PFEntityDuplicateHandle** 的每次调用都会为你提供一个必须关闭的句柄。
* 关闭句柄不会使指向同一实体的其他句柄失效。
* 在调用 [**PFServicesUninitializeAsync**](/services/playfab/api-references/c/pfservices/functions/pfservicesuninitializeasync) 之前关闭所有句柄。

```cpp theme={null}
// Duplicate a handle for another component
PFEntityHandle secondHandle{ nullptr };
HRESULT hr = PFEntityDuplicateHandle(entityHandle, &secondHandle);

// Both handles are independently valid
// ...

// Each owner closes their own handle
PFEntityCloseHandle(secondHandle);
PFEntityCloseHandle(entityHandle);
```

## 获取实体信息

### 实体键

实体键标识实体(它的类型和 ID)。使用两个调用模式:首先获取大小,然后获取数据。

```cpp theme={null}
size_t size{};
HRESULT hr = PFEntityGetEntityKeySize(entityHandle, &size);

std::vector<char> buffer(size);
PFEntityKey const* entityKey{};
hr = PFEntityGetEntityKey(entityHandle, buffer.size(), buffer.data(), &entityKey, nullptr);

// entityKey->type is "title_player_account" for players
// entityKey->id is the entity's unique ID
```

### 实体令牌

实体令牌授权服务调用。SDK 自动管理令牌,但如果需要,你可以检索当前令牌。

```cpp theme={null}
XAsyncBlock async{};
HRESULT hr = PFEntityGetEntityTokenAsync(entityHandle, &async);
hr = XAsyncGetStatus(&async, true);

size_t size{};
hr = PFEntityGetEntityTokenResultSize(&async, &size);

std::vector<char> buffer(size);
const PFEntityToken* entityToken{};
hr = PFEntityGetEntityTokenResult(&async, buffer.size(), buffer.data(), &entityToken, nullptr);

// entityToken->token is the token string
// entityToken->expiration is the optional expiration time (UTC)
```

### 检查实体类型

使用 [**PFEntityIsTitlePlayer**](/services/playfab/api-references/c/pfentity/functions/pfentityistitleplayer) 作为快速检查,而不是检查实体键类型字符串:

```cpp theme={null}
bool isTitlePlayer{};
HRESULT hr = PFEntityIsTitlePlayer(entityHandle, &isTitlePlayer);
```

## 玩家实体与游戏实体

玩家实体和游戏实体都使用 **PFEntityHandle**,但它们的功能有所不同:

|                                                                                                          | 玩家实体                                           | 游戏实体                                          |
| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | --------------------------------------------- |
| **如何获取**                                                                                                 | 登录调用(例如 `PFAuthenticationLoginWithXUserAsync`) | `PFAuthenticationGetEntityWithSecretKeyAsync` |
| **实体类型**                                                                                                 | `title_player_account`                         | `title`                                       |
| [**PFEntityIsTitlePlayer**](/services/playfab/api-references/c/pfentity/functions/pfentityistitleplayer) | 返回 `true`                                      | 返回 `false`                                    |
| [**PFEntityGetSecretKey**](/services/playfab/api-references/c/pfentity/functions/pfentitygetsecretkey)   | 失败并返回 `E_PF_NOSECRETKEY`                       | 返回密钥                                          |
| **可用 API**                                                                                               | 带 Client 前缀的和不带前缀的 API                         | 带 Server 前缀的和不带前缀的 API                        |

不带前缀的 API(例如 Inventory、Leaderboards、Data)通常可以由玩家实体和游戏实体调用,除非其文档中另有说明。带 Client 前缀的 API 仅限玩家,带 Server 前缀的 API 仅限游戏。

**PFEntityGetSecretKey** 检索与游戏实体关联的密钥。它对玩家实体失败,因为它们没有密钥。

```cpp theme={null}
size_t keySize{};
HRESULT hr = PFEntityGetSecretKeySize(entityHandle, &keySize);
if (SUCCEEDED(hr))
{
    std::vector<char> secretKey(keySize);
    hr = PFEntityGetSecretKey(entityHandle, secretKey.size(), secretKey.data(), nullptr);
}
```

<Note>
  **PFEntityGetSecretKey** 仅在 Windows、Linux 和 macOS 平台上可用。
</Note>

## 令牌事件处理程序

SDK 会在实体令牌过期之前自动刷新它们。你可以注册回调来观察这些事件。

### 令牌已过期

如果自动刷新失败(例如,原始登录凭据不再有效),SDK 将触发令牌过期事件。注册一个处理程序以提供新凭据并重试登录。

```cpp theme={null}
PFRegistrationToken registrationToken{};
HRESULT hr = PFEntityRegisterTokenExpiredEventHandler(
    nullptr,  // XTaskQueueHandle, or nullptr for default
    nullptr,  // optional context
    [](void* ctx, PFEntityKey const* entityKey)
    {
        // Re-authenticate the player with a fresh credential
        // See relogin.md for a complete example
    },
    &registrationToken);
```

### 令牌已刷新

如果你想知道 SDK 何时在后台成功刷新令牌,请注册一个令牌已刷新处理程序。这只是提供信息——你不需要采取任何操作。

```cpp theme={null}
PFRegistrationToken registrationToken{};
HRESULT hr = PFEntityRegisterTokenRefreshedEventHandler(
    nullptr,  // XTaskQueueHandle, or nullptr for default
    nullptr,  // optional context
    [](void* ctx, PFEntityKey const* entityKey, const PFEntityToken* newToken)
    {
        // Log the refresh or update cached token references
    },
    &registrationToken);
```

### 何时注册和取消注册

* **注册**处理程序应尽早进行,即在 SDK 初始化之后和登录之前。这可以确保你不会错过任何事件。
* **取消注册**处理程序应在关闭期间进行,在关闭实体句柄之前。

```cpp theme={null}
PFEntityUnregisterTokenExpiredEventHandler(expiredRegistrationToken);
PFEntityUnregisterTokenRefreshedEventHandler(refreshedRegistrationToken);
```

有关令牌过期处理和重新登录的完整演练,请参阅[处理令牌过期](/services/playfab/sdks/c/relogin)。

## 实用访问器

你可以从实体句柄检索 API 端点和游戏 ID。它们来自登录期间使用的 **PFServiceConfigHandle**。

```cpp theme={null}
// Get API endpoint
size_t endpointSize{};
HRESULT hr = PFEntityGetAPIEndpointSize(entityHandle, &endpointSize);

std::vector<char> endpoint(endpointSize);
hr = PFEntityGetAPIEndpoint(entityHandle, endpoint.size(), endpoint.data(), nullptr);

// Get title ID
size_t titleIdSize{};
hr = PFEntityGetTitleIdSize(entityHandle, &titleIdSize);

std::vector<char> titleId(titleIdSize);
hr = PFEntityGetTitleId(entityHandle, titleId.size(), titleId.data(), nullptr);
```

## 完整示例

此示例演示了创建、复制、查询和关闭实体句柄:

```cpp theme={null}
#include <playfab/core/PFEntity.h>
#include <playfab/services/PFServices.h>

void EntityHandleExample(PFServiceConfigHandle serviceConfigHandle, XUserHandle userHandle)
{
    //
    // Log in and get an entity handle
    //
    PFAuthenticationLoginWithXUserRequest request{};
    request.createAccount = true;
    request.user = userHandle;

    XAsyncBlock asyncLogin{};
    HRESULT hr = PFAuthenticationLoginWithXUserAsync(serviceConfigHandle, &request, &asyncLogin);
    hr = XAsyncGetStatus(&asyncLogin, true);

    PFEntityHandle entityHandle{ nullptr };
    size_t resultSize{};
    hr = PFAuthenticationLoginWithXUserGetResultSize(&asyncLogin, &resultSize);

    std::vector<char> loginBuffer(resultSize);
    PFAuthenticationLoginResult const* loginResult{};
    hr = PFAuthenticationLoginWithXUserGetResult(
        &asyncLogin, &entityHandle,
        loginBuffer.size(), loginBuffer.data(),
        &loginResult, nullptr);

    //
    // Register token event handlers
    //
    PFRegistrationToken expiredToken{};
    hr = PFEntityRegisterTokenExpiredEventHandler(nullptr, nullptr,
        [](void* ctx, PFEntityKey const* entityKey)
        {
            // Handle re-authentication
        }, &expiredToken);

    PFRegistrationToken refreshedToken{};
    hr = PFEntityRegisterTokenRefreshedEventHandler(nullptr, nullptr,
        [](void* ctx, PFEntityKey const* entityKey, const PFEntityToken* newToken)
        {
            // Log token refresh
        }, &refreshedToken);

    //
    // Duplicate the handle for a subsystem
    //
    PFEntityHandle subsystemHandle{ nullptr };
    hr = PFEntityDuplicateHandle(entityHandle, &subsystemHandle);

    //
    // Query entity info
    //
    bool isTitlePlayer{};
    hr = PFEntityIsTitlePlayer(entityHandle, &isTitlePlayer);

    size_t keySize{};
    hr = PFEntityGetEntityKeySize(entityHandle, &keySize);

    std::vector<char> keyBuffer(keySize);
    PFEntityKey const* entityKey{};
    hr = PFEntityGetEntityKey(entityHandle, keyBuffer.size(), keyBuffer.data(), &entityKey, nullptr);

    //
    // ... make service calls with entityHandle or subsystemHandle ...
    //

    //
    // Cleanup: unregister handlers, then close all handles
    //
    PFEntityUnregisterTokenExpiredEventHandler(expiredToken);
    PFEntityUnregisterTokenRefreshedEventHandler(refreshedToken);

    PFEntityCloseHandle(subsystemHandle);
    PFEntityCloseHandle(entityHandle);
}
```

## API 参考

| 函数                                                                                                                                                 | 描述                                           |
| -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| [PFEntityDuplicateHandle](/services/playfab/api-references/c/pfentity/functions/pfentityduplicatehandle)                                           | 复制一个句柄,增加引用计数。两个句柄必须独立关闭。                    |
| [PFEntityCloseHandle](/services/playfab/api-references/c/pfentity/functions/pfentityclosehandle)                                                   | 关闭一个句柄,减少引用计数。当最后一个句柄关闭时,实体将被销毁。             |
| [PFEntityGetEntityKeySize](/services/playfab/api-references/c/pfentity/functions/pfentitygetentitykeysize)                                         | 获取存储实体键所需的缓冲区大小。                             |
| [PFEntityGetEntityKey](/services/playfab/api-references/c/pfentity/functions/pfentitygetentitykey)                                                 | 获取实体的实体键(类型和 ID)。                            |
| [PFEntityGetEntityTokenAsync](/services/playfab/api-references/c/pfentity/functions/pfentitygetentitytokenasync)                                   | 异步检索缓存的实体令牌。                                 |
| [PFEntityGetEntityTokenResultSize](/services/playfab/api-references/c/pfentity/functions/pfentitygetentitytokenresultsize)                         | 获取实体令牌结果所需的缓冲区大小。                            |
| [PFEntityGetEntityTokenResult](/services/playfab/api-references/c/pfentity/functions/pfentitygetentitytokenresult)                                 | 从已完成的 PFEntityGetEntityTokenAsync 调用中获取实体令牌。 |
| [PFEntityIsTitlePlayer](/services/playfab/api-references/c/pfentity/functions/pfentityistitleplayer)                                               | 返回实体是否为 title\_player\_account。              |
| [PFEntityGetSecretKeySize](/services/playfab/api-references/c/pfentity/functions/pfentitygetsecretkeysize)                                         | 获取密钥所需的缓冲区大小。如果实体不是游戏实体,则失败。                 |
| [PFEntityGetSecretKey](/services/playfab/api-references/c/pfentity/functions/pfentitygetsecretkey)                                                 | 获取游戏实体的密钥。仅在 Windows、Linux 和 macOS 上可用。      |
| [PFEntityGetAPIEndpointSize](/services/playfab/api-references/c/pfentity/functions/pfentitygetapiendpointsize)                                     | 获取 API 端点字符串所需的缓冲区大小。                        |
| [PFEntityGetAPIEndpoint](/services/playfab/api-references/c/pfentity/functions/pfentitygetapiendpoint)                                             | 从实体的关联服务配置中获取 API 端点。                        |
| **PFEntityGetTitleIdSize**                                                                                                                         | 获取游戏 ID 字符串所需的缓冲区大小。                         |
| **PFEntityGetTitleId**                                                                                                                             | 从实体的关联服务配置中获取游戏 ID。                          |
| [PFEntityRegisterTokenExpiredEventHandler](/services/playfab/api-references/c/pfentity/functions/pfentityregistertokenexpiredeventhandler)         | 注册一个自动令牌刷新失败时的回调。                            |
| [PFEntityUnregisterTokenExpiredEventHandler](/services/playfab/api-references/c/pfentity/functions/pfentityunregistertokenexpiredeventhandler)     | 取消注册令牌过期回调。                                  |
| [PFEntityRegisterTokenRefreshedEventHandler](/services/playfab/api-references/c/pfentity/functions/pfentityregistertokenrefreshedeventhandler)     | 注册一个 SDK 成功刷新令牌时的回调。                         |
| [PFEntityUnregisterTokenRefreshedEventHandler](/services/playfab/api-references/c/pfentity/functions/pfentityunregistertokenrefreshedeventhandler) | 取消注册令牌已刷新回调。                                 |

有关完整的 API 参考,请参阅 [PFEntity 成员](/services/playfab/api-references/c/pfentity/pfentity_members)。

## 另请参阅

* [处理令牌过期](/services/playfab/sdks/c/relogin)
* [使用游戏实体访问 PlayFab](/services/playfab/sdks/c/server)
* [SDK 生存期](/services/playfab/sdks/c/lifecycle)
* [快速入门:Windows](/services/playfab/sdks/c/quickstart-gdk)
* [快速入门:Win32](/services/playfab/sdks/c/quickstart-win32)


## Related topics

- [来自 title 实体的游戏服务器](/zh-CN/services/playfab/sdks/c/server.md)
- [快速入门 (Windows) - 调用 PlayFab 服务](/zh-CN/services/playfab/sdks/unified-sdk/quickstart-services.md)
- [从 PlayFab 独立 SDK v1 迁移到统一 SDK v2](/zh-CN/services/playfab/sdks/unified-sdk/migrating-from-v1.md)
- [事件管道教程](/zh-CN/services/playfab/sdks/c/event-pipeline/eventpipeline-tutorial.md)
- [PFEntityDuplicateHandle](/zh-CN/services/playfab/api-references/c/pfentity/functions/pfentityduplicatehandle.md)
