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 入门实战(三):支持目录树扫描与多文件占位创建

相关推荐
cheniie2 小时前
Windows网络共享指南
windows·ics·网络共享
初听于你4 小时前
Thymeleaf 模板引擎讲解
java·服务器·windows·spring boot·spring·eclipse
MrEnginx4 小时前
解决Windows提示无法加载文件 xxx.ps1,因为在此系统上禁止运行脚本
windows
水木姚姚5 小时前
PyTorch在Microsft windows 11下的使用
人工智能·pytorch·windows
询问QQ:4877392785 小时前
79.基于matlab的卷积稀疏的形态成分分析的医学图像融合,基于卷积稀疏性的形态分量分析 (...
windows
字节旅行者6 小时前
Win10下无法拖动文件如何解决
windows
星释6 小时前
Rust 练习册 113:构建你自己的 CSV 处理器
开发语言·windows·rust
字节拾光录6 小时前
局域网文件共享全方案:Windows/Mac/Linux通用指南,告别U盘与权限难题!
linux·windows·macos
艾莉丝努力练剑6 小时前
【Python基础:语法第四课】列表和元组——Python 里的“爱情”:列表善变,元组长情
大数据·人工智能·windows·python·安全·pycharm·编辑器