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

相关推荐
非凡ghost1 天前
MusicPlayer2(本地音乐播放器)
前端·windows·学习·软件需求
时光803.1 天前
快速搭建青龙面板Docker教程
windows·ubuntu·bash·httpx
ComputerInBook1 天前
函数调用栈帧分析(Windows平台)
c语言·windows·编译原理·汇编语言·c++语言
Halo_tjn1 天前
Java List集合知识点
java·开发语言·windows·算法·list
染指11101 天前
20.过保护句柄提权和黑客工具检测-Windows驱动
windows·驱动开发·windows驱动
守城小轩1 天前
Chromium 142 编译指南 Windows篇:Git 配置与安装(二)
windows·chrome devtools·指纹浏览器·浏览器开发
zzcufo2 天前
wincc VBS 中的 Round() 函数详解
windows·笔记
love530love2 天前
【笔记】ComfyUI 启动时端口被占用(PermissionError [winerror 10013])解决方案
人工智能·windows·笔记·stable diffusion·aigc·端口·comfyui
Biehmltym2 天前
【AI】02实现AI Agent全栈:十分钟,跑通Python调用 Gemini(大模型)的小型Web项目
人工智能·windows·python
无限进步_2 天前
C++ Vector 全解析:从使用到深入理解
开发语言·c++·ide·windows·git·github·visual studio