IExtractImageを利用してエクスプローラのサムネイルを取得する

友達にPDFファイルのサムネイルを取得するプログラムをVB.NETで作れないか?と言われたので、色々と調べまわって作ってみました。


日本語のサイトなんかだとC#だと記事があった↓りしたんですけど

Re[4]: IExtractImageによるサムネイルの取得

なんとも初心者な私ではこれだけでは理解ができない!

ちなみにこの記事を元会社の上司に見られたら確実に怒られる
何故ならコードをちゃんと理解してない!

あとモノとしても不完全で、取得対象ファイルのアドレスが不正と言われたり、ファイルの種類によって取得できなかったり・・・。

そういった問題はおいおい解決できたらいいな〜、って思ってます。
いや、なんかもういらないって言われたから・・・orz

ソースコードVB.NET

まずは必要なインポートから
Imports System
Imports System.Text
Imports System.Drawing.Imaging.BitmapData
Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Drawing

以上です。

クラス定義
Public Class Thumbnail
    Inherits System.Web.UI.Page

    Private MAX_PATH As Integer = 1024

    Public Enum IEIFLAG As Integer
        ASYNC = &H1
        CACHE = &H2
        ASPECT = &H4
        OFFLINE = &H8
        GLEAM = &H10
        SCREEN = &H20
        ORIGSIZE = &H40
        NOSTAMP = &H80
        NOBORDER = &H100
        QUALITY = &H200
    End Enum

まずはクラス定義と「Inherits」継承というやつですね。
「System.Web.UI.Page」を継承しま〜す、って言ってます、そのまんま

はい、次
「MAX_PATH」確保するメモリ領域、かな?
この数値が不十分だと多分サムネイル取得に失敗します。

列挙体「IEIFLAG」
私の英語力では調べても詳細がわからない!
IExtractImage::GetLocationに関係するらしい

ここまで読んでもらったらわかるとおり、わからない事だらけなんですね〜。
言い訳がましいことはこれで最後にしましょう。

関数宣言及び変数宣言
    Public Function GetThumbnailImage(ByVal filePath As String, ByVal sizepic As Integer) As Image
        GetThumbnailImage = Nothing

        Dim desktopFolder As IShellFolder = Nothing
        Dim someFolder As IShellFolder = Nothing
        Dim extract As IExtractImage = Nothing
        Dim pidl As New IntPtr
        Dim filePidl As New IntPtr
        Dim thePath As New IntPtr

        Dim IID_IShellFolder = New Guid("000214E6-0000-0000-C000-000000000046")
        Dim IID_IExtractImage = New Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")

        Dim folderName = Path.GetDirectoryName(filePath)
        Dim shortFileName = Path.GetFileName(filePath)

関数内で使用する変数の宣言です。
「IShellFolder」や「IExtractImage」は現状エラーが出ると思います。
これらを使用する方法は後々記述します。

引数の説明ですが、「fileName」がサムネイル画象を取得するファイルのパスになります。
次に「sizepic」が取得するサムネイル画象のサイズです、ピクセル単位で指定します。取得する画象の高さ、幅は正方形になります。

各変数については後ほど

「IID_〜」についてはこれまた理解不能、ごめんなさい。
各IIDを手動で定義する、とのことですがIIDがわからん!

「folderName」と「shortFileName」はそれぞれ、サムネイル取得対象が保存されているディレクトリとそのファイル自体の名前を取得します。
これらはいくつか方法があるかと思いますが、今回は参考元の方法を使わせていただいております。

以下、関数内
        ShellInterop.SHGetDesktopFolder(desktopFolder)

変数「desktopFolder」にデスクトップの位置を設定します。

ここで言うデスクトップとは、エクスプローラを開いたとき、画面左にデスクトップをルートとした階層型のフォルダ一覧が出ますよね?
つまりPC起動時に表示されるデスクトップとは別の、マイコンピュータ等を下位層に含む仮想のデスクトップです。多分

        desktopFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, folderName, 0, pidl, 0)
        desktopFolder.BindToObject(pidl, IntPtr.Zero, IID_IShellFolder, someFolder)

        someFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, shortFileName, 0, filePidl, 0)
        someFolder.GetUIObjectOf(IntPtr.Zero, 1, filePidl, IID_IExtractImage, 0, extract)

先ほど取得した「folderName」と「shortFileName」がここで出ました。
上2行でフォルダの情報を「someFolder」にバインドします。

下2行でサムネイル取得対象ファイルのオブジェクトを「extract」に設定する、かな。

ここの処理で時折ファイルパスを見失ってしまうことがあるのです、未だに謎(;´д`)

        Dim size As New SIZE
        size.cx = sizepic
        size.cy = sizepic

        Dim flags = IEIFLAG.ASPECT Or IEIFLAG.SCREEN
        Dim bmp As New IntPtr
        thePath = Marshal.AllocHGlobal(MAX_PATH)

        Try
            extract.GetLocation(thePath, MAX_PATH, 0, size, 32, flags)
            extract.Extract(bmp)
        Catch ex As Exception
        End Try

この処理でいよいよサムネイル画象を取得します。

「extract.GetLocation」でサムネイル画象の設定を行っていると思います。
その上の6行はプロパティ値の設定ですね。

そして「extract.Extract(bmp)」で変数「bmp」にサムネイル画象を取得します。

        If Not bmp.Equals(IntPtr.Zero) Then
            GetThumbnailImage = Image.FromHbitmap(bmp)
        Else
            GetThumbnailImage = Nothing
        End If

取得に成功していれば戻り値にサムネイル画象を設定

以下は後始末となっております。

        If thePath <> IntPtr.Zero Then
            Marshal.FreeHGlobal(thePath)
        End If

        If thePath <> IntPtr.Zero Then
            Marshal.FreeCoTaskMem(pidl)
        End If

        If thePath <> IntPtr.Zero Then
            Marshal.FreeCoTaskMem(filePidl)
        End If
    End Function

正直今回の記事、構成に失敗したかな?って思ってます。
コード切り離して全体が扱いづらくなっちゃったんじゃないかと。
というわけで上記のコードを纏めたものを

インポートから関数の終了まで
Imports System
Imports System.Text
Imports System.Drawing.Imaging.BitmapData
Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Drawing

Public Class Thumbnail
    Inherits System.Web.UI.Page

    Private MAX_PATH As Integer = 1024

    Public Enum IEIFLAG As Integer
        ASYNC = &H1
        CACHE = &H2
        ASPECT = &H4
        OFFLINE = &H8
        GLEAM = &H10
        SCREEN = &H20
        ORIGSIZE = &H40
        NOSTAMP = &H80
        NOBORDER = &H100
        QUALITY = &H200
    End Enum

    Public Function GetThumbnailImage(ByVal filePath As String, ByVal sizepic As Integer) As Image
        GetThumbnailImage = Nothing

        Dim desktopFolder As IShellFolder = Nothing
        Dim someFolder As IShellFolder = Nothing
        Dim extract As IExtractImage = Nothing
        Dim pidl As New IntPtr
        Dim filePidl As New IntPtr
        Dim thePath As New IntPtr

        Dim IID_IShellFolder = New Guid("000214E6-0000-0000-C000-000000000046")
        Dim IID_IExtractImage = New Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")

        Dim folderName = Path.GetDirectoryName(filePath)
        Dim shortFileName = Path.GetFileName(filePath)

        ShellInterop.SHGetDesktopFolder(desktopFolder)

        desktopFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, folderName, 0, pidl, 0)
        desktopFolder.BindToObject(pidl, IntPtr.Zero, IID_IShellFolder, someFolder)

        someFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, shortFileName, 0, filePidl, 0)
        someFolder.GetUIObjectOf(IntPtr.Zero, 1, filePidl, IID_IExtractImage, 0, extract)

        Dim size As New SIZE
        size.cx = sizepic
        size.cy = sizepic

        Dim flags = IEIFLAG.ASPECT Or IEIFLAG.SCREEN
        Dim bmp As New IntPtr
        thePath = Marshal.AllocHGlobal(MAX_PATH)

        Try
            extract.GetLocation(thePath, MAX_PATH, 0, size, 32, flags)
            extract.Extract(bmp)
        Catch ex As Exception
        End Try

        If Not bmp.Equals(IntPtr.Zero) Then
            GetThumbnailImage = Image.FromHbitmap(bmp)
        Else
            GetThumbnailImage = Nothing
        End If

        If thePath <> IntPtr.Zero Then
            Marshal.FreeHGlobal(thePath)
        End If

        If thePath <> IntPtr.Zero Then
            Marshal.FreeCoTaskMem(pidl)
        End If

        If thePath <> IntPtr.Zero Then
            Marshal.FreeCoTaskMem(filePidl)
        End If
    End Function

そして最後に、「IShellFolder」や「IExtractImage」を使うのに必要なコードを記述します。
ここから先は本当に私のレベルでは理解が及ばないので、微妙な解説も無しなので、上記のコードとそのまま合わせて使ってみてください。

クラスの終わりまで
<StructLayout(LayoutKind.Sequential)> _
    Public Structure STRRET_CSTR
        Public uType As Integer
        <FieldOffset(4), MarshalAs(UnmanagedType.LPWStr)> _
        Public pOleStr As String
        <FieldOffset(4)> _
        Public uOffset As Integer
        <FieldOffset(4), MarshalAs(UnmanagedType.ByValArray, SizeConst:=520)> _
        Public strName As Byte()
    End Structure

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure SIZE
        Public cx As Integer
        Public cy As Integer
    End Structure

    <ComImportAttribute(), _
     GuidAttribute("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1"), _
     InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
   Public Interface IExtractImage

        <PreserveSig()> Sub GetLocation( _
            ByVal pszPathBuffer As IntPtr, _
            ByVal cch As Integer, _
            ByRef pdwPriority As Integer, _
            ByRef prgSize As SIZE, _
            ByVal dwRecClrDepth As Integer, _
            ByRef pdwFlags As Integer)

        <PreserveSig()> Sub Extract(ByRef phBmpThumbnail As IntPtr)

    End Interface

    <ComImportAttribute(), _
    GuidAttribute("000214E6-0000-0000-C000-000000000046"), _
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IShellFolder

        Sub ParseDisplayName( _
          ByVal hWnd As IntPtr, _
          ByVal pbc As IntPtr, _
          ByVal pszDisplayName As String, _
          ByRef pchEaten As Integer, _
          ByRef ppidl As System.IntPtr, _
          ByRef pdwAttributes As Integer)

        Sub EnumObjects( _
          ByVal hwndOwner As IntPtr, _
          <MarshalAs(UnmanagedType.U4)> ByVal grfFlags As Integer, _
          <Out()> ByRef ppenumIDList As IntPtr)

        Sub BindToObject( _
          ByVal pidl As IntPtr, _
          ByVal pbcReserved As IntPtr, _
          ByRef riid As Guid, _
          ByRef ppvOut As IShellFolder)

        Sub BindToStorage( _
          ByVal pidl As IntPtr, _
          ByVal pbcReserved As IntPtr, _
          ByRef riid As Guid, _
          <Out()> ByVal ppvObj As IntPtr)

        <PreserveSig()> _
        Function CompareIDs( _
          ByVal lParam As IntPtr, _
          ByVal pidl1 As IntPtr, _
          ByVal pidl2 As IntPtr) As Integer

        Sub CreateViewObject( _
          ByVal hwndOwner As IntPtr, _
          ByRef riid As Guid, _
          ByVal ppvOut As Object)

        Sub GetAttributesOf( _
          ByVal cidl As Integer, _
          ByVal apidl As IntPtr, _
          <MarshalAs(UnmanagedType.U4)> ByRef rgfInOut As Integer)

        Sub GetUIObjectOf( _
          ByVal hwndOwner As IntPtr, _
          ByVal cidl As Integer, _
          ByRef apidl As IntPtr, _
          ByRef riid As Guid, _
          <Out()> ByVal prgfInOut As Integer, _
          <Out(), MarshalAs(UnmanagedType.IUnknown)> ByRef ppvOut As Object)

        Sub GetDisplayNameOf( _
          ByVal pidl As IntPtr, _
          <MarshalAs(UnmanagedType.U4)> ByVal uFlags As Integer, _
          ByRef lpName As STRRET_CSTR)

        Sub SetNameOf( _
          ByVal hwndOwner As IntPtr, _
          ByVal pidl As IntPtr, _
          <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszName As String, _
          <MarshalAs(UnmanagedType.U4)> ByVal uFlags As Integer, _
          ByRef ppidlOut As IntPtr)

    End Interface

    Public Class ShellInterop
        <DllImport("shell32.dll", CharSet:=CharSet.Auto)> _
        Public Shared Function SHGetDesktopFolder( _
          <Out()> ByRef ppshf As IShellFolder) As Integer
        End Function
    End Class
End Class

以上です。
一応説明入れましたが間違ってる部分も多いと思いますし、ソース自体もマズイところがあるんじゃないかと思ってますので
この記事を読んでくださった方で、ここが駄目だ!ってところがあったら是非コメントいただけるとありがたいです!

ここまで読んでくださった方、長々と駄文にお付き合いだき、恐縮です。