cfapi 入门实战(二):实现云文件回传

在 Windows 10/11 中,Cloud Filter API(简称 CFAPI)是微软提供的官方云存储平台扩展接口,也是 OneDrive 按需下载(Files On-Demand)功能的底层技术。通过 CFAPI,我们能够在文件还未实际下载到本地磁盘的情况下,让用户在资源管理器中预览、打开、编辑这些云端文件。

上一篇文章我们实现了最小的 CFAPI 初始化和占位符创建。本篇将聚焦在一个关键能力上:

用户打开文件时,我们动态返回数据给资源管理器。

本示例的目标非常清晰:

  • 在某个 SyncRoot 目录中注册一个虚拟云盘

  • 创建一个占位文件 test.txt

  • 当用户点击它查看内容时,程序动态返回字符串数据

  • 不需要真实云端通信

这是 CFAPI 的第一个有效闭环。


示例代码

以下完整代码可直接编译运行(x64、Windows 10/11 1809+):

cpp 复制代码
#include <Windows.h>
#include <cfapi.h>
#include <iostream>

#pragma comment(lib, "cldapi.lib")   

static CF_CONNECTION_KEY g_connectionKey;

const wchar_t* SYNC_ROOT = L"D:\\CFSyncRoot";
const wchar_t* PLACEHOLDER_FILE_NAME = L"test.txt";
const char*    FILE_DATA = "Hello cfapi, this is my first cfapi demo!";

static LARGE_INTEGER FileTimeToLargeInteger(const FILETIME& ft) {
    LARGE_INTEGER li = { 0 };
    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;
    return li;
}

static void FillBasicInfo(FILE_BASIC_INFO& basicInfo) {
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);
    LARGE_INTEGER timeLi = FileTimeToLargeInteger(ft);

    basicInfo.CreationTime = timeLi;
    basicInfo.LastAccessTime = timeLi;
    basicInfo.LastWriteTime = timeLi;
    basicInfo.ChangeTime = timeLi;
    basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
}

static bool RegisterSyncRoot()
{
    CF_SYNC_REGISTRATION registration = {};
    registration.StructSize = sizeof(registration);
    registration.ProviderName = L"MyCloud";
    registration.ProviderVersion = L"1.0";

    CF_SYNC_POLICIES policies = {};
    policies.StructSize = sizeof(CF_SYNC_POLICIES);

    HRESULT hr = CfRegisterSyncRoot(
        SYNC_ROOT,      // 要注册的同步根路径
        &registration,  // 注册的同步提供程序和同步根的信息
        &policies,      // 同步根的策略
        CF_REGISTER_FLAG_NONE
    );

    if (FAILED(hr)) {
        std::cout << "同步根目录注册失败: " << std::hex << hr << std::endl;
        return false;
    }
    return true;
}


static void CALLBACK OnFetchPlaceholders(
    _In_ CONST CF_CALLBACK_INFO* info,
    _In_ CONST CF_CALLBACK_PARAMETERS* params
)
{
    CF_OPERATION_INFO opInfo = { 0 };
    opInfo.StructSize = sizeof(opInfo);
    opInfo.ConnectionKey = g_connectionKey; // 使用全局连接键
    opInfo.TransferKey = info->TransferKey; // 使用回调信息中的 TransferKey
    opInfo.Type = CF_OPERATION_TYPE_TRANSFER_PLACEHOLDERS; // 关键: 传输占位符  

    CF_OPERATION_PARAMETERS opParams = { 0 };
    opParams.ParamSize = sizeof(opParams);

    HRESULT hr = CfExecute(&opInfo, &opParams);

    if (FAILED(hr)) {
        std::cout << "OnFetchPlaceholders 失败: 0x" << std::hex << hr << std::endl;
    }
    else {
        std::cout << "OnFetchPlaceholders 成功返回占位符信息。" << std::endl;
    }
}

static void CALLBACK OnNotifyData(
    _In_ CONST CF_CALLBACK_INFO* info,
    _In_ CONST CF_CALLBACK_PARAMETERS* params
)
{
    std::wcout << L"文件被删除!" <<  std::endl;
     
}

static void CALLBACK OnFetchData(
    _In_ CONST CF_CALLBACK_INFO* info,
    _In_ CONST CF_CALLBACK_PARAMETERS* params
)
{
    if (!info) return;
     
    LARGE_INTEGER payloadLen = {};
    payloadLen.QuadPart = static_cast<LONGLONG>(strlen(FILE_DATA));

    CF_OPERATION_INFO opInfo = {};
    opInfo.StructSize = sizeof(opInfo);
    opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA;
    opInfo.ConnectionKey = info->ConnectionKey;
    opInfo.TransferKey = info->TransferKey;
    opInfo.RequestKey = info->RequestKey;

    CF_OPERATION_PARAMETERS opParams = {};
    opParams.ParamSize = sizeof(opParams);
    opParams.TransferData.Buffer = FILE_DATA;
    opParams.TransferData.Length = payloadLen;
    opParams.TransferData.Offset.QuadPart = 0; // 从文件开头开始传输
    opParams.TransferData.CompletionStatus = 0;

    HRESULT hr = CfExecute(&opInfo, &opParams);
    if (FAILED(hr)) {
        std::wcout << L"CfExecute TransferData failed hr=0x" << std::hex << hr << L"\n";
    }
    else {
        std::wcout << L"CfExecute TransferData succeeded\n";
    }
}
// 启动回调连接
static bool StartSyncCallbacks()
{
    CF_CALLBACK_REGISTRATION reg[] =
    {
        { CF_CALLBACK_TYPE_FETCH_DATA,         OnFetchData },
        { CF_CALLBACK_TYPE_NOTIFY_DELETE,      OnNotifyData },
        { CF_CALLBACK_TYPE_FETCH_PLACEHOLDERS, OnFetchPlaceholders },
        CF_CALLBACK_REGISTRATION_END
    };

    HRESULT hr = CfConnectSyncRoot(
        SYNC_ROOT,
        reg,
        nullptr,
        CF_CONNECT_FLAG_NONE,
        &g_connectionKey
    );

    if (FAILED(hr)) {
        std::cout << "链接同步根目录失败: " << std::hex << hr << std::endl;
        return false;
    }

    return true;
}

/*
* 创建一个占位符文件
*/
static void CreatePlaceholder()
{
    CF_PLACEHOLDER_CREATE_INFO info = {};
    info.RelativeFileName = PLACEHOLDER_FILE_NAME;

    FILE_BASIC_INFO basic = {};

    SYSTEMTIME st;
    GetSystemTime(&st);
    FILETIME ft;
    SystemTimeToFileTime(&st, &ft);

    LARGE_INTEGER liTime;
    liTime.LowPart = ft.dwLowDateTime;
    liTime.HighPart = ft.dwHighDateTime;

    basic.CreationTime = liTime;
    basic.LastAccessTime = liTime;
    basic.LastWriteTime = liTime;
    basic.ChangeTime = liTime;
    basic.FileAttributes = FILE_ATTRIBUTE_ARCHIVE ;

    info.FsMetadata.BasicInfo = basic;
    info.FsMetadata.FileSize.QuadPart = strlen(FILE_DATA); // !!!实际字符大小 ("Hello from CFAPI")
     
    static const char identity[] = "demo-file-identity";
    info.FileIdentity = identity;
    info.FileIdentityLength = (DWORD)strlen(identity);

    info.Flags = CF_PLACEHOLDER_CREATE_FLAG_NONE;
    info.Result = S_OK;
    info.CreateUsn = 0;

    DWORD entriesProcessed = 0;
    HRESULT hr = CfCreatePlaceholders(SYNC_ROOT, &info, 1, CF_CREATE_FLAG_NONE, &entriesProcessed);
    if (FAILED(hr)) {
        std::wcout << L"CfCreatePlaceholders failed hr=0x" << std::hex << hr << L"\n";
    }
    else {
        std::wcout << L"CfCreatePlaceholders succeeded, entriesProcessed=" << entriesProcessed << L"\n";
    } 
}


int main()
{
    std::cout << "云同步最小DEMO" << std::endl;

    CreateDirectoryW(SYNC_ROOT, nullptr);

    RegisterSyncRoot();

    StartSyncCallbacks();

    CreatePlaceholder();

    std::cout << "资源管理器中,访问云文件夹!" << std::endl;
    std::cin.get();

    CfDisconnectSyncRoot(g_connectionKey);
    CfUnregisterSyncRoot(SYNC_ROOT);

    return 0;
}

运行效果

程序启动后:

  1. 打开 D:\CFSyncRoot

  2. 双击 test.txt,Windows 请求读取其内容

  3. 程序动态回传数据 FILE_DATA

  4. 文本编辑器将显示:

记住:文件在本地并不存在,只是一个占位符。这个文件的内容来自 运行中的你的程序

为什么这段代码已经具备云盘能力?

因为它完成了 CFAPI 的最关键闭环:

步骤 实现的 CFAPI 能力
注册同步根 云盘来源身份建立
创建占位文件 云端文件映射在本地出现
FETCH_DATA 回调提供内容 支持按需下载文件数据
返回成功状态 系统信任数据来源并缓存

这就是"按需下载"的核心机制。

下一篇:将构建真正的云同步

当前我们只返回固定字符串。下一步将:

  • 文件实际存储于云服务(或本地模拟云服务器)

  • 支持目录树扫描与多文件占位创建

  • 支持分块传输和断点续传

  • 支持用户回写、上传修改内容

下篇计划:

CFAPI 入门实战(三):支持目录树扫描与多文件占位创建

相关推荐
凯子坚持 c19 小时前
CANN 性能剖析实战:从原始事件到交互式火焰图
windows·microsoft
开开心心就好19 小时前
发票合并打印工具,多页布局设置实时预览
linux·运维·服务器·windows·pdf·harmonyos·1024程序员节
獨枭19 小时前
PyCharm 跑通 SAM 全流程实战
windows
仙剑魔尊重楼20 小时前
音乐制作电子软件FL Studio2025.2.4.5242中文版新功能介绍
windows·音频·录屏·音乐·fl studio
PHP小志21 小时前
Windows 服务器怎么修改密码和用户名?账户被系统锁定如何解锁
windows
专注VB编程开发20年1 天前
vb.net datatable新增数据时改用数组缓存
java·linux·windows
仙剑魔尊重楼1 天前
专业音乐制作软件fl Studio 2025.2.4.5242中文版新功能
windows·音乐·fl studio
rjc_lihui1 天前
Windows 运程共享linux系统的方法
windows
失忆爆表症1 天前
01_项目搭建指南:从零开始的 Windows 开发环境配置
windows·postgresql·fastapi·milvus
阿昭L1 天前
C++异常处理机制反汇编(三):32位下的异常结构分析
c++·windows·逆向工程