news 2026/1/22 14:02:57

ACPI!ACPIBuildDeviceRequest函数分析和ACPI!ACPIBuildDeviceDpc函数的关系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ACPI!ACPIBuildDeviceRequest函数分析和ACPI!ACPIBuildDeviceDpc函数的关系

ACPI!ACPIBuildDeviceRequest函数分析和ACPI!ACPIBuildDeviceDpc函数的关系


NTSTATUS
ACPIBuildDeviceRequest(
IN PDEVICE_EXTENSION DeviceExtension,
IN PACPI_BUILD_CALLBACK CallBack,
IN PVOID CallBackContext,
IN BOOLEAN RunDPC
)
/*++

Routine Description:

This routine is called when a device extension is ready to be filled in.
This routine creates a request which is enqueued. When the DPC is fired,
the request will be processed

Note: AcpiDeviceTreeLock must be held to call this function

Arguments:

DeviceExtension - The device which wants to be filled in
CallBack - The function to call when done
CallBackContext - The argument to pass to that function
RunDPC - Should we enqueue the DPC immediately (if it is not
running?)

Return Value:

NTSTATUS

--*/
{
PACPI_BUILD_REQUEST buildRequest;

ASSERT( KeGetCurrentIrql() == DISPATCH_LEVEL );

//
// Allocate a buildRequest structure
//
buildRequest = ExAllocateFromNPagedLookasideList(
&BuildRequestLookAsideList
);
if (buildRequest == NULL) {

return STATUS_INSUFFICIENT_RESOURCES;

}

//
// If the current reference is 0, that means that someone else beat
// use to the device extension that that we *CANNOT* touch it
//
if (DeviceExtension->ReferenceCount == 0) {

ExFreeToNPagedLookasideList(
&BuildRequestLookAsideList,
buildRequest
);
return STATUS_DEVICE_REMOVED;

} else {

InterlockedIncrement( &(DeviceExtension->ReferenceCount) );

}

//
// Fill in the structure
//
RtlZeroMemory( buildRequest, sizeof(ACPI_BUILD_REQUEST) );
buildRequest->Signature = ACPI_SIGNATURE;
buildRequest->TargetListEntry = &AcpiBuildDeviceList;
buildRequest->WorkDone = WORK_DONE_STEP_0;
buildRequest->Status = STATUS_SUCCESS;
buildRequest->CallBack = CallBack;
buildRequest->CallBackContext = CallBackContext;
buildRequest->BuildContext = DeviceExtension;
buildRequest->Flags = BUILD_REQUEST_VALID_TARGET |
BUILD_REQUEST_DEVICE;

//
// At this point, we need the spinlock
//
KeAcquireSpinLockAtDpcLevel( &AcpiBuildQueueLock );

//
// Add this to the list
//
InsertTailList(
&AcpiBuildQueueList,
&(buildRequest->ListEntry)
);

//
// Do we need to queue up the DPC?
//
if (RunDPC && !AcpiBuildDpcRunning) {

KeInsertQueueDpc( &AcpiBuildDpc, 0, 0 );

}

//
// Done with the lock
//
KeReleaseSpinLockFromDpcLevel( &AcpiBuildQueueLock );

//
// Done
//
return STATUS_PENDING;
}


0: kd> t
Breakpoint 4 hit
eax=0000000a ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=80ae2bca
eip=f73fcc7c esp=f789a0b4 ebp=f789a0d8 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
ACPI!ACPIBuildDeviceRequest:
f73fcc7c 55 push ebp
0: kd> kc
#
00 ACPI!ACPIBuildDeviceRequest
01 ACPI!OSNotifyCreateDevice
02 ACPI!OSNotifyCreate
03 ACPI!Device
04 ACPI!ParseTerm
05 ACPI!RunContext
06 ACPI!InsertReadyQueue
07 ACPI!RestartContext
08 ACPI!SyncLoadDDB
09 ACPI!AMLILoadDDB
0a ACPI!ACPIInitializeDDB
0b ACPI!ACPIInitializeDDBs
0c ACPI!ACPIInitialize
0d ACPI!ACPIInitStartACPI
0e ACPI!ACPIRootIrpStartDevice
0f ACPI!ACPIDispatchIrp
10 nt!IofCallDriver
11 nt!IopSynchronousCall
12 nt!IopStartDevice
13 nt!PipProcessStartPhase1
14 nt!PipProcessDevNodeTree
15 nt!PipDeviceActionWorker
16 nt!PipRequestDeviceAction
17 nt!IopInitializeBootDrivers
18 nt!IoInitSystem
19 nt!Phase1Initialization
1a nt!PspSystemThreadStartup
1b nt!KiThreadStartup


0: kd> x acpi!AcpiBuildDpc
f743b840 ACPI!AcpiBuildDpc = struct _KDPC
0: kd> dx -id 0,0,899a2278 -r1 (*((ACPI!_KDPC *)0xf743b840))
(*((ACPI!_KDPC *)0xf743b840)) [Type: _KDPC]
[+0x000] Type : 19 [Type: short]
[+0x002] Number : 0x0 [Type: unsigned char]
[+0x003] Importance : 0x1 [Type: unsigned char]
[+0x004] DpcListEntry [Type: _LIST_ENTRY]
[+0x00c] DeferredRoutine : 0xf73fc5b2 [Type: void (*)(_KDPC *,void *,void *,void *)]
[+0x010] DeferredContext : 0x0 [Type: void *]
[+0x014] SystemArgument1 : 0x0 [Type: void *]
[+0x018] SystemArgument2 : 0x0 [Type: void *]
[+0x01c] DpcData : 0x0 [Type: void *]
0: kd> u f73fc5b2
ACPI!ACPIBuildDeviceDpc [d:\srv03rtm\base\busdrv\acpi\driver\nt\buildsrc.c @ 478]:
f73fc5b2 53 push ebx
f73fc5b3 8b1d70b042f7 mov ebx,dword ptr [ACPI!_imp_KefAcquireSpinLockAtDpcLevel (f742b070)]
f73fc5b9 56 push esi
f73fc5ba be98b843f7 mov esi,offset ACPI!AcpiBuildQueueLock (f743b898)
f73fc5bf 8bce mov ecx,esi
f73fc5c1 ffd3 call ebx
f73fc5c3 803d9eb843f700 cmp byte ptr [ACPI!AcpiBuildDpcRunning (f743b89e)],0
f73fc5ca 740d je ACPI!ACPIBuildDeviceDpc+0x27 (f73fc5d9)


0: kd> dv
DeviceExtension = 0x899c0d58
CallBack = 0x00000000
CallBackContext = 0x00000000
RunDPC = 0x00 ''

0: kd> dx -id 0,0,899a2278 -r1 ((ACPI!_DEVICE_EXTENSION *)0x899c0d58)
((ACPI!_DEVICE_EXTENSION *)0x899c0d58) : 0x899c0d58 [Type: _DEVICE_EXTENSION *]
[+0x000] Flags : 0xa [Type: unsigned __int64]
[+0x000] UFlags [Type: __unnamed]
[+0x008] Signature : 0x5f534750 [Type: unsigned long]
[+0x00c] DebugFlags : 0x0 [Type: unsigned long]
[+0x010] DispatchTable : 0x0 [Type: IRP_DISPATCH_TABLE *]
[+0x014] WorkContext [Type: WORK_QUEUE_CONTEXT]
[+0x014] Fdo [Type: _FDO_DEVICE_EXTENSION]
[+0x014] Filter [Type: _FILTER_DEVICE_EXTENSION]
[+0x014] Pdo [Type: _PDO_DEVICE_EXTENSION]
[+0x058] WorkQueue [Type: EXTENSION_WORKER]
[+0x058] Button [Type: BUTTON_EXTENSION]
[+0x058] Thermal [Type: THERMAL_EXTENSION]
[+0x058] LinkNode [Type: LINK_NODE_EXTENSION]
[+0x058] Dock [Type: DOCK_EXTENSION]
[+0x058] Processor [Type: _PROCESSOR_DEVICE_EXTENSION]
[+0x088] DeviceState : Stopped (0) [Type: _ACPI_DEVICE_STATE]
[+0x08c] PreviousState : Stopped (0) [Type: _ACPI_DEVICE_STATE]
[+0x090] PowerInfo [Type: _ACPI_POWER_INFO]
[+0x10c] DeviceID : 0x0 [Type: unsigned char *]
[+0x10c] Address : 0x0 [Type: unsigned long]
[+0x110] InstanceID : 0x0 [Type: unsigned char *]
[+0x114] ResourceList : 0x0 [Type: _CM_RESOURCE_LIST *]
[+0x118] PnpResourceList : 0x0 [Type: _ObjData *]
[+0x11c] OutstandingIrpCount : 1 [Type: long]
[+0x120] ReferenceCount : 2 [Type: long]
[+0x124] HibernatePathCount : 0 [Type: long]
[+0x128] RemoveEvent : 0x0 [Type: _KEVENT *]
[+0x12c] AcpiObject : 0x899affac [Type: _NSObj *]
[+0x130] DeviceObject : 0x0 [Type: _DEVICE_OBJECT *]
[+0x134] TargetDeviceObject : 0x0 [Type: _DEVICE_OBJECT *]
[+0x138] PhysicalDeviceObject : 0x0 [Type: _DEVICE_OBJECT *]
[+0x13c] ParentExtension : 0x89981a18 [Type: _DEVICE_EXTENSION *]
[+0x140] ChildDeviceList [Type: _LIST_ENTRY]
[+0x148] SiblingDeviceList [Type: _LIST_ENTRY]
[+0x150] EjectDeviceHead [Type: _LIST_ENTRY]
[+0x158] EjectDeviceList [Type: _LIST_ENTRY]

0: kd> x acpi!*rootdevice*
f743b710 ACPI!RootDeviceExtension = 0x89981a18

bp nt!IoCreateDevice


//
// Allocate a buildRequest structure
//
buildRequest = ExAllocateFromNPagedLookasideList(
&BuildRequestLookAsideList
);
if (buildRequest == NULL) {

return STATUS_INSUFFICIENT_RESOURCES;

}


0: kd> p
eax=899860d0 ebx=00000000 ecx=00000001 edx=0000000a esi=899860d0 edi=f743b7e0
eip=f73fccaf esp=f789a0a8 ebp=f789a0b0 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
ACPI!ACPIBuildDeviceRequest+0x33:
f73fccaf 85f6 test esi,esi


0: kd> dt _ACPI_BUILD_REQUEST 899860d0
ACPI!_ACPI_BUILD_REQUEST
+0x000 ListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x008 Signature : 0
+0x00c Flags : 3
+0x00c UFlags : __unnamed
+0x010 WorkDone : 0x10101
+0x014 CurrentWorkDone : 0x3f8
+0x018 NextWorkDone : 0
+0x01c BuildContext : 0x00000007 Void
+0x020 Status : 0n65538
+0x024 CurrentObject : 0x00000004 _NSObj
+0x028 CallBack : 0x00000004 void +4
+0x02c CallBackContext : 0xffffffff Void
+0x030 DeviceRequest : __unnamed
+0x030 RunRequest : __unnamed
+0x030 SynchronizeRequest : __unnamed
+0x044 Integer : 0x1c2000
+0x044 String : 0x001c2000 "--- memory read error at address 0x001c2000 ---"
+0x044 TargetListEntry : 0x001c2000 _LIST_ENTRY


0: kd> x acpi!AcpiBuildDeviceList
f743b888 ACPI!AcpiBuildDeviceList = struct _LIST_ENTRY [ 0xf743b888 - 0xf743b888 ]
0: kd> dx -id 0,0,899a2278 -r1 (*((ACPI!_LIST_ENTRY *)0xf743b888))
(*((ACPI!_LIST_ENTRY *)0xf743b888)) [Type: _LIST_ENTRY]
[+0x000] Flink : 0xf743b888 [Type: _LIST_ENTRY *]
[+0x004] Blink : 0xf743b888 [Type: _LIST_ENTRY *]

//
// Fill in the structure
//
RtlZeroMemory( buildRequest, sizeof(ACPI_BUILD_REQUEST) );
buildRequest->Signature = ACPI_SIGNATURE;
buildRequest->TargetListEntry = &AcpiBuildDeviceList; 关键地方1:
buildRequest->WorkDone = WORK_DONE_STEP_0;
buildRequest->Status = STATUS_SUCCESS;
buildRequest->CallBack = CallBack;
buildRequest->CallBackContext = CallBackContext;
buildRequest->BuildContext = DeviceExtension; 关键地方2:
buildRequest->Flags = BUILD_REQUEST_VALID_TARGET |
BUILD_REQUEST_DEVICE;


0: kd> dt _ACPI_BUILD_REQUEST 899860d0
ACPI!_ACPI_BUILD_REQUEST
+0x000 ListEntry : _LIST_ENTRY [ 0xf743b890 - 0x899c6d08 ]
+0x008 Signature : 0x5f534750
+0x00c Flags : 0x1001
+0x00c UFlags : __unnamed
+0x010 WorkDone : 3
+0x014 CurrentWorkDone : 0
+0x018 NextWorkDone : 0
+0x01c BuildContext : 0x899c0d58 Void
+0x020 Status : 0n0
+0x024 CurrentObject : (null)
+0x028 CallBack : (null)
+0x02c CallBackContext : (null)
+0x030 DeviceRequest : __unnamed
+0x030 RunRequest : __unnamed
+0x030 SynchronizeRequest : __unnamed
+0x044 Integer : 0xf743b888
+0x044 String : 0xf743b888 "???"
+0x044 TargetListEntry : 0xf743b888 _LIST_ENTRY [ 0xf743b888 - 0xf743b888 ]

//
// Add this to the list
//
InsertTailList(
&AcpiBuildQueueList,
&(buildRequest->ListEntry)
);


0: kd> x acpi!AcpiBuildQueueList
f743b890 ACPI!AcpiBuildQueueList = struct _LIST_ENTRY [ 0x899c6d08 - 0x899860d0 ]
0: kd> dx -id 0,0,899a2278 -r1 (*((ACPI!_LIST_ENTRY *)0xf743b890))
(*((ACPI!_LIST_ENTRY *)0xf743b890)) [Type: _LIST_ENTRY]
[+0x000] Flink : 0x899c6d08 [Type: _LIST_ENTRY *]
[+0x004] Blink : 0x899860d0 [Type: _LIST_ENTRY *]
0: kd> dx -id 0,0,899a2278 -r1 ((ACPI!_LIST_ENTRY *)0x899c6d08)
((ACPI!_LIST_ENTRY *)0x899c6d08) : 0x899c6d08 [Type: _LIST_ENTRY *]
[+0x000] Flink : 0x899860d0 [Type: _LIST_ENTRY *]
[+0x004] Blink : 0xf743b890 [Type: _LIST_ENTRY *]

参考:第一个是什么情况:
0: kd> dt _ACPI_BUILD_REQUEST 0x899c6d08
ACPI!_ACPI_BUILD_REQUEST
+0x000 ListEntry : _LIST_ENTRY [ 0x899860d0 - 0xf743b890 ]
+0x008 Signature : 0x5f534750
+0x00c Flags : 0x100a
+0x00c UFlags : __unnamed
+0x010 WorkDone : 3
+0x014 CurrentWorkDone : 0
+0x018 NextWorkDone : 0
+0x01c BuildContext : 0x89981a18 Void
+0x020 Status : 0n0
+0x024 CurrentObject : (null)
+0x028 CallBack : 0xf7400be2 void ACPI!ACPIDevicePowerNotifyEvent+0
+0x02c CallBackContext : 0xf789a260 Void
+0x030 DeviceRequest : __unnamed
+0x030 RunRequest : __unnamed
+0x030 SynchronizeRequest : __unnamed
+0x044 Integer : 0xf743b868
+0x044 String : 0xf743b868 "h???"
+0x044 TargetListEntry : 0xf743b868 _LIST_ENTRY [ 0xf743b868 - 0xf743b868 ]


0: kd> u 0xf743b868
ACPI!AcpiBuildSynchronizationList:
f743b868 68b843f768 push 68F743B8h
f743b86d b843f770b8 mov eax,0B870F743h
f743b872 43 inc ebx
f743b873 f770b8 div eax,dword ptr [eax-48h]
f743b876 43 inc ebx
f743b877 f778b8 idiv eax,dword ptr [eax-48h]
f743b87a 43 inc ebx
f743b87b f778b8 idiv eax,dword ptr [eax-48h]

F:\srv03rtm>grep "AcpiBuildSynchronizationList" -nr F:\srv03rtm\base\busdrv\acpi |grep -v "inary"
F:\srv03rtm\base\busdrv\acpi/driver/nt/acpiosnt.c:182: InitializeListHead( &AcpiBuildSynchronizationList );
F:\srv03rtm\base\busdrv\acpi/driver/nt/buildsrc.c:124:LIST_ENTRY AcpiBuildSynchronizationList;
F:\srv03rtm\base\busdrv\acpi/driver/nt/buildsrc.c:717: if (!IsListEmpty( &AcpiBuildSynchronizationList) ) {
F:\srv03rtm\base\busdrv\acpi/driver/nt/buildsrc.c:725: &AcpiBuildSynchronizationList
F:\srv03rtm\base\busdrv\acpi/driver/nt/buildsrc.c:6725: syncRequest->TargetListEntry = &AcpiBuildSynchronizationList;
F:\srv03rtm\base\busdrv\acpi/driver/nt/buildsrc.c:7037: buildRequest->TargetListEntry = &AcpiBuildSynchronizationList;
F:\srv03rtm\base\busdrv\acpi/driver/nt/buildsrc.h:256: extern LIST_ENTRY AcpiBuildSynchronizationList;
F:\srv03rtm\base\busdrv\acpi/driver/nt/obj/i386/acpi.map:3021: 0003:00003868 _AcpiBuildSynchronizationList 00052868 <common>
F:\srv03rtm\base\busdrv\acpi/tools/kdext/build.c:229: dumpAcpiBuildList( "ACPI!AcpiBuildSynchronizationList" );


NTSTATUS
ACPIBuildSynchronizationRequest(
IN PDEVICE_EXTENSION DeviceExtension,
IN PACPI_BUILD_CALLBACK CallBack,
IN PVOID CallBackContext,
IN PLIST_ENTRY SynchronizeListEntry,
IN BOOLEAN RunDPC
)
{

//
// Fill in the structure
//
RtlZeroMemory( buildRequest, sizeof(ACPI_BUILD_REQUEST) );
buildRequest->Signature = ACPI_SIGNATURE;
buildRequest->TargetListEntry = &AcpiBuildSynchronizationList;


0: kd> dt ACPI!_DEVICE_EXTENSION 0x89981a18
+0x000 Flags : 0x0001e000`00200010
+0x000 UFlags : __unnamed
+0x008 Signature : 0x5f534750
+0x00c DebugFlags : 0
+0x010 DispatchTable : 0xf743826c IRP_DISPATCH_TABLE
+0x014 WorkContext : WORK_QUEUE_CONTEXT
+0x014 Fdo : _FDO_DEVICE_EXTENSION
+0x014 Filter : _FILTER_DEVICE_EXTENSION
+0x014 Pdo : _PDO_DEVICE_EXTENSION
+0x058 WorkQueue : EXTENSION_WORKER
+0x058 Button : BUTTON_EXTENSION
+0x058 Thermal : THERMAL_EXTENSION
+0x058 LinkNode : LINK_NODE_EXTENSION
+0x058 Dock : DOCK_EXTENSION
+0x058 Processor : _PROCESSOR_DEVICE_EXTENSION
+0x088 DeviceState : 0 ( Stopped )
+0x08c PreviousState : 0 ( Stopped )
+0x090 PowerInfo : _ACPI_POWER_INFO
+0x10c DeviceID : 0x899bfea0 "ACPI\PNP0C08"
+0x10c Address : 0x899bfea0
+0x110 InstanceID : 0x899c53e8 "0x5F534750"
+0x114 ResourceList : 0x899bfeb8 _CM_RESOURCE_LIST
+0x118 PnpResourceList : (null)
+0x11c OutstandingIrpCount : 0n2
+0x120 ReferenceCount : 0n3
+0x124 HibernatePathCount : 0n0
+0x128 RemoveEvent : (null)
+0x12c AcpiObject : (null)
+0x130 DeviceObject : 0x89981b98 _DEVICE_OBJECT
+0x134 TargetDeviceObject : 0x899c1de0 _DEVICE_OBJECT
+0x138 PhysicalDeviceObject : 0x899c1de0 _DEVICE_OBJECT
+0x13c ParentExtension : (null)
+0x140 ChildDeviceList : _LIST_ENTRY [ 0x899c0ea0 - 0x899c0ea0 ]
+0x148 SiblingDeviceList : _LIST_ENTRY [ 0x89981b60 - 0x89981b60 ]
+0x150 EjectDeviceHead : _LIST_ENTRY [ 0x89981b68 - 0x89981b68 ]
+0x158 EjectDeviceList : _LIST_ENTRY [ 0x89981b70 - 0x89981b70 ]

参考:第一个是什么情况:


0: kd> x acpi!AcpiBuildDpcRunning
f743b89e ACPI!AcpiBuildDpcRunning = 0x00 ''
0: kd> x acpi!RunDPC
0: kd> dv RunDPC
RunDPC = 0x00 ''

//
// Do we need to queue up the DPC?
//
if (RunDPC && !AcpiBuildDpcRunning) {

KeInsertQueueDpc( &AcpiBuildDpc, 0, 0 );

}

//
// Done with the lock
//
KeReleaseSpinLockFromDpcLevel( &AcpiBuildQueueLock );

//
// Done
//
return STATUS_PENDING;
}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/20 17:41:45

认知四境:从天真到通透的人生智慧

认知四境:从见山是山到见山无山,我们如何看懂世界的真相? 你是否有过这样的体验:小时候抬头看云,觉得那就是棉花糖做的城堡,简单又纯粹;长大后再看云,却会琢磨气流、水汽、大气环流,云不再是云,而是一堆物理公式的集合;等到历经沧桑,再抬头时,云依然是云,只是多…

作者头像 李华
网站建设 2026/1/21 6:06:34

昇腾NPU适配进展:华为硬件支持HunyuanOCR的时间表预测

昇腾NPU适配进展&#xff1a;HunyuanOCR在国产硬件上的落地前景 在企业数字化转型加速的今天&#xff0c;智能文档处理已成为金融、政务、物流等行业的刚需。传统OCR方案依赖检测、识别、后处理多个模型串联&#xff0c;不仅部署复杂&#xff0c;还容易因中间环节误差累积导致整…

作者头像 李华
网站建设 2026/1/21 9:31:01

手写体识别能力考察:HunyuanOCR对手写字迹的支持度

手写体识别能力考察&#xff1a;HunyuanOCR对手写字迹的支持度 在数字化办公日益普及的今天&#xff0c;纸质文档、手写笔记、填表记录等非标准文本正成为信息自动化处理的“最后一公里”难题。尤其是在教育、医疗、金融等行业&#xff0c;大量关键信息仍以手写形式存在——学生…

作者头像 李华
网站建设 2026/1/13 17:49:00

Kubernetes集群部署:大规模运行HunyuanOCR的架构设想

Kubernetes集群部署&#xff1a;大规模运行HunyuanOCR的架构设想 在企业级AI应用日益普及的今天&#xff0c;如何将前沿模型高效、稳定地落地到生产环境&#xff0c;已成为技术团队的核心命题。尤其是在文档解析、跨境内容处理等场景中&#xff0c;对高精度、低延迟、多语言支持…

作者头像 李华
网站建设 2026/1/21 8:01:55

Notion数据库联动:将HunyuanOCR识别结果同步至知识库

HunyuanOCR与Notion数据库联动&#xff1a;构建智能知识归档系统 在企业日常运营中&#xff0c;合同、发票、证件等非结构化文档的处理始终是一大痛点。这些文件大多以扫描件或图片形式存在&#xff0c;无法直接检索、难以批量分析&#xff0c;信息往往“沉睡”在文件夹里。更麻…

作者头像 李华
网站建设 2026/1/21 0:12:54

ProcessOn在线作图集成:导入图片自动生成可编辑流程图

图片一键变流程图&#xff1a;AI如何重塑在线作图体验 在一次跨部门协作会议后&#xff0c;产品经理拿着手机里拍下的白板草图发愁——上面是刚刚讨论出的业务流程&#xff0c;潦草但关键。他需要尽快把这张图整理成标准流程图发给开发团队&#xff0c;可重绘不仅耗时&#xff…

作者头像 李华