WPDデバイスにファイルをアップロードする - C#

WPDデバイスにファイルをアップロードしてファイル転送するコードを紹介します。

概要

WindowsマシンとUSBケーブルで接続したデバイスとの間でMTPを利用してファイルのアップロードをします。

事前準備

Portable Device APIの参照追加

Windows Formプロジェクトを作成します。こちらの記事を参照してPortable Device APIのCOMオブジェクトを参照します。

WPDデバイスのファイル、フォルダを参照する機能の実装

ダウンロードするファイルを選択するため、WPDデバイスにあるファイル、フォルダを参照する機能を実装します。実装手順やコードはこちらの記事を参照してください。

プログラム

UI

下図のUIを作成します。こちらの記事で作成したUIにボタンを2つ追加します。また、FileOpenDialog, FileSaveDialogを追加します。ファイルのアップロードではFileOpenDialogのみを利用します。

コード

プログラムコードは長いため、末尾に記載します。

解説

Uploadボタンのクリック

Uploadボタンをクリックすると下記コードが実行されます。 OpenFileDialogを開きアップロードするファイルを選択させます。また、ファイルをアップロードする先はListViewで選択されている項目のフォルダ内になります。UIとしては現在リストビューに表示されているフォルダにアップロードするほうが自然ですが、プログラムのコードを簡単にするためリストビューで選択している項目のフォルダ内にアップロードします。
Uploadメソッドを呼び出しファイルのアップロードします。第一引数にアップロードするフォルダのオブジェクトID、第二引数にアップロードするファイルのフルパスを与えます。
  private void button2_Click(object sender, EventArgs e)
  {
    if (openFileDialog1.ShowDialog() == DialogResult.OK) {
      string FolderID = listView1.Items[listView1.SelectedItems[0].Index].SubItems[2].Text;
      Upload(FolderID, openFileDialog1.FileName);
    }
  }

アップロード処理

アップロード処理は下記のコードになります。
ファイルの情報の取得と同様に、IPortableDeviceContent を取得し IPortableDeviceProperties を取得します。
IPortableDeviceContent オブジェクトのTransfer メソッドを呼び出し、IPortableDeviceResources オブジェクトを取得します。アップロードするファイルの情報をIPortableDeviceValues オブジェクトを作成して設定する必要があります。IPortableDeviceValues への値の設定は後述する GetRequiredPropertiesForContentType メソッドで実施しています。
  public void Upload(string FolderID, string FilePath)
  {
    WPDDevice device = (WPDDevice)comboBox_device.SelectedItem;

    IPortableDeviceContent content;
    device.DeviceClass.Content(out content);

    IPortableDeviceProperties properties;
    content.Properties(out properties);

    IPortableDeviceValues values = GetRequiredPropertiesForContentType(FilePath, FolderID);

PortableDeviceApiLib.IStream を準備しますが、ComTypes.IStream に移し替えるため、tempStream として宣言します。アップロードに際し、アップロードに適したバッファサイズを取得する必要があります。バッファサイズとアップロード用のストリームの取得はCreateObjectWithPropertiesAndData()メソッドを呼び出して取得します。取得したPortableDeviceApiLib.IStream をキャストしComTypes.IStream に変換して利用します。
アップロードするファイルをディスクから読み出すための、FileStream を作成しアップロードするファイルを読み出します。1回の読出しで読み込むサイズはCreateObjectWithPropertiesAndDataで取得したバッファサイズを利用します。読み出したデータは、ComTypes.IStream のWriteメソッドを呼び出しWPDデバイスへ書き込みます。アップロードするファイルのFileStream がすべて読み込み終わるまでアップロードを繰り返します。
    PortableDeviceApiLib.IStream tempStream;
    uint optimalTransferSizeBytes = 0;
    content.CreateObjectWithPropertiesAndData(values, out tempStream, ref optimalTransferSizeBytes, null);

    System.Runtime.InteropServices.ComTypes.IStream targetStream = (System.Runtime.InteropServices.ComTypes.IStream)tempStream;

    try {
      System.IO.FileStream sourceStream = new System.IO.FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
      byte[] buffer = new byte[optimalTransferSizeBytes];
      int bytesRead;
      while (true) {
        bytesRead = sourceStream.Read(buffer, 0, (int)optimalTransferSizeBytes);
        if (bytesRead <= 0) {
          break;
        }

        IntPtr pcbWritten = IntPtr.Zero;
        targetStream.Write(buffer, (int)bytesRead, pcbWritten);
      }
      targetStream.Commit(0);
    }
    finally {
      Marshal.ReleaseComObject(tempStream);
    }
  }

GetRequiredPropertiesForContentType

アップロードするファイルの情報を格納する IPortableDeviceValues オブジェクトを作成するメソッドです。ファイルの情報を設定するため、項目ごとの_tagpropertykey オブジェクトを作成します。IPortableDeviceValues オブジェクトの SetStringValue, SetUnsignedLargeIntegerValue メソッドでIPortableDeviceValues オブジェクトに値を設定します。値の設定の際には設定する項目を示す _tagpropertykey を第一引数に与えます。第二引数に設定する値を与えます。
    private IPortableDeviceValues GetRequiredPropertiesForContentType(string FileName, string parentObjectId)
    {
      IPortableDeviceValues values = new PortableDeviceTypesLib.PortableDeviceValues() as IPortableDeviceValues;

      _tagpropertykey WPD_OBJECT_PARENT_ID_PK = new _tagpropertykey();
      WPD_OBJECT_PARENT_ID_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_PARENT_ID_PK.pid = WPD_OBJECT_PARENT_ID;
      values.SetStringValue(ref WPD_OBJECT_PARENT_ID_PK, parentObjectId);

      System.IO.FileInfo fileInfo = new System.IO.FileInfo(FileName);
      var WPD_OBJECT_SIZE_PK = new _tagpropertykey();
      WPD_OBJECT_SIZE_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_SIZE_PK.pid = WPD_OBJECT_SIZE;
      values.SetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE_PK, (ulong)fileInfo.Length);

      var WPD_OBJECT_ORIGINAL_FILE_NAME_PK = new _tagpropertykey();
      WPD_OBJECT_ORIGINAL_FILE_NAME_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_ORIGINAL_FILE_NAME_PK.pid = WPD_OBJECT_ORIGINAL_FILE_NAME;
      values.SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME_PK, System.IO.Path.GetFileName(FileName));

      var WPD_OBJECT_NAME_PK = new _tagpropertykey();
      WPD_OBJECT_NAME_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_NAME_PK.pid = WPD_OBJECT_NAME;
      values.SetStringValue(WPD_OBJECT_NAME_PK, System.IO.Path.GetFileName(FileName));

      return values;
    }

実行結果

今回アップロードするファイルは下記のPNG画像とします。


プロジェクトを実行します。下図のウィンドウが表示されます。


コンボボックスを開き接続するWPDデバイスを選択します。選択するとルートディレクトリのファイル、フォルダがリストビューに表示されます。


項目をダブルクリックしてフォルダ内を開きます。


今回はスマートフォンのカメラ画像が保存されるフォルダにアップロードをします。Cameraフォルダを選択します。選択後[Upload]ボタンをクリックします。


ファイル選択ダイアログボックスが表示されます。準備した画像ファイルを選択します。


ファイルがアップロードされたことを確認するため、選択したフォルダをダブルクリックしフォルダ内を表示します。選択したファイル名と同じファイルが作成されていることが確認できます。


エクスプローラで確認します。ファイルが作成されていることが確認できます。


画像ビューワーで開いて確認します。画像ファイルがアップロードできていることが確認できました。

参考: プログラムコード全体

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using PortableDeviceApiLib;

namespace WpdDemo
{
  public partial class FormMain : Form
  {
    public const uint WPD_OBJECT_ID = 2;
    public const uint WPD_OBJECT_PARENT_ID = 3;
    public const uint WPD_OBJECT_NAME = 4;
    public const uint WPD_OBJECT_CONTENT_TYPE = 7;
    public const uint WPD_OBJECT_SIZE = 11;
    public const uint WPD_OBJECT_ORIGINAL_FILE_NAME = 12;
    public const uint WPD_OBJECT_DATE_CREATED = 18;
    public const uint WPD_OBJECT_DATE_MODIFIED = 19;

    public const uint WPD_FUNCTIONAL_OBJECT_CATEGORY = 2;
    public const uint WPD_CLIENT_NAME = 2;
    public const uint WPD_CLIENT_MAJOR_VERSION = 3;
    public const uint WPD_CLIENT_MINOR_VERSION = 4;
    public const uint WPD_CLIENT_REVISION = 5;
    public const uint WPD_RESOURCE_DEFAULT = 0;
    public const uint WPD_STORAGE_FREE_SPACE_IN_BYTES = 5;

    private PortableDeviceManager deviceManager;

    class WPDDevice
    {
      public string DeviceID;
      public string DeviceName;
      public PortableDeviceClass DeviceClass;


      public override string ToString()
      {
        return string.Format("WPDデバイス名:{0}\r\n", DeviceName);
      }
    }

    public enum ObjectKind { FOLDER, FILE };

    public class PortableDeviceObject
    {
      public string Id { get; set; }
      public string Name { get; set; }
      public ObjectKind kind { get; set; }
    }

    public FormMain()
    {
      InitializeComponent();
    }

    private void FormMain_Load(object sender, EventArgs e)
    {
      init();
    }

    private void init()
    {
      deviceManager = new PortableDeviceManager();

      deviceManager.RefreshDeviceList();
      uint count = 0;
      deviceManager.GetDevices(null, ref count);

      string[] devicesIDs = new string[count];
      WPDDevice[] wpdDevice = new WPDDevice[count];

      //PortableDeviceClass[] devices = new PortableDeviceClass[count];

      deviceManager.GetDevices(devicesIDs, ref count);
      for (int i = 0; i < count; i++) {
        wpdDevice[i] = new WPDDevice();
        wpdDevice[i].DeviceID = devicesIDs[i];
      }

      IPortableDeviceValues clientInfo = (IPortableDeviceValues)new PortableDeviceTypesLib.PortableDeviceValuesClass();

      for (int i = 0; i < count; i++) {
        wpdDevice[i].DeviceClass = new PortableDeviceClass();
        wpdDevice[i].DeviceClass.Open(wpdDevice[i].DeviceID, clientInfo);

        IPortableDeviceContent content;
        wpdDevice[i].DeviceClass.Content(out content);

        IPortableDeviceProperties properties;
        content.Properties(out properties);

        IPortableDeviceValues propertyValues;
        properties.GetValues("DEVICE", null, out propertyValues);

        //wpdDevice[i].DeviceClass.Close();

        string name;
        _tagpropertykey property = new _tagpropertykey();
        property.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
        property.pid = 4;
        try {
          propertyValues.GetStringValue(property, out name);
          wpdDevice[i].DeviceName = name;
        }
        catch (System.Runtime.InteropServices.COMException) {
          wpdDevice[i].DeviceName = "なし";
        }

        comboBox_device.Items.Add(wpdDevice[i]);
      }
    }

    private void comboBox_device_SelectedIndexChanged(object sender, EventArgs e)
    {
      DeviceChanged();
    }

    private void DeviceChanged()
    {
      WPDDevice device = (WPDDevice)comboBox_device.SelectedItem;

      IPortableDeviceContent content;
      device.DeviceClass.Content(out content);

      IPortableDeviceProperties properties;
      content.Properties(out properties);

      IEnumPortableDeviceObjectIDs objectIDs;
      string FolderID = "DEVICE";
      content.EnumObjects(0, FolderID, null, out objectIDs);

      listView1.Items.Clear();

      string objectID;
      uint fetched = 0;

      while (true) {
        objectIDs.Next(1, out objectID, ref fetched);
        if (fetched <= 0) break;

        PortableDeviceObject currentObject = WrapObject(properties, objectID);

        ListViewItem li = listView1.Items.Add(currentObject.Name);
        switch (currentObject.kind) {
          case ObjectKind.FILE:
            li.SubItems.Add("ファイル");
            break;
          case ObjectKind.FOLDER:
            li.SubItems.Add("フォルダ");
            break;
        }
        li.SubItems.Add(currentObject.Id);
      }
    }


    private void listView1_DoubleClick(object sender, EventArgs e)
    {
      string FolderID = listView1.Items[listView1.SelectedItems[0].Index].SubItems[2].Text;
      WPDDevice device = (WPDDevice)comboBox_device.SelectedItem;

      IPortableDeviceContent content;
      device.DeviceClass.Content(out content);

      IPortableDeviceProperties properties;
      content.Properties(out properties);

      IEnumPortableDeviceObjectIDs objectIDs;
      content.EnumObjects(0, FolderID, null, out objectIDs);

      listView1.Items.Clear();

      string objectID;
      uint fetched = 0;

      while (true) {
        objectIDs.Next(1, out objectID, ref fetched);
        if (fetched <= 0) break;

        PortableDeviceObject currentObject = WrapObject(properties, objectID);

        ListViewItem li = listView1.Items.Add(currentObject.Name);
        switch (currentObject.kind) {
          case ObjectKind.FILE:
            li.SubItems.Add("ファイル");
            break;
          case ObjectKind.FOLDER:
            li.SubItems.Add("フォルダ");
            break;
        }
        li.SubItems.Add(currentObject.Id);
      }

    }

    public static PortableDeviceObject WrapObject(IPortableDeviceProperties properties, string objectID)
    {
      IPortableDeviceKeyCollection keys;
      properties.GetSupportedProperties(objectID, out keys);

      IPortableDeviceValues values;
      properties.GetValues(objectID, keys, out values);

      // Get the name of the object
      string name;
      _tagpropertykey property = new _tagpropertykey();
      property.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      property.pid = WPD_OBJECT_NAME;

      try {
        values.GetStringValue(property, out name);
      }
      catch (System.Runtime.InteropServices.COMException exc) {
        name = "(non name)";
      }

      // Get the original name of the object
      string OriginalName;
      property = new _tagpropertykey();
      property.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      property.pid = WPD_OBJECT_ORIGINAL_FILE_NAME;
      try {
        values.GetStringValue(property, out OriginalName);
      }
      catch (Exception e) {
        OriginalName = "";
      }

      // Get the type of the object
      Guid contentType;
      property = new _tagpropertykey();
      property.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      property.pid = WPD_OBJECT_CONTENT_TYPE;
      try {
        values.GetGuidValue(property, out contentType);
      }
      catch (System.Runtime.InteropServices.COMException exc) {
        PortableDeviceObject obj = new PortableDeviceObject();
        obj.Id = null;
        obj.Name = name;
        obj.kind = ObjectKind.FOLDER;
        return obj;
      }

      Guid folderType = new Guid(0x27E2E392, 0xA111, 0x48E0, 0xAB, 0x0C, 0xE1, 0x77, 0x05, 0xA0, 0x5F, 0x85);
      Guid functionalType = new Guid(0x99ED0160, 0x17FF, 0x4C44, 0x9D, 0x98, 0x1D, 0x7A, 0x6F, 0x94, 0x19, 0x21);

      if (contentType == folderType || contentType == functionalType) {
        PortableDeviceObject fobj = new PortableDeviceObject();
        fobj.Id = objectID;
        fobj.Name = name;
        fobj.kind = ObjectKind.FOLDER;
        return fobj;
      }

      if (OriginalName.CompareTo("") != 0) {
        name = OriginalName;
      }

      PortableDeviceObject robj = new PortableDeviceObject();
      robj.Id = objectID;
      robj.Name = name;
      robj.kind = ObjectKind.FILE;
      return robj;
    }

    private void button1_Click(object sender, EventArgs e)
    {
      string Filename = listView1.Items[listView1.SelectedItems[0].Index].Text;
      string FileID = listView1.Items[listView1.SelectedItems[0].Index].SubItems[2].Text;

      saveFileDialog1.FileName = Filename;
      if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
        Download(FileID, saveFileDialog1.FileName);
      }
    }

    public void Download(string FileID, string FilePath)
    {
      WPDDevice device = (WPDDevice)comboBox_device.SelectedItem;

      IPortableDeviceContent content;
      device.DeviceClass.Content(out content);

      IPortableDeviceProperties properties;
      content.Properties(out properties);

      PortableDeviceObject downloadFileObj = WrapObject(properties, FileID);

      IPortableDeviceResources resources;
      content.Transfer(out resources);

      PortableDeviceApiLib.IStream wpdStream;
      uint optimalTransferSize = 0;

      var property = new _tagpropertykey();
      property.fmtid = new Guid(0xE81E79BE, 0x34F0, 0x41BF, 0xB5, 0x3F, 0xF1, 0xA0, 0x6A, 0xE8, 0x78, 0x42);
      property.pid = WPD_RESOURCE_DEFAULT;

      resources.GetStream(FileID, ref property, 0, ref optimalTransferSize, out wpdStream);
      System.Runtime.InteropServices.ComTypes.IStream sourceStream = (System.Runtime.InteropServices.ComTypes.IStream)wpdStream;

      System.IO.FileStream targetStream = new System.IO.FileStream(FilePath, System.IO.FileMode.Create, System.IO.FileAccess.Write);

      int BUFFER_SIZE = 32767;
      byte[] buffer = new byte[BUFFER_SIZE];

      IntPtr pbytesRead = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)));

      while (true) {
        sourceStream.Read(buffer, BUFFER_SIZE, pbytesRead);

        int bytesRead = Marshal.ReadInt32(pbytesRead);
        if (bytesRead <= 0) {
          break;
        }
        targetStream.Write(buffer, 0, bytesRead);
      }
      targetStream.Close();

      Marshal.ReleaseComObject(sourceStream);
      Marshal.ReleaseComObject(wpdStream);
    }

    private void button2_Click(object sender, EventArgs e)
    {
      if (openFileDialog1.ShowDialog() == DialogResult.OK) {
        string FolderID = listView1.Items[listView1.SelectedItems[0].Index].SubItems[2].Text;
        Upload(FolderID, openFileDialog1.FileName);
      }

    }

    public void Upload(string FolderID, string FilePath)
    {
      WPDDevice device = (WPDDevice)comboBox_device.SelectedItem;

      IPortableDeviceContent content;
      device.DeviceClass.Content(out content);

      IPortableDeviceProperties properties;
      content.Properties(out properties);

      IPortableDeviceValues values = GetRequiredPropertiesForContentType(FilePath, FolderID);

      PortableDeviceApiLib.IStream tempStream;
      uint optimalTransferSizeBytes = 0;
      content.CreateObjectWithPropertiesAndData(values, out tempStream, ref optimalTransferSizeBytes, null);

      System.Runtime.InteropServices.ComTypes.IStream targetStream = (System.Runtime.InteropServices.ComTypes.IStream)tempStream;

      try {
        System.IO.FileStream sourceStream = new System.IO.FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
        byte[] buffer = new byte[optimalTransferSizeBytes];
        int bytesRead;
        while (true) {
          bytesRead = sourceStream.Read(buffer, 0, (int)optimalTransferSizeBytes);
          if (bytesRead <= 0) {
            break;
          }

          IntPtr pcbWritten = IntPtr.Zero;
          targetStream.Write(buffer, (int)bytesRead, pcbWritten);
        }
        targetStream.Commit(0);
      }
      finally {
        Marshal.ReleaseComObject(tempStream);
      }
    }


    private IPortableDeviceValues GetRequiredPropertiesForContentType(string FileName, string parentObjectId)
    {
      IPortableDeviceValues values = new PortableDeviceTypesLib.PortableDeviceValues() as IPortableDeviceValues;

      _tagpropertykey WPD_OBJECT_PARENT_ID_PK = new _tagpropertykey();
      WPD_OBJECT_PARENT_ID_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_PARENT_ID_PK.pid = WPD_OBJECT_PARENT_ID;
      values.SetStringValue(ref WPD_OBJECT_PARENT_ID_PK, parentObjectId);

      System.IO.FileInfo fileInfo = new System.IO.FileInfo(FileName);
      var WPD_OBJECT_SIZE_PK = new _tagpropertykey();
      WPD_OBJECT_SIZE_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_SIZE_PK.pid = WPD_OBJECT_SIZE;
      values.SetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE_PK, (ulong)fileInfo.Length);

      var WPD_OBJECT_ORIGINAL_FILE_NAME_PK = new _tagpropertykey();
      WPD_OBJECT_ORIGINAL_FILE_NAME_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_ORIGINAL_FILE_NAME_PK.pid = WPD_OBJECT_ORIGINAL_FILE_NAME;
      values.SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME_PK, System.IO.Path.GetFileName(FileName));

      var WPD_OBJECT_NAME_PK = new _tagpropertykey();
      WPD_OBJECT_NAME_PK.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
      WPD_OBJECT_NAME_PK.pid = WPD_OBJECT_NAME;
      values.SetStringValue(WPD_OBJECT_NAME_PK, System.IO.Path.GetFileName(FileName));

      return values;
    }
  }
}
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2018-08-13
iPentec all rights reserverd.