资讯动态

使用 Media Foundation CaptureEngine 构建桌面视频采集应用——Windows-classic-samples 示例深度剖析

发布时间:2026/10/15 20:50:43 来源:尧图企业网站定制
示例工程【免费下载链接】Windows-classic-samplesThis repo contains samples that demonstrate the API used in Windows classic desktop applications.项目地址https://gitcode.com/gh_mirrors/wi/Windows-classic-samples点击查看免费下载导读本文围绕 CaptureEngineVideoCapture 示例 展开它是 Windows-classic-samples 仓库中演示Media Foundation CaptureEngine采集引擎用法的经典桌面程序从网络摄像头等视频采集设备完成实时预览、录制视频文件、拍摄静态照片三大核心功能。读完本文你将掌握 CaptureEngine 的完整生命周期初始化、事件回调、Sink 配置、设备枚举与选择方式、H.264/MP4 编码直通与码率计算策略以及桌面应用在低功耗connected standby场景下的电源管理处理——这些模式可直接迁移到自己的 Windows 桌面采集程序中。一、示例概述能做什么该示例是一款标准的 Win32 桌面应用程序非 UWP / Windows Store 应用通过菜单提供四个核心操作菜单项功能Start Preview / Stop Preview启动/停止摄像头实时预览Choose Device枚举并选择视频采集设备Start Recording / Stop Recording开始/停止录制视频文件Take Photo拍摄并保存 JPEG 静态照片这些菜单项定义在资源脚本 Capture.rc 中IDR_MENU1对应的命令 ID 在 resource.h 中声明ID_CAPTURE_CHOOSEDEVICE、ID_CAPTURE_RECORD、ID_CAPTURE_TAKEPHOTO、ID_CAPTURE_PREVIEW。主窗口底部还带一个状态栏IDC_STATUS_BAR实时显示当前状态文本录制中显示 Recording预览中显示 Previewing否则提示 Please select a device or start preview (using the default device).见 winmain.cpp。从架构上看示例通过一个名为CaptureManager的 C 类Capture.h封装了全部采集引擎逻辑对外暴露StartPreview / StopPreview / StartRecord / StopRecord / TakePhoto等接口UI 层只负责菜单命令与消息分发职责划分清晰是学习 CaptureEngine 的理想范例。二、环境要求与构建运行2.1 系统与工具链要求按示例 README 的说明操作系统客户端 Windows 8.1服务器 Windows Server 2012 R2示例发布于该时期多数情况下可在更新版本上原样运行开发工具要求 Microsoft Visual Studio 2013 或更高版本任意 SKU明确限制该示例无法在 Visual Studio Express 2013 for Windows 中编译。从工程文件 CaptureEngine.vcxproj 可以看到工程默认使用PlatformToolset V110即 VS2012 工具集配置了 Debug/Release 两个 Win32 平台配置字符集为 Unicode。链接器依赖以下库vcxproj 第 69 行powrprof.lib;d3d11.lib;mf.lib;mfplat.lib;mfuuid.lib;shlwapi.lib;Comctl32.lib其中mf.lib / mfplat.lib / mfuuid.lib提供 Media Foundation 主体与 CaptureEngine APId3d11.lib用于创建 D3D11 设备预览渲染powrprof.lib用于电源请求与系统电源能力查询shlwapi.lib提供路径与扩展名处理Comctl32.lib提供状态栏等公共控件。源码中 Capture.h 明确要求以下头文件mfapi.h、mfidl.h、mferror.h、mfcaptureengine.h、d3d11.h等并设置了NTDDI_VERSION NTDDI_WIN8、_WIN32_WINNT _WIN32_WINNT_WIN8Capture.h 第 15-21 行即最低面向 Windows 8 平台 SDK。2.2 构建步骤按 README 的操作流程启动 Visual Studio选择File Open Project/Solution进入示例目录Samples/CaptureEngineVideoCapture/cpp双击解决方案文件CaptureEngine.sln按F7Visual Studio 2013 中也可用F6或选择Build Build Solution编译示例。2.3 运行方式按F5或选择Debug Start Debugging进入调试并运行按CtrlF5或选择Debug Start Without Debugging直接运行不调试。运行后通过 Choose Device 选择摄像头随后即可开始预览、录制或拍照。三、程序启动流程COM 与 Media Foundation 初始化桌面版采集应用的第一步是完成 COM 与 Media Foundation 的初始化见 winmain.cpp 的 wWinMain 入口// Note: The shell common File dialog requires apartment threading. HRESULT hr CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); ... hr MFStartup(MF_VERSION);两点值得注意线程模型CoInitializeEx显式使用COINIT_APARTMENTTHREADED源码注释说明这是因为后续要使用 Shell 通用文件对话框IFileSaveDialog保存录制文件它要求 STA单线程单元模式MF 初始化MFStartup(MF_VERSION)启动 Media Foundation 平台并在程序结束时对称调用MFShutdown()随后调用CoUninitialize()释放 COM。初始化完成后创建主窗口CreateMainWindow并进入标准 Win32 消息循环GetMessage / TranslateMessage / DispatchMessage。主窗口类名为 Capture Engine Window Class窗口标题为 Capture Applicationwinmain.cpp 第 739-757 行。在WM_CREATE处理MainWindow::OnCreate中程序依次完成创建预览子窗口CreatePreviewWindow、创建状态栏、创建CaptureManager实例并初始化采集引擎最后注册电源通知详见后文电源管理章节。四、架构核心CaptureManager 与事件回调机制4.1 CaptureManager 的职责CaptureManager 类 是示例的中枢持有以下关键成员成员类型作用m_pEngineIMFCaptureEngine*采集引擎本体m_pPreviewIMFCapturePreviewSink*预览 Sinkm_pCallbackCaptureEngineCB*事件回调对象m_bPreviewing / m_bRecording / m_bPhotoPendingbool运行状态标志m_hEventHANDLE等待事件同步等待操作完成m_hpwrRequest / m_fPowerRequestSet电源请求句柄与标志防止流媒体期间系统睡眠构造函数中调用PowerCreateRequest创建了一个采集引擎正在录制CaptureEngine is recording!的电源请求上下文Capture.h 第 147-160 行为后续防睡眠做准备。4.2 事件回调线程安全地投递到 UI 线程CaptureEngine 的所有异步操作初始化、预览、录制、拍照都会通过事件通知应用。示例定义了内部类CaptureEngineCB实现IMFCaptureEngineOnEventCallback接口class CaptureEngineCB : public IMFCaptureEngineOnEventCallback { // IUnknown STDMETHODIMP QueryInterface(REFIID riid, void** ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IMFCaptureEngineOnEventCallback STDMETHODIMP OnEvent( _In_ IMFMediaEvent* pEvent); ... };关键实现在 Capture.cpp 的 OnEvent回调线程拿到IMFMediaEvent后并不直接处理而是通过PostMessage(m_hwnd, WM_APP_CAPTURE_EVENT, (WPARAM)pEvent, 0L)把事件对象作为wParam投递到主窗口让所有状态变更都在应用的UI 主线程上执行避免跨线程操作窗口和 COM 对象带来的风险。例外情况当系统即将睡眠m_fSleeping true时消息泵可能已不可用此时回调会直接同步处理MF_CAPTURE_ENGINE_PREVIEW_STOPPED / MF_CAPTURE_ENGINE_RECORD_STOPPED事件并通过SetEvent(m_hEvent)唤醒等待方Capture.cpp 第 46-80 行。主窗口收到WM_APP_CAPTURE_EVENT后调用CaptureManager::OnCaptureEventCapture.cpp 第 236-306 行按事件的扩展类型GetExtendedType分派到对应处理函数事件 GUID处理动作MF_CAPTURE_ENGINE_INITIALIZED初始化完成无采集设备时忽略错误MF_CAPTURE_ENGINE_PREVIEW_STARTED / STOPPED更新预览状态标志MF_CAPTURE_ENGINE_RECORD_STARTED / STOPPED更新录制状态标志MF_CAPTURE_ENGINE_PHOTO_TAKEN清除拍照待处理标志MF_CAPTURE_ENGINE_ERROR销毁采集引擎并报错每次处理完成后调用SetEvent(m_hEvent)通知等待方随后由UpdateUI同步菜单和状态栏。五、采集引擎初始化DXGI 设备管理与属性配置CaptureManager::InitializeCaptureManagerCapture.cpp 第 157-232 行是引擎初始化的核心完整步骤为调用DestroyCaptureEngine()清理旧实例创建同步事件m_hEvent创建回调对象CaptureEngineCB并关联管理器创建 D3D11 设备与 DXGI 设备管理器CreateD3DManagerD3D11CreateDevice使用D3D11_CREATE_DEVICE_VIDEO_SUPPORT标志创建硬件设备功能级别从 11_1 向下兼容到 9_1Capture.cpp 第 90-132 行通过SetMultithreadProtected(TRUE)启用多线程保护确保采集引擎与渲染线程可安全共享设备MFCreateDXGIDeviceManager创建 DXGI 设备管理器并调用ResetDevice绑定 D3D11 设备Capture.cpp 第 134-155 行创建属性集IMFAttributes通过SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, g_pDXGIMan)把 DXGI 设备管理器交给采集引擎使预览渲染可以走硬件路径用CoCreateInstance(CLSID_MFCaptureEngineClassFactory, ...)创建 CaptureEngine 的类工厂再CreateInstance(CLSID_MFCaptureEngine, ...)创建引擎实例调用m_pEngine-Initialize(m_pCallback, pAttributes, NULL, pUnk)完成初始化——pUnk参数即用户选择的设备激活对象IMFActivate*为空时使用默认设备。这里体现了 CaptureEngine 的典型四步初始化范式工厂 → 实例 → 属性 → Initialize(回调, 属性, 音频设备, 视频设备)其中MF_CAPTURE_ENGINE_D3D_MANAGER属性是桌面采集程序实现高性能预览的关键配置。六、设备枚举与选择6.1 使用 MFEnumDeviceSources 枚举摄像头选择设备由菜单命令ID_CAPTURE_CHOOSEDEVICE触发实现在 winmain.cpp 的 OnChooseDevice// Ask for source type video capture devices hr pAttributes-SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); // Enumerate devices. hr MFEnumDeviceSources(pAttributes, param.ppDevices, param.count);枚举的要点创建IMFAttributes属性集设置MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID限定只查找视频捕获类设备MFEnumDeviceSources返回IMFActivate**数组及设备数量每个IMFActivate对象可以通过GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, ...)取出设备的友好名称如 Integrated Camera填入Select Camera选择对话框的列表框winmain.cpp 第 130-171 行若枚举数量为 0禁用 OK 按钮否则默认选中列表第一项。ChooseDeviceParam结构体Capture.h 第 83-100 行用于在对话框与主流程之间传递设备数组、数量与最终选择的下标其析构函数负责释放IMFActivate数组CoTaskMemFree。6.2 切换设备用户在对话框中按下 OK 后主流程取出所选IMFActivate重新调用g_pEngine-InitializeCaptureManager(hPreview, param.ppDevices[iDevice])完成设备切换winmain.cpp 第 412-430 行并保存pSelectedDevice供后续如系统恢复后重建引擎使用。七、实时预览Preview Sink 与 D3D 渲染7.1 预览 Sink 的构建CaptureManager::StartPreviewCapture.cpp 第 338-432 行演示了预览链路的完整配置m_pEngine-GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, pSink)获取预览 Sink再QueryInterface得到IMFCapturePreviewSinkm_pPreview-SetRenderHandle(m_hwndPreview)把预览窗口句柄交给 Sink由引擎把视频帧直接渲染到该窗口m_pEngine-GetSource(pSource)获取采集源GetCurrentDeviceMediaType(MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, ...)取得当前设备视频格式调用CloneVideoMediaType(pMediaType, MFVideoFormat_RGB32, pMediaType2)把媒体类型克隆为RGB32 子类型预览显示的通用格式并设置MF_MT_ALL_SAMPLES_INDEPENDENT TRUE声明所有采样相互独立m_pPreview-AddStream(...)把视频流连接到预览 Sinkm_pEngine-StartPreview()启动预览。其中CloneVideoMediaType实现在 utils.cpp 第 30-82 行新建媒体类型后将主类型设为MFMediaType_Video、子类型设为指定 GUID并从源类型复制帧尺寸MF_MT_FRAME_SIZE、帧率MF_MT_FRAME_RATE、像素纵横比MF_MT_PIXEL_ASPECT_RATIO、隔行模式MF_MT_INTERLACE_MODE等关键属性——这是在保持几何参数不变的前提下更换编码格式的通用工具函数同样服务于后续录制链路。7.2 预览窗口的绘制预览窗口类实现在 PreviewWnd.cppWM_PAINT中若正在预览则调用g_pEngine-UpdateVideo()它内部调用m_pPreview-UpdateVideo(NULL, NULL, NULL)触发 Sink 把最新帧绘制到窗口Capture.cpp 第 253-263 行未预览时用黑色画刷填充背景窗口尺寸变化WM_SIZE时通过InvalidateRect触发重绘主窗口的OnSize会同步调整预览子窗口和状态栏尺寸winmain.cpp 第 352-366 行。渲染底层预览帧通过 D3D11/DXGI 硬件路径显示初始化阶段配置的MF_CAPTURE_ENGINE_D3D_MANAGER这也是桌面采集应用获得流畅预览体验的基础。八、视频录制按扩展名自动选择编码格式8.1 录制目标文件选择菜单 Start Recording 触发OnStartRecordwinmain.cpp 第 442-522 行它弹出IFileSaveDialog保存对话框预设文件名MyVideo.mp4、默认扩展名mp4并给出三种文件类型过滤器过滤器扩展名MP4 (H.264/AAC)*.mp4Windows Media Video*.wmvAll Files*.*用户取消时把ERROR_CANCELLED当作正常流程处理否则把选择的文件系统路径传给g_pEngine-StartRecord(pszFileName)。8.2 编码选择的判定逻辑CaptureManager::StartRecordCapture.cpp 第 622-727 行首先用PathFindExtension取出目标文件扩展名据此决定音视频编码 GUID扩展名视频编码音频编码.mp4MFVideoFormat_H264MFAudioFormat_AAC.wmvMFVideoFormat_WMV3MFAudioFormat_WMAudioV9.wmaGUID_NULL纯音频MFAudioFormat_WMAudioV9其他返回MF_E_INVALIDMEDIATYPE—随后执行录制链路配置GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD)获取录制 Sink转为IMFCaptureRecordSinkRemoveAllStreams()清理上一次录制遗留的流支持连续多次录制SetOutputFileName(pszDestinationFile)设置输出文件按编码 GUID 分别调用ConfigureVideoEncoding与ConfigureAudioEncoding添加视频/音频流m_pEngine-StartRecord()启动录制。停止录制时调用m_pEngine-StopRecord(TRUE, FALSE)并WaitForResult()同步等待MF_CAPTURE_ENGINE_RECORD_STOPPED事件Capture.cpp 第 729-740 行确保文件完整收尾。8.3 视频编码配置H.264 直通与码率估算ConfigureVideoEncodingCapture.cpp 第 512-568 行体现了两种截然不同的策略H.264 直通若摄像头原生输出MFVideoFormat_H264_ES或MFVideoFormat_H264则直接把媒体类型子类型设为MFVideoFormat_H264旁路重编码输出与摄像头原生格式一致避免二次编码带来的质量损失与 CPU 开销通用格式重编码否则基于帧尺寸与帧率估算平均码率uiBitrate uiWidth / 3.0f * uiHeight * uiFrameRateNum / uiFrameRateDenom;即按 宽高 × 帧率 / 3 的启发式公式计算MF_MT_AVG_BITRATE随后pRecord-AddStream(...)把视频流接入录制 Sink。8.4 音频编码配置低延迟与容错ConfigureAudioEncodingCapture.cpp 第 570-620 行通过以下方式挑选编码器输出格式创建属性集并设置MF_LOW_LATENCY TRUE只枚举低延迟音频输出格式调用MFTranscodeGetAudioOutputAvailableTypes(guidEncodingType, MFT_ENUM_FLAG_ALL | MFT_ENUM_FLAG_SORTANDFILTER, ...)获取编码器支持的类型列表借助模板函数GetCollectionObjectCapture.h 第 69-80 行从IMFCollection中取出第一个类型加入录制 Sink。特别值得注意的是容错处理若AddStream返回MF_E_INVALIDSTREAMNUMBER说明当前设备没有音频输入示例将其视为S_OK继续从而在无麦克风环境下仍可完成纯视频录制。九、静态拍照Photo Sink 与 JPEG 格式构造菜单 Take Photo 触发OnTakePhotowinmain.cpp 第 551-607 行它把照片保存到Documents 文件夹文件名按当前本地时间生成格式为MyPhoto年_月日_时分秒.jpg如MyPhoto2026_1014_123956.jpg。CaptureManager::TakePhotoCapture.cpp 第 743-823 行的配置流程与录制类似GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO)获取照片 Sink转为IMFCapturePhotoSinkGetCurrentDeviceMediaType(MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, ...)获取静态图像流媒体类型调用CreatePhotoMediaTypeutils.cpp 第 86-125 行构造 JPEG 输出格式主类型设为MFMediaType_Image、子类型设为GUID_ContainerFormatJpeg并复制源类型的帧尺寸RemoveAllStreams()清理旧流后AddStream连接照片流SetOutputFileName指定输出路径m_pEngine-TakePhoto()触发拍照并置m_bPhotoPending true收到MF_CAPTURE_ENGINE_PHOTO_TAKEN事件后清除该标志。UI 侧同步做了联动约束仅当IsPreviewing()为真且没有拍照在途时拍照菜单才可用winmain.cpp 第 285-291 行避免用户在无预览状态下或拍照未完成时重复触发。十、电源管理connected standby / AOAC 场景的稳健处理这是一个桌面采集示例中容易被忽略、但实现得非常完整的技术点当系统进入睡眠或SOC 设备关闭显示器时必须优雅地停止媒体流并销毁引擎唤醒后再重建。示例在MainWindow::OnCreate中注册了两类电源通知winmain.cpp 第 325-331 行RegisterSuspendResumeNotification接收挂起/恢复广播WM_POWERBROADCASTRegisterPowerSettingNotification(..., GUID_MONITOR_POWER_ON, ...)监听显示器电源状态变化AOAC/SOC 系统专用GetPwrCapabilities查询系统是否支持AoAc低功耗待机。WM_POWERBROADCAST的处理逻辑在 winmain.cpp 第 673-733 行消息处理动作PBT_APMSUSPEND置m_fSleeping停止录制、停止预览并DestroyCaptureEngine()销毁引擎PBT_APMRESUMEAUTOMATIC清除睡眠标志重新InitializeCaptureManager重建引擎PBT_POWERSETTINGCHANGEGUID_MONITOR_POWER_ON仅对AoAc系统生效显示器关闭dwData 0时停流并销毁引擎显示器打开dwData ! 0时重建引擎此外预览期间会调用PowerSetRequest(m_hpwrRequest, PowerRequestExecutionRequired)向系统请求执行必须继续的电源请求防止流媒体播放/录制期间被系统强制睡眠Capture.cpp 第 413-424 行停止预览时对称调用PowerClearRequest释放请求Capture.cpp 第 454-458 行。源码注释说明由于应用已挂接显示器开关通知能够在 SOC 设备进入 connected standby 前主动停流并清除电源请求从而既保证媒体不被打断又不阻碍设备进入低功耗状态。十一、错误处理与资源清理11.1 错误字符串与提示示例把错误信息统一放在资源字符串表中Capture.rc 第 104-112 行通过ShowError(hwnd, UINT id, HRESULT hr)utils.cpp 第 138-146 行加载字符串并附加 HRESULT 十六进制值弹出MessageBox展示例如资源 ID文案IDS_ERR_SET_DEVICEUnable to set the capture device.IDS_ERR_INITIALIZEUnable to initialize the capture engine.IDS_ERR_PREVIEWAn error occurred during preview.IDS_ERR_RECORDAn error occurred during recording.IDS_ERR_CAPTUREAn error occurred during capture.IDS_ERR_PHOTOUnable to capture still photo.不同操作失败时SetErrorID(hrStatus, 对应资源ID)会把正确的资源 ID 记录到m_errorIDCapture.h 第 162-165 行事件分发时由 UI 线程读取并展示。11.2 引用计数与 SafeRelease示例统一使用模板函数SafeReleaseCapture.h 第 59-66 行释放 COM 接口并在CaptureManager::DestroyCaptureEngineCapture.cpp 中声明于 Capture.h 第 206-228 行中集中清理关闭事件句柄、释放预览 Sink 与引擎、ResetDevice后释放 DXGI 管理器与 D3D11 设备并复位所有状态标志。CaptureManager的析构函数也会调用它保证窗口销毁OnDestroy→delete g_pEngine时资源不会泄漏。调试方面示例提供DBGMSG宏与DbgPrint输出到OutputDebugString仅在_DEBUG下生效Capture.h 第 50-56 行事件处理中也会以 GUID 字符串形式打印捕获到的引擎事件类型Capture.cpp 第 253-260 行便于开发期排障。十二、从示例到自研采集程序的要点小结把本示例沉淀为可复用的开发模式核心要点如下初始化四步曲MFStartup→ 创建MFCaptureEngineClassFactory→CreateInstance(CLSID_MFCaptureEngine)→Initialize(回调, 属性, NULL, 设备IMFActivate)其中MF_CAPTURE_ENGINE_D3D_MANAGER属性配合 D3D11/DXGI 设备管理器实现硬件预览事件驱动模型实现IMFCaptureEngineOnEventCallback::OnEvent建议把事件PostMessage到 UI 线程处理并保留睡眠时同步处理的特殊分支三路 Sink 分工预览走IMFCapturePreviewSinkSetRenderHandle绑定窗口录制走IMFCaptureRecordSinkSetOutputFileName 按扩展名选编码拍照走IMFCapturePhotoSinkJPEG 媒体类型设备管理MFEnumDeviceSources配合MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID枚举摄像头用MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME展示名称Initialize传入IMFActivate即可切换设备格式适配CloneVideoMediaType是改编码、留几何参数的通用工具H.264 设备直接直通旁路编码其余按宽×高×帧率/3估算码率容错与体验无音频设备时允许纯视频录制MF_E_INVALIDSTREAMNUMBER视为成功UI 根据预览/录制/拍照状态联动使能菜单电源稳健性用PowerCreateRequest / PowerSetRequest防睡眠用WM_POWERBROADCAST在挂起前停流、恢复后重建引擎并对 AOAC 系统监听GUID_MONITOR_POWER_ON。以上各环节均有 Capture.cpp、winmain.cpp、utils.cpp 与 Capture.h 中的具体实现可对照查阅实际开发时可基于本示例的解决方案文件CaptureEngine.sln打开工程直接编译运行再逐步替换为自己的业务逻辑。赞分享示例工程【免费下载链接】Windows-classic-samplesThis repo contains samples that demonstrate the API used in Windows classic desktop applications.项目地址https://gitcode.com/gh_mirrors/wi/Windows-classic-samples点击查看免费下载相关推荐SkyPilot 存储性能基准测试实战用 fio 量化 SkyPilot Storage 与 EBS 的带宽与 IOPS 差距SkyPilot 存储性能基准测试实战用 fio 量化 SkyPilot Storage 与 EBS 的带宽与 IOPS 差距 SkyPilot 是一个把碎片示例工程使用 Packaging API 提取 .appxbundle 应用捆绑包内容Windows-classic-samples ExtractBundle 示例深度解析使用 Packaging API 提取 .appxbundle 应用捆绑包内容Windows classic samples ExtractBundle 示例示例工程Windows-classic-samples实战教程构建你的第一个桌面应用程序Windows classic samples实战教程构建你的第一个桌面应用程序 想要快速掌握Windows桌面应用程序开发吗 这个完整的Windows示例工程上一篇解锁FOC轮腿机器人从设计哲学到实战应用的完整探索下一篇Navicat无限试用终极指南三步实现Mac版Navicat16/17永久免费使用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑