在 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, // 要注册的同步根路径
®istration, // 注册的同步提供程序和同步根的信息
&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;
}
运行效果
程序启动后:
-
打开 D:\CFSyncRoot
-
双击 test.txt,Windows 请求读取其内容
-
程序动态回传数据 FILE_DATA
-
文本编辑器将显示:

记住:文件在本地并不存在,只是一个占位符。这个文件的内容来自 运行中的你的程序。
为什么这段代码已经具备云盘能力?
因为它完成了 CFAPI 的最关键闭环:
| 步骤 | 实现的 CFAPI 能力 |
|---|---|
| 注册同步根 | 云盘来源身份建立 |
| 创建占位文件 | 云端文件映射在本地出现 |
| FETCH_DATA 回调提供内容 | 支持按需下载文件数据 |
| 返回成功状态 | 系统信任数据来源并缓存 |
这就是"按需下载"的核心机制。
下一篇:将构建真正的云同步
当前我们只返回固定字符串。下一步将:
-
文件实际存储于云服务(或本地模拟云服务器)
-
支持目录树扫描与多文件占位创建
-
支持分块传输和断点续传
-
支持用户回写、上传修改内容
下篇计划:
CFAPI 入门实战(三):支持目录树扫描与多文件占位创建