最下方有demo及源码。

背景

当手机通过 USB 连接 PC (选择文件传输,也就是MTP方式) 时,会看到设备管理器中出现便携设备这一栏,如下图:

打开我的电脑可以看到设备和驱动器中出现对应的设备,如下图:

可以发现,在设备管理器中,便携式设备有两个,可是在我的电脑中看到的设备只有一个,还有一个Nokia的设备显示不出来。这就是为什么我要使用WPD:用于读取和传输我的电脑中所看不见的设备(都是老设备,windows phone,塞班等)的一些信息, 如图片/视频等,相当于自己构建一个小小的文件系统,拥有展示文件列表和传输文件的能力。
现为统一技术栈,需要使用go来实现微软的WPD。

应用技术 WPD(windows portable device)

这是微软提供的一个库,可以通过MTP方式读取到一些设备信息,如:设备名、生产商、设备型号等信息。上github搜索了一下,发现了一个库可以使用 github.com/rlj1202/go-wpd , 貌似是一个韩国人写的。

具体使用

由于需要在项目中使用C++代码,需要本机有gcc的环境,具体如何配置环境就不在本篇赘述了。很贴心的,github的作者将C++的代码库放上去了。

枚举设备基本信息:

func deviceEnumerate() {gowpd.Initialize()mng, err := gowpd.CreatePortableDeviceManager()if err != nil {panic(err)}deviceIDs, err := mng.GetDevices()if err != nil {panic(err)}for i, deviceID := range deviceIDs {friendlyName, err := mng.GetDeviceFriendlyName(deviceID)if err != nil {panic(err)}manufacturer, err := mng.GetDeviceManufacturer(deviceID)if err != nil {panic(err)}description, err := mng.GetDeviceDescription(deviceID)if err != nil {panic(err)}log.Printf("[%d]:\n", i)log.Printf("\tName:         %s\n", friendlyName)log.Printf("\tManufacturer: %s\n", manufacturer)log.Printf("\tDescription:  %s\n", description)gowpd.FreeDeviceID(deviceID)}gowpd.Uninitialize()
}

得到content的objectID

func RecursiveEnumerate(parentObjectID string, content *gowpd.IPortableDeviceContent) {enum, err := content.EnumObjects(parentObjectID)if err != nil {panic(err)}objectIDs := make([]string, 0)for {tmp, err := enum.Next(10)if err != nil {panic(err)}if len(tmp) == 0 {break}objectIDs = append(objectIDs, tmp...)}for _, objectID := range objectIDs {log.Println(objectID)}for _, objectID := range objectIDs {RecursiveEnumerate(objectID, content)}
}func contentEnumerate() {gowpd.Initialize()mng, err := gowpd.CreatePortableDeviceManager()if err != nil {panic(err)}pClientInfo, err := gowpd.CreatePortableDeviceValues()if err != nil {panic(err)}pClientInfo.SetStringValue(gowpd.WPD_CLIENT_NAME, "libgowpd")pClientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_MAJOR_VERSION, 1)pClientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_MINOR_VERSION, 0)pClientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_REVISION, 2)deviceIDs, err := mng.GetDevices()if err != nil {panic(err)}for _, deviceID := range deviceIDs {device, err := gowpd.CreatePortableDevice()if err != nil {panic(err)}err = device.Open(deviceID, pClientInfo)if err != nil {panic(err)}content, err := device.Content()if err != nil {panic(err)}RecursiveEnumerate(gowpd.WPD_DEVICE_OBJECT_ID, content)gowpd.FreeDeviceID(deviceID)}gowpd.Uninitialize()
}

文件传输 Device to PC

func Example_transferToPC() {gowpd.Initialize()mng, err := gowpd.CreatePortableDeviceManager()if err != nil {panic(err)}deviceIDs, err := mng.GetDevices()if err != nil {panic(err)}clientInfo, err := gowpd.CreatePortableDeviceValues()if err != nil {panic(err)}clientInfo.SetStringValue(gowpd.WPD_CLIENT_NAME, "libgowpd")clientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_MAJOR_VERSION, 1)clientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_MINOR_VERSION, 0)clientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_REVISION, 2)// object ID which will be transferred to PC.targetObjectID := "F:\\test.txt" // 这边是模拟的一个,通过枚举content得到的ID// location where file will be transferred into.targetDestination := "E:\\test.txt"for _, id := range deviceIDs {portableDevice, err := gowpd.CreatePortableDevice()if err != nil {panic(err)}portableDevice.Open(id, clientInfo)content, err := portableDevice.Content()if err != nil {panic(err)}resources, err := content.Transfer()if err != nil {panic(err)}objectDataStream, optimalTransferSize, err := resources.GetStream(targetObjectID, gowpd.WPD_RESOURCE_DEFAULT, gowpd.STGM_READ)if err != nil {panic(err)}pFinalFileStream, err := gowpd.SHCreateStreamOnFile(targetDestination, gowpd.STGM_CREATE|gowpd.STGM_WRITE)if err != nil {panic(err)}totalBytesWritten, err := gowpd.StreamCopy(pFinalFileStream, objectDataStream, optimalTransferSize)if err != nil {panic(err)}err = pFinalFileStream.Commit(0)if err != nil {panic(err)}log.Printf("Total bytes written: %d\n", totalBytesWritten)gowpd.FreeDeviceID(id)portableDevice.Release()}mng.Release()gowpd.Uninitialize()// Output:
}

文件传输 PC to Device

func Example_transferToDevice() {gowpd.Initialize()mng, err := gowpd.CreatePortableDeviceManager()if err != nil {panic(err)}deviceIDs, err := mng.GetDevices()if err != nil {panic(err)}//for _, id := range deviceIDs {//  fmt.Println(string(gowpd.PnpToByte(id)))//}pClientInfo, err := gowpd.CreatePortableDeviceValues()if err != nil {panic(err)}pClientInfo.SetStringValue(gowpd.WPD_CLIENT_NAME, "grassto")pClientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_MAJOR_VERSION, 1)pClientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_MINOR_VERSION, 0)pClientInfo.SetUnsignedIntegerValue(gowpd.WPD_CLIENT_REVISION, 2)targetDeviceFriendlyName := "M1852"// objectId where the file will be transferred under.targetObjectID := "s10001" // gowpd.WPD_DEVICE_OBJECT_IDfor _, id := range deviceIDs {friendlyName, err := mng.GetDeviceFriendlyName(id)if err != nil {panic(err)}if friendlyName[:len(friendlyName)-1] != targetDeviceFriendlyName {gowpd.FreeDeviceID(id)continue}pPortableDevice, err := gowpd.CreatePortableDevice()if err != nil {panic(err)}// Establish a connectionerr = pPortableDevice.Open(id, pClientInfo)if err != nil {panic(err)}// path to selected file to transfer to device.filePath := "E:\\test\\1.txt"// open file as IStream.pFileStream, err := gowpd.SHCreateStreamOnFile(filePath, 0)if err != nil {panic(err)}pObjectProperties, err := manualIPV(targetObjectID, filePath)if err != nil {panic(err)}// get stream to devicecontent, err := pPortableDevice.Content()if err != nil {panic(err)}pTempStream, cbTransferSize, err := content.CreateObjectWithPropertiesAndData(pObjectProperties)if err != nil {panic(err)}// convert pTempStream to PortableDeviceDataStream to use more method e.g newly created object id._pFinalObjectDataStream, err := pTempStream.QueryInterface(gowpd.IID_IPortableDeviceDataStream)if err != nil {panic(err)}pFinalObjectDataStream := (*gowpd.IPortableDeviceDataStream)(_pFinalObjectDataStream)// copy data from pFileStream to pFinalObjectDataStreamcbBytesWritten, err := gowpd.StreamCopy((*gowpd.IStream)(_pFinalObjectDataStream), pFileStream, cbTransferSize)// cbBytesWritten, err := gowpd.StreamCopy(pTempStream, pFileStream, cbTransferSize)if err != nil {panic(err)}// call commit method to notice device that transferring data is finished.err = pFinalObjectDataStream.Commit(0)if err != nil {panic(err)}newlyCreatedObjectID, err := pFinalObjectDataStream.GetObjectID()if err != nil {panic(err)}log.Printf("\"%s\" has been transferred to device successfully: %d\n", newlyCreatedObjectID, cbBytesWritten)// transferring is finished. release the deviceID.gowpd.FreeDeviceID(id)// release device interface too.pPortableDevice.Release()}for _, id := range deviceIDs {gowpd.FreeDeviceID(id)}gowpd.Uninitialize()
}func manualIPV(targetObjectID, filePath string) (*gowpd.IPortableDeviceValues, error) {srcFileInfo, err := os.Stat(filePath)if err != nil {return nil, err}pObjectProperties, err := gowpd.CreatePortableDeviceValues()if pObjectProperties == nil {return nil, gowpd.E_UNEXPECTED}if err != nil {return nil, err}err = pObjectProperties.SetStringValue(gowpd.WPD_OBJECT_PARENT_ID, targetObjectID)if err != nil {return nil, err}err = pObjectProperties.SetUnsignedLargeIntegerValue(gowpd.WPD_OBJECT_SIZE, uint64(srcFileInfo.Size()))if err != nil {return nil, err}originalFileName := filepath.Base(filePath)ext := filepath.Ext(filePath)err = pObjectProperties.SetStringValue(gowpd.WPD_OBJECT_ORIGINAL_FILE_NAME, originalFileName)if err != nil {return nil, err}err = pObjectProperties.SetStringValue(gowpd.WPD_OBJECT_NAME, originalFileName[:len(originalFileName)-len(ext)])if err != nil {return nil, err}return pObjectProperties, nil
}

过程中遇到的一些问题

先说结论
1. 这个go的库缺少了device的Close,资源释放的不干净。
如何发现的: 由于工作需要,现有一个比较老的windows phone手机,但这个手机通过MTP方式连接PC,只能够有一个地方能访问该手机的文件系统(例:通过文件资源管理器进行了文件的查看,就不能通过Zune来查看文件内容)。但是在访问的时候,由于资源没有释放干净,导致了该进程运行时,其他进程无法访问手机文件系统。
2. 传输过程中,有的手机传输了一个文件后,就不能再继续传输了,应该是哪里资源还有问题,这个暂时还未解决

以上是源码中原本就包含的一些例子,在这提供一个自己写的小demo。使用vue搭的页面,用go作为服务,实现文件列表的展示以及文件的导出。demo使用过程中可能遇到:请求服务无反应的状况,手动重启ginServer.exe,然后重启项目即可,目前默认ginServer监听的端口号为7860。(CmdOrCtrl+Alt+Y可打开控制台)

链接:百度网盘
提取码:er6m

源码链接:https://gitee.com/Grassto/WPD-FileSystem.git

望解决上述问题的大佬私聊我。

----------------------------------- 2019.11.13更新 ----------------------------------

上述遇到的问题2(传输过程中,有的手机传输了一个文件后,就不能再继续传输了,应该是哪里资源还有问题)解决了,是在导出过程中,通过重新创建protableDevice资源实现的。源码已更新。

----------------------------------- 2019.11.15更新 ----------------------------------

问题2,发现是打开的IStream未进行释放,修改了源码进行资源的释放,添加了IStream.Release方法,解决了该问题。
顺带提一下,当进行文件传输的时候,resources.GetStream 若是返回E_FAIL错误,很有可能是由于没有文件的访问权限。返回0x800700AA错误,应该是资源未释放

----------------------------------- 2020.12.09更新 ----------------------------------

这个库还是有些问题的,今天评论区说从PC写文件到设备,提供的示例使用不了,的确是有问题的。手动构造了一个IPortableDeviceValues对象,可以成功写文件。

go语言 使用MTP协议 通过WPD(windows portable device)读取便携式设备信息并进行文件传输相关推荐

  1. c#使用wpd读取便携式设备信息二

    在上节内容(c#使用wpd读取便携式设备信息一)中,我们已经获取到了设备名称,容量等信息,本节进行读写设备的存储内容操作.WPD对设备的操作都是基于对象的ID的,例如文件夹和文件都有各自的object ...

  2. c#使用WPD读取便携式设备信息一(枚举设备、连接设备及读取设备信息)

    手机或其他电子设备通过USB插入电脑上,并且以MTP(媒体传输协议)方式连接时,可在"计算机"中看到类似计算机盘符的便携式设备文件夹显示,但是这并不是一个计算机盘符,并不能通过常规 ...

  3. c#使用PortableDeviceApiLib读取便携式设备(WPD:Windows Portable Devices)信息

    相关名词解释: WPD( Windows Portable Devices) 译作Windows 便携设备 (WPD) 是一种驱动程序技术,可支持广泛的可移动设备,比如移动电话.数码相机和便携媒体播放 ...

  4. xmodem java_Xmodem XMODEM协议是一种串口通信中广泛用到的异步文件传输协议 联合开发网 - pudn.com...

    Xmodem 所属分类:串口编程 开发工具:Java 文件大小:3KB 下载次数:6 上传日期:2017-11-02 21:50:52 上 传 者:雄霸天下19 说明:  XMODEM协议是一种串口通 ...

  5. C# WPD (windows portable devices) 检测WPD设备 获取设备信息

    最近用c#写过一个WPD的列子,主要是参考 c++的实例, 在 windows sdk 中 ( C:/Program Files/Microsoft SDKs/Windows/v7.0/Samples ...

  6. Windows网络编程:Winsock实现客户端与服务器文件传输(TCP/IP)

    在<Qt实现客户端与服务器消息发送与文件传输>一文里Jungle用Qt和Qt封装的类实现了客户端与服务器之间的消息发送和文件传输.本文Jungle尝试用Windows编程实现客户端与服务器 ...

  7. Windows提示无法访问指定设备、路径或文件该怎么办?

    在运行某个程序或者打开某个文件时,你是否会遇到"Windows无法访问指定设备.路径或文件.你可能没有适当的权限访问该项目."的错误?其实这种错误也不是很复杂,只要用对解决方案,就 ...

  8. 解决Windows 10 无法访问指定设备、路径或文件

    参考https://www.bilibili.com/video/BV1Ki4y157cV windows安全中心-应用和浏览器设置-基于声誉的保护-组织可能不需要的应用

  9. LinuxProbe 0x14 虚拟网站主机功能(基于端口)、Vsftpd服务传输文件、TFTP简单文件传输协议

    虚拟网站主机功能 基于端口号 基于端口号的虚拟主机功能可以让用户通过指定的端口号来访问服务器上的网站资源.在使用Apache配置虚拟网站主机功能时,基于端口号的配置方式是最复杂的. 因此我们不仅要考虑 ...

最新文章

  1. eclipse openmp mpi并行编程例子
  2. 数据结构树之二分查找树
  3. angular五大服务顺序_建议收藏 | 一篇文章告诉你工种的进场顺序
  4. CentOS 6.9/7通过yum安装指定版本的MySQL
  5. Flux架构小白入门笔记
  6. 数据库-优化-每个字段的说明
  7. 『设计模式』开发设计的七大原则,我做人还是挺有原则,那些代码呢?
  8. Spark K-Means
  9. 统计学中【矩】的概念
  10. ES6学习笔记(一):轻松搞懂面向对象编程、类和对象
  11. 2015-04-11一些知识点
  12. 敏捷开发生态系统系列之二:敏捷生态系统-计划跟踪 I(跨职能团队-共同估算-每日立会-同行压力)...
  13. SWT FontFieldEditor使用
  14. 电脑一窍不通可以学计算机吗,对电脑一窍不通,要如何学重装系统?你想学的方法在这里!...
  15. Matlab 查阅、读取nc数据
  16. Windows: Ctrl,Alt, Shift等快捷键的含义
  17. 第5节 批处理编写及其示例
  18. 1.深入.NET框架
  19. WEB前端优化之内容篇
  20. 3W-60W恒流LED驱动电源AH3103

热门文章

  1. 遇见物联,西电开启智慧校园新大门
  2. 西电杨宗凯调研计算机学院,杨宗凯调研指导研究生工作:深化研究生教育改革...
  3. 科技公司的域名大战!
  4. 手机User-Agent
  5. 007_NLP_Task6 利用Text-CNN模型来进行文本分类
  6. Abaqus GUI程序开发之常用的Abaqus内核指令(一)
  7. 春节祝福短信怎么发?付详细文案
  8. STC15f2k60s2C语言定时器2,STC15F2K60S2 定时器2测试C.doc
  9. java熔断器_详解spring cloud分布式关于熔断器
  10. 计算机社团技术部部长述职报告,社团管理部部长述职报告