Jump to content

Drag&Drop Outlook Emails into AutoIt GUI / Dragging virtual files / OLE Drag&Drop


Recommended Posts

Hi there,

I am trying to save an email which was dragged from Outlook onto my AutoIt Application to file.

Accepting files the regular way doesn't work since Outlook seems to be using the OLE Drag&Drop.

I've found the DragDropEvent UDF by @Ward, but unfortunately, it can only retreive a limited amount of Formats, so I tried my luck.

 

Here's the UDF in case you don't want to click the Link above.

Spoiler
; ===============================================================================================================================
; File     : DragDropEvent.au3 (2012/3/15)
; Purpose  : Convert OLE drag-and-drop event to "Windows Message" that AutoIt3 can handle by GUIRegisterMsg
;            Provide 4 message: $WM_DRAGENTER, $WM_DRAGOVER, $WM_DRAGLEAVE, and $WM_DROP
; Author   : Ward
; ===============================================================================================================================

#Include-once
#Include <Memory.au3>
#Include <SendMessage.au3>
#Include <WindowsConstants.au3>

; ===============================================================================================================================
; Public Functions:
;
; DragDropEvent_Startup()
;   DragDropEvent UDF startup.
;
; DragDropEvent_Register($hWnd, $hWndToReceiveMsg = $hWnd)
;   Register a window or control that can be the target. $hWndToReceiveMsg is needed only when $hWnd is a control.
;
; DragDropEvent_Revoke($hWnd)
;   Revokes the registration of the specified window or control.
;
; DragDropEvent_GetHWnd($wParam)
;   Can invoke by all message handler, to get what window or control is the target.
;
; DragDropEvent_GetX($wParam)
; DragDropEvent_GetY($wParam)
; DragDropEvent_GetKeyState($wParam)
;   Can invoke by all message handler except $WM_DRAGLEAVE, to get the modifier keys and mouse position.
;
; DragDropEvent_IsText($wParam)
; DragDropEvent_IsFile($wParam)
;   Can invoke by $WM_DRAGENTER or $WM_DROP, to check what data is being dragged.
;
; DragDropEvent_GetText($wParam)
; DragDropEvent_GetFile($wParam)
;   Can invoke by $WM_DRAGENTER or $WM_DROP, to get related data. Path of file is splited by "|".
;
; ===============================================================================================================================

; ===============================================================================================================================
; Internal Functions:
;
; IDropTarget_QueryInterface($pSelf, $pRIID, $pObj)
; IDropTarget_AddRef($pSelf)
; IDropTarget_Release($pSelf)
; IDropTarget_DragEnter($pSelf, $DataObj, $KeyState, $X, $Y, $pEffect)
; IDropTarget_DragLeave($pSelf)
; IDropTarget_DragOver($pSelf, $KeyState, $X, $Y, $pEffect)
; IDropTarget_Drop($pSelf, $DataObj, $KeyState, $X, $Y, $pEffect)
; IDropTarget_SetEffect($pEffect, $Value)
;   Methods of IDropTarget interface.
;
; IDropTarget_SetHWnd($pSelf, $hWnd, $hWndToReceiveMsg)
; IDropTarget_GetHWnd($pSelf, ByRef $hWnd, ByRef $hWndToReceiveMsg)
;   To set and get data store in IDropTarget interface.
;
; DataObject_QueryText($DataObj)
; DataObject_QueryFile($DataObj)
; DataObject_GetText($DataObj)
; DataObject_GetFile($DataObj)
;   Functions to handle DataObject.
;
; DragDropEvent_InfoCreate($hWnd, $DataObj, $KeyState, $X, $Y)
; DragDropEvent_Get($wParam, $Name)
;   Functions to handle infomation data for DragDropEvent.
;
; __CreateCOMInterface($sFunctionPrefix, $dtagInterface, $fNoUnknown = False, $ExtraSpace = 0)
;   To create vtable of a COM interface. Modify from _AutoItObject_ObjectFromDtag.
;
; ===============================================================================================================================

; ===============================================================================================================================
; Global const and variables
; ===============================================================================================================================

; Define 4 kind of DragDropEvent: DragEnter, DragOver, DragLeave, and Drop
Global Enum $WM_DRAGENTER = $WM_USER + 0x1001, $WM_DRAGOVER, $WM_DRAGLEAVE, $WM_DROP

; Message handler return one of these flags, which indicates what the result of the drop operation would be.
Global Enum $DROPEFFECT_NONE = 0, $DROPEFFECT_COPY = 1, $DROPEFFECT_MOVE = 2, $DROPEFFECT_LINK = 4, $DROPEFFECT_SCROLL = 0x80000000

Global Const $IID_IDataObject = "{0000010e-0000-0000-C000-000000000046}"

Global Const $dtagIDropTarget = "DragEnter hresult(ptr;uint;uint64;ptr);DragOver hresult(uint;uint64;ptr);DragLeave hresult();Drop hresult(ptr;uint;uint64;ptr);"
Global Const $dtagIDataObject = "GetData hresult(struct*;struct*);GetDataHere hresult(struct*;struct*);QueryGetData hresult(struct*);GetCanonicalFormatEtc hresult(struct*;struct*);SetData hresult(struct*;struct*;int);EnumFormatEtc hresult(uint;ptr);DAdvise hresult(struct*;uint;ptr;ptr);DUnadvise hresult(uint);EnumDAdvise hresult(ptr);"

Global Const $tagFORMATETC = "struct;uint Format;ptr ptd;uint Aspect;int lindex;uint tymed;endstruct"
Global Const $tagSTGMEDIUM = "struct;uint tymed;ptr hGlobal;ptr UnkForRelease;endstruct"
Global Const $tagDragDropEventInfo = "ptr hwnd;ptr DataObj;uint KeyState;uint x;uint y"

Global Const $__KERNEL32_DLL = DllOpen("kernel32.dll")
Global Const $__OLE32_DLL = DllOpen("ole32.dll")
Global $__IDropTargetLen = 0

; ===============================================================================================================================
; DragDropEvent startup and register functions
; ===============================================================================================================================

Func DragDropEvent_Startup()
    ; If @AutoItVersion < "3.3.8.0" Then Exit MsgBox(16, "DragDropEvent Fail", "Require AutoIt Version 3.3.8.0 at least")

    DllCall($__OLE32_DLL, "int", "OleInitialize", "ptr", 0)
EndFunc

Func DragDropEvent_Register($hWnd, $hWndToReceiveMsg = Default)
    If IsKeyword($hWndToReceiveMsg) Then $hWndToReceiveMsg = $hWnd

    Local $IDropTarget = __CreateCOMInterface("IDropTarget_", $dtagIDropTarget, True, 2) ; add 2 extra space to store hWnd
    If $IDropTarget Then
        $__IDropTargetLen = @Extended

        IDropTarget_SetHWnd($IDropTarget, $hWnd, $hWndToReceiveMsg)
        DllCall($__OLE32_DLL, "int", "RegisterDragDrop", "hwnd", $hWnd, "ptr", $IDropTarget)
    EndIf
EndFunc

Func DragDropEvent_Revoke($hWnd)
    DllCall($__OLE32_DLL, "int", "RevokeDragDrop", "hwnd", $hWnd)
EndFunc

; ===============================================================================================================================
; Methods of IDropTarget interface
; ===============================================================================================================================

Func IDropTarget_QueryInterface($pSelf, $pRIID, $pObj)
    Return 0x80004002 ; E_NOINTERFACE
EndFunc

Func IDropTarget_AddRef($pSelf)
EndFunc

Func IDropTarget_Release($pSelf)
    DllCall($__OLE32_DLL, "none", "CoTaskMemFree", "ptr", $pSelf)
EndFunc

Func IDropTarget_DragEnter($pSelf, $DataObj, $KeyState, $Point, $pEffect)
    Local $hWnd, $hWndToReceiveMsg
    IDropTarget_GetHWnd($pSelf, $hWnd, $hWndToReceiveMsg)

    Local $X = BitAND($Point, 0xFFFFFFFF), $Y = Dec(StringTrimRight(Hex($Point), 8))
    Local $Info = DragDropEvent_InfoCreate($hWnd, $DataObj, $KeyState, $X, $Y)
    Local $Ret = _SendMessage($hWndToReceiveMsg, $WM_DRAGENTER, DllStructGetPtr($Info), 0)
    DllStructSetData(DllStructCreate("dword", $pEffect), 1, $Ret)
EndFunc

Func IDropTarget_DragLeave($pSelf)
    Local $hWnd, $hWndToReceiveMsg
    IDropTarget_GetHWnd($pSelf, $hWnd, $hWndToReceiveMsg)

    Local $Info = DragDropEvent_InfoCreate($hWnd, 0, 0, 0, 0)
    _SendMessage($hWndToReceiveMsg, $WM_DRAGLEAVE, DllStructGetPtr($Info), 0)
EndFunc

Func IDropTarget_DragOver($pSelf, $KeyState, $Point, $pEffect)
    Local $hWnd, $hWndToReceiveMsg
    IDropTarget_GetHWnd($pSelf, $hWnd, $hWndToReceiveMsg)

    Local $X = BitAND($Point, 0xFFFFFFFF), $Y = Dec(StringTrimRight(Hex($Point), 8))
    Local $Info = DragDropEvent_InfoCreate($hWnd, 0, $KeyState, $X, $Y)
    Local $Ret = _SendMessage($hWndToReceiveMsg, $WM_DRAGOVER, DllStructGetPtr($Info), 0)
    DllStructSetData(DllStructCreate("dword", $pEffect), 1, $Ret)
EndFunc

Func IDropTarget_Drop($pSelf, $DataObj, $KeyState, $Point, $pEffect)
    Local $hWnd, $hWndToReceiveMsg
    IDropTarget_GetHWnd($pSelf, $hWnd, $hWndToReceiveMsg)

    Local $X = BitAND($Point, 0xFFFFFFFF), $Y = Dec(StringTrimRight(Hex($Point), 8))
    Local $Info = DragDropEvent_InfoCreate($hWnd, $DataObj, $KeyState, $X, $Y)
    Local $Ret = _SendMessage($hWndToReceiveMsg, $WM_DROP, DllStructGetPtr($Info), 0)
    DllStructSetData(DllStructCreate("dword", $pEffect), 1, $Ret)
EndFunc

Func IDropTarget_SetHWnd($pSelf, $hWnd, $hWndToReceiveMsg)
    Local $Buffer = DllStructCreate("ptr[" & ($__IDropTargetLen + 2) & "]", $pSelf)
    DllStructSetData($Buffer, 1, $hWnd, $__IDropTargetLen + 1)
    DllStructSetData($Buffer, 1, $hWndToReceiveMsg, $__IDropTargetLen + 2)
EndFunc

Func IDropTarget_GetHWnd($pSelf, ByRef $hWnd, ByRef $hWndToReceiveMsg)
    Local $Buffer = DllStructCreate("ptr[" & ($__IDropTargetLen + 2) & "]", $pSelf)
    $hWnd = DllStructGetData($Buffer, 1, $__IDropTargetLen + 1)
    $hWndToReceiveMsg = DllStructGetData($Buffer, 1, $__IDropTargetLen + 2)
EndFunc

; ===============================================================================================================================
; Functions to handle DataObject
; ===============================================================================================================================

Func DataObject_QueryText($DataObj)
    Local $IDataObj = ObjCreateInterface($DataObj, $IID_IDataObject, $dtagIDataObject)
    If Not IsObj($IDataObj) Then Return -1
    $IDataObj.AddRef()

    Local $FORMATETC = DllStructCreate($tagFORMATETC)
    DllStructSetData($FORMATETC, "Format", 13) ; 13 = CF_UNICODETEXT
    DllStructSetData($FORMATETC, "Aspect", 1)
    DllStructSetData($FORMATETC, "lindex", -1)
    DllStructSetData($FORMATETC, "tymed", 1) ; 1 = TYMED_HGLOBAL

    Local $Ret = $IDataObj.QueryGetData($FORMATETC)
    If $Ret <> 0 Then
        DllStructSetData($FORMATETC, "Format", 1) ; 1 = CF_TEXT
        $Ret = $IDataObj.QueryGetData($FORMATETC)
    EndIf
    Return $Ret
EndFunc

Func DataObject_QueryFile($DataObj)
    Local $IDataObj = ObjCreateInterface($DataObj, $IID_IDataObject, $dtagIDataObject)
    If Not IsObj($IDataObj) Then Return -1
    $IDataObj.AddRef()

    Local $FORMATETC = DllStructCreate($tagFORMATETC)
    DllStructSetData($FORMATETC, "Format", 15) ; 15 = CF_HDROP
    DllStructSetData($FORMATETC, "Aspect", 1)
    DllStructSetData($FORMATETC, "lindex", -1)
    DllStructSetData($FORMATETC, "tymed", 1) ; 1 = TYMED_HGLOBAL
    Return $IDataObj.QueryGetData($FORMATETC)
EndFunc

Func DataObject_GetText($DataObj)
    Local $IDataObj = ObjCreateInterface($DataObj, $IID_IDataObject, $dtagIDataObject)
    If Not IsObj($IDataObj) Then Return -1
    $IDataObj.AddRef()

    Local $FORMATETC = DllStructCreate($tagFORMATETC)
    Local $STGMEDIUM = DllStructCreate($tagSTGMEDIUM)
    DllStructSetData($FORMATETC, "Format", 13) ; 13 = CF_UNICODETEXT
    DllStructSetData($FORMATETC, "Aspect", 1)
    DllStructSetData($FORMATETC, "lindex", -1)
    DllStructSetData($FORMATETC, "tymed", 1) ; 1 = TYMED_HGLOBAL

    Local $IsUnicode = True
    Local $Ret = $IDataObj.QueryGetData($FORMATETC)
    If $Ret <> 0 Then
        $IsUnicode = False
        DllStructSetData($FORMATETC, "Format", 1) ; 1 = CF_TEXT
        $Ret = $IDataObj.QueryGetData($FORMATETC)
    EndIf
    If $Ret <> 0 Then Return SetError(1, 0, "")

    Local $Error = 1, $Text = ""
    If $IDataObj.GetData($FORMATETC, $STGMEDIUM) = 0 Then
        If DllStructGetData($STGMEDIUM, "tymed") = 1 Then
            Local $Ptr = _MemGlobalLock(DllStructGetData($STGMEDIUM, "hGlobal"))
            Local $Tag
            If $IsUnicode Then
                $Tag = "wchar[" & (_MemGlobalSize($Ptr) / 2) & "]"
            Else
                $Tag = "char[" & _MemGlobalSize($Ptr) & "]"
            EndIf
            $Text = DllStructGetData(DllStructCreate($Tag, $Ptr), 1)
            _MemGlobalUnlock($Ptr)
            If DllStructGetData($STGMEDIUM, "UnkForRelease") = 0 Then _MemGlobalFree($Ptr)
            $Error = 0
        EndIf
    EndIf
    Return SetError($Error, 0, $Text)
EndFunc

Func DataObject_GetFile($DataObj)
    Local $IDataObj = ObjCreateInterface($DataObj, $IID_IDataObject, $dtagIDataObject)
    If Not IsObj($IDataObj) Then Return -1
    $IDataObj.AddRef()

    Local $FORMATETC = DllStructCreate($tagFORMATETC)
    Local $STGMEDIUM = DllStructCreate($tagSTGMEDIUM)
    DllStructSetData($FORMATETC, "Format", 15) ; 15 = CF_HDROP
    DllStructSetData($FORMATETC, "Aspect", 1)
    DllStructSetData($FORMATETC, "lindex", -1)
    DllStructSetData($FORMATETC, "tymed", 1) ; 1 = TYMED_HGLOBAL

    Local $Error = 1, $FileList = ""
    If $IDataObj.GetData($FORMATETC, $STGMEDIUM) = 0 Then
        If DllStructGetData($STGMEDIUM, "tymed") = 1 Then
            Local $Ptr = _MemGlobalLock(DllStructGetData($STGMEDIUM, "hGlobal"))
            Local $StrPtr = $Ptr + DllStructGetData(DllStructCreate("dword", $Ptr), 1)
            Do
                Local $Ret = DllCall($__KERNEL32_DLL, "uint", "lstrlenW", "ptr", $StrPtr)
                Local $StrLen = $Ret[0]
                If $StrLen Then
                    Local $Str = DllStructGetData(DllStructCreate("wchar[" & $StrLen & "]", $StrPtr), 1)
                    $FileList &= $Str & "|"
                    $StrPtr += $StrLen * 2 + 2
                EndIf
            Until $StrLen = 0
            If StringRight($FileList, 1) = "|" Then $FileList = StringTrimRight($FileList, 1)
            _MemGlobalUnlock($Ptr)
            If DllStructGetData($STGMEDIUM, "UnkForRelease") = 0 Then _MemGlobalFree($Ptr)
            $Error = 0
        EndIf
    EndIf
    Return SetError($Error, 0, $FileList)
EndFunc

; ===============================================================================================================================
; Functions to handle infomation data for DragDropEvent
; ===============================================================================================================================

Func DragDropEvent_InfoCreate($hWnd, $DataObj, $KeyState, $X, $Y)
    Local $Info = DllStructCreate($tagDragDropEventInfo)
    DllStructSetData($Info, "hwnd", $hWnd)
    DllStructSetData($Info, "DataObj", $DataObj)
    DllStructSetData($Info, "KeyState", $KeyState)
    DllStructSetData($Info, "x", $X)
    DllStructSetData($Info, "y", $Y)
    Return $Info
EndFunc

Func DragDropEvent_Get($wParam, $Name)
    If Not $wParam Then Return SetError(1, 0, 0)
    Local $Info = DllStructCreate($tagDragDropEventInfo, $wParam)
    Return DllStructGetData($Info, $Name)
EndFunc

Func DragDropEvent_GetHWnd($wParam)
    Local $Ret = DragDropEvent_Get($wParam, "hwnd")
    Return SetError(@Error, 0, $Ret)
EndFunc

Func DragDropEvent_GetX($wParam)
    Local $Ret = DragDropEvent_Get($wParam, "x")
    Return SetError(@Error, 0, $Ret)
EndFunc

Func DragDropEvent_GetY($wParam)
    Local $Ret = DragDropEvent_Get($wParam, "y")
    Return SetError(@Error, 0, $Ret)
EndFunc

Func DragDropEvent_GetKeyState($wParam)
    Local $Ret = DragDropEvent_Get($wParam, "KeyState")
    Return SetError(@Error, 0, $Ret)
EndFunc

Func DragDropEvent_IsText($wParam)
    Local $DataObj = DragDropEvent_Get($wParam, "DataObj")
    Return DataObject_QueryText($DataObj) = 0
EndFunc

Func DragDropEvent_IsFile($wParam)
    Local $DataObj = DragDropEvent_Get($wParam, "DataObj")
    Return DataObject_QueryFile($DataObj) = 0
EndFunc

Func DragDropEvent_GetText($wParam)
    Local $DataObj = DragDropEvent_Get($wParam, "DataObj")
    Return DataObject_GetText($DataObj)
EndFunc

Func DragDropEvent_GetFile($wParam)
    Local $DataObj = DragDropEvent_Get($wParam, "DataObj")
    Return DataObject_GetFile($DataObj)
EndFunc

; ===============================================================================================================================
; Functions to create COM interface
; ===============================================================================================================================

Func __CreateCOMInterface($sFunctionPrefix, $dtagInterface, $fNoUnknown = False, $ExtraSpace = 0)
    ; Original is _AutoItObject_ObjectFromDtag in AutoItObject.au3 by AutoItObject-Team
    ; Modify by Ward

    Local Const $__PtrSize = DllStructGetSize(DllStructCreate('ptr', 1))
    Local Const $dtagIUnknown = "QueryInterface hresult(ptr;ptr*);AddRef dword();Release dword();"

    If $fNoUnknown Then $dtagInterface = $dtagIUnknown & $dtagInterface
    Local $sMethods = StringReplace(StringRegExpReplace($dtagInterface, "\h*(\w+)\h*(\w+\*?)\h*(\((.*?)\))\h*(;|;*\z)", "$1\|$2;$4" & @LF), ";" & @LF, @LF)
    If $sMethods = $dtagInterface Then $sMethods = StringReplace(StringRegExpReplace($dtagInterface, "\h*(\w+)\h*(;|;*\z)", "$1\|" & @LF), ";" & @LF, @LF)
    $sMethods = StringTrimRight($sMethods, 1)
    $sMethods = StringReplace(StringReplace(StringReplace(StringReplace($sMethods, "object", "idispatch"), "variant*", "ptr"), "hresult", "long"), "bstr", "ptr")

    Local $aMethods = StringSplit($sMethods, @LF, 3)
    Local $iUbound = UBound($aMethods)
    Local $sMethod, $aSplit, $sNamePart, $aTagPart, $sTagPart, $sRet, $sParams, $hCallback

    Local $AllocSize = $__PtrSize * ($iUbound + 1 + $ExtraSpace)
    Local $Ret = DllCall($__OLE32_DLL, "ptr", "CoTaskMemAlloc", "uint_ptr", $AllocSize)
    If @error Then Return SetError(1, 0, 0)
    Local $AllocPtr = $Ret[0]

    Local $tInterface = DllStructCreate("ptr[" & $iUbound + 1 & "]", $AllocPtr)
    If @error Then Return SetError(1, 0, 0)
    For $i = 0 To $iUbound - 1
        $aSplit = StringSplit($aMethods[$i], "|", 2)
        If UBound($aSplit) <> 2 Then ReDim $aSplit[2]
        $sNamePart = $aSplit[0]
        $sTagPart = $aSplit[1]
        $sMethod = $sFunctionPrefix & $sNamePart
        $aTagPart = StringSplit($sTagPart, ";", 2)
        $sRet = $aTagPart[0]
        $sParams = StringReplace($sTagPart, $sRet, "", 1)
        $sParams = "ptr" & $sParams

        ; To avoid repeat allocate the same callback, a memory leakage
        $hCallback = Eval(":Callback:" & $sMethod)
        If Not $hCallback Then
            $hCallback = DllCallbackRegister($sMethod, $sRet, $sParams)
            Assign(":Callback:" & $sMethod, $hCallback, 2)
        EndIf
        DllStructSetData($tInterface, 1, DllCallbackGetPtr($hCallback), $i + 2)
    Next
    DllStructSetData($tInterface, 1, $AllocPtr + $__PtrSize) ; Interface method pointers are actually pointer size away
    Return SetExtended($iUbound + 1, $AllocPtr) ; Return interface size as @Extended for access extra space
EndFunc

 

Based on the UDF, I created a new Function "DragDropEvent_GetVirtualFile" (see below), that is supposed to retreive the Filecontents. 

I managed to successfully retreive the amount of Mails and their titles (="filenames") but I am struggling to get to the filecontents.

To test for yourself: Run The script, and Drag 1 or more mails from outlook onto the button. Then check the Console-Output.

Spoiler
; ===============================================================================================================================
; File     : DragDropEvent_Example_2.au3 (2012/3/9)
; Purpose  : Demonstrate the usage of DragDropEvent UDF
; Author   : Ward
; ===============================================================================================================================

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include "DragDropEvent.au3"
#include <WinAPI.au3>

Global $Button3

DragDropEvent_Startup()
Main()

Exit

Func Main()
    Local $MainWin = GUICreate("DragDropEvent Example", 460, 400, -1, -1, -1, $WS_EX_TOPMOST)
    GUISetFont(12, 900)

    $Button3 = GUICtrlCreateButton("Drop Outlook Mail", 20, 20, 420, 360)
    DragDropEvent_Register(GUICtrlGetHandle($Button3), $MainWin)

    GUIRegisterMsg($WM_DRAGENTER, "OnDragDrop")
    GUIRegisterMsg($WM_DRAGOVER, "OnDragDrop")
    GUIRegisterMsg($WM_DRAGLEAVE, "OnDragDrop")
    GUIRegisterMsg($WM_DROP, "OnDragDrop")

    GUISetState(@SW_SHOW)
    While 1
        Local $Msg = GUIGetMsg()
        Switch $Msg

            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd

    GUIDelete()
EndFunc   ;==>Main

Func OnDragDrop($hWnd, $Msg, $wParam, $lParam)
    Static $DropAccept

    Switch $Msg
        Case $WM_DRAGLEAVE
            Return $DROPEFFECT_NONE
        Case $WM_DROP
            DragDropEvent_GetVirtualFile($wParam)
            ContinueCase
        Case Else
            Return $DROPEFFECT_COPY

    EndSwitch
EndFunc   ;==>OnDragDrop



Func DragDropEvent_GetVirtualFile($wParam)


    Local Enum $TYMED_HGLOBAL = 1, _
            $TYMED_FILE, _
            $TYMED_ISTREAM, _
            $TYMED_ISTORAGE, _
            $TYMED_GDI, _
            $TYMED_MFPICT, _
            $TYMED_ENHMF, _
            $TYMED_NULL


    Local $tagCLSID = $tagGUID
    Local $tagSIZEL = "struct;long cx;long cy;endstruct;"
    Local $tagPOINTL = "struct;long x;long y;endstruct;"
    Local $tagFILETIME = "struct;DWORD dwLowDateTime;DWORD dwHighDateTime;endstruct;"

    Local $tagFILEDESCRIPTORW = _
            "struct;" & _
            "DWORD dwFlags;" & _
            "BYTE clsid[" & DllStructGetSize(DllStructCreate($tagCLSID)) & "];" & _
            "BYTE sizel[" & DllStructGetSize(DllStructCreate($tagSIZEL)) & "];" & _
            "BYTE pointl[" & DllStructGetSize(DllStructCreate($tagPOINTL)) & "];" & _
            "DWORD dwFileAttributes;" & _
            "BYTE ftFileCreationTime[" & DllStructGetSize(DllStructCreate($tagFILETIME)) & "];" & _
            "BYTE ftLastAccessTime[" & DllStructGetSize(DllStructCreate($tagFILETIME)) & "];" & _
            "BYTE ftLastWriteTime[" & DllStructGetSize(DllStructCreate($tagFILETIME)) & "];" & _
            "dword nFileSizeHigh;" & _
            "dword nFileSizeLow;" & _
            "WCHAR cFileName[260];" & _
            "endstruct;"

    Local $tagFILEDESCRIPTORA = _
            "struct;" & _
            "DWORD dwFlags;" & _
            "BYTE clsid[" & DllStructGetSize(DllStructCreate($tagCLSID)) & "];" & _
            "BYTE sizel[" & DllStructGetSize(DllStructCreate($tagSIZEL)) & "];" & _
            "BYTE pointl[" & DllStructGetSize(DllStructCreate($tagPOINTL)) & "];" & _
            "DWORD dwFileAttributes;" & _
            "BYTE ftFileCreationTime[" & DllStructGetSize(DllStructCreate($tagFILETIME)) & "];" & _
            "BYTE ftLastAccessTime[" & DllStructGetSize(DllStructCreate($tagFILETIME)) & "];" & _
            "BYTE ftLastWriteTime[" & DllStructGetSize(DllStructCreate($tagFILETIME)) & "];" & _
            "dword nFileSizeHigh;" & _
            "dword nFileSizeLow;" & _
            "CHAR cFileName[260];" & _
            "endstruct;"

    Local $tagFILEGROUPDESCRIPTORW = _
            "struct;" & _
            "UINT cItems;" & _
            "BYTE fgd[" & DllStructGetSize(DllStructCreate($tagFILEDESCRIPTORW)) & "];" & _
            "endstruct;"

    Local $tagFILEGROUPDESCRIPTORA = _
            "struct;" & _
            "UINT cItems;" & _
            "BYTE fgd[" & DllStructGetSize(DllStructCreate($tagFILEDESCRIPTORA)) & "];" & _
            "endstruct;"


    Local $FORMATETC, $STGMEDIUM, $hMemGlobalPtr
    Local $tFileGroupDescriptorA, $tFileDescriptorA
    Local $tFileGroupDescriptorW, $tFileDescriptorW
    Local $IDataObj
    Local $iDropCount

    Local $DataObj = DragDropEvent_Get($wParam, "DataObj")

    $IDataObj = ObjCreateInterface($DataObj, $IID_IDataObject, $dtagIDataObject)
    If Not IsObj($IDataObj) Then Return -1
    $IDataObj.AddRef()


    ; ----------------------------------------------------------------------------
    ; Format FileGroupDescriptorW to query amount & mail titles (=filenames) from
    ; DataObject.
    ; ----------------------------------------------------------------------------
    $FORMATETC = DllStructCreate($tagFORMATETC)
    $FORMATETC.Format = _RegisterFormat("FileGroupDescriptorW")
    $FORMATETC.Aspect = 1
    $FORMATETC.lindex = -1
    $FORMATETC.tymed = $TYMED_HGLOBAL

    ; ----------------------------------------------------------------------------
    ; QueryGetData: Find out, if Outlook supports this Format "FileGroupDescriptorW"
    ; = Determine whether a subsequent call of "GetData" would likely succeed.
    ; ----------------------------------------------------------------------------
    If $IDataObj.QueryGetData($FORMATETC) = 0 Then

        ; Create Result-Structure "STGMEDIUM"
        $STGMEDIUM = DllStructCreate($tagSTGMEDIUM)
        If $IDataObj.GetData($FORMATETC, $STGMEDIUM) = 0 Then

            ; we didn't leave Outlook much choice, so the resulting STGMEDIUM.tymed should match
            ; the one we supplied with FORMATETC
            If $STGMEDIUM.tymed = $TYMED_HGLOBAL Then

                ; $STGMEDIUM.hGlobal is a pointer to global Memory provided by Outlook, create FileGroupDescriptorW-Structure
                ; (which is what we requested) at that location so we can access the result data.
                Local $hMemGlobalPtr = _MemGlobalLock($STGMEDIUM.hGlobal)
                $tFileGroupDescriptorW = DllStructCreate($tagFILEGROUPDESCRIPTORW, $hMemGlobalPtr)
                If $tFileGroupDescriptorW <> 0 Then

                    ; for each item, print out the data.
                    ConsoleWrite("$tFileGroupDescriptor.cItems: " & $tFileGroupDescriptorW.cItems & @CRLF)

                    $iDropCount = $tFileGroupDescriptorW.cItems
                    For $i = 1 To $iDropCount
                        ConsoleWrite(@CRLF & @CRLF)
                        Local $iOffset = DllStructGetSize(DllStructCreate($tagFILEDESCRIPTORW)) * ($i - 1)
                        $tFileDescriptorW = DllStructCreate($tagFILEDESCRIPTORW, DllStructGetPtr($tFileGroupDescriptorW, 'fgd') + $iOffset)

                        If $tFileDescriptorW <> 0 Then
                            ConsoleWrite("$tFileDescriptorW.dwFlags: " & $tFileDescriptorW.dwFlags & @CRLF)

                            $tCLSID = DllStructCreate($tagCLSID, DllStructGetPtr($tFileDescriptorW, 'clsid'))
                            ConsoleWrite("$tFileDescriptorW.clsid: " & _WinAPI_StringFromGUID($tCLSID) & @CRLF)


                            $tSIZEL = DllStructCreate($tagSIZEL, DllStructGetPtr($tFileDescriptorW, 'sizel'))
                            ConsoleWrite("$tFileDescriptorW.sizel: " & $tSIZEL.cx & ", " & $tSIZEL.cy & @CRLF)

                            $tPOINTL = DllStructCreate($tagPOINTL, DllStructGetPtr($tFileDescriptorW, 'pointl'))
                            ConsoleWrite("$tFileDescriptorW.pointl: " & $tPOINTL.x & ", " & $tPOINTL.y & @CRLF)

                            ConsoleWrite("$tFileDescriptorW.dwFileAttributes: " & $tFileDescriptorW.dwFileAttributes & @CRLF)

                            $tFILECREATIONTIME = DllStructCreate($tagFILETIME, DllStructGetPtr($tFileDescriptorW, 'ftFileCreationTime'))
                            ConsoleWrite("$tFileDescriptorW.ftFileCreationTime: " & $tFILECREATIONTIME.dwLowDateTime & ", " & $tFILECREATIONTIME.dwHighDateTime & @CRLF)

                            $tLASTACCESSTIME = DllStructCreate($tagFILETIME, DllStructGetPtr($tFileDescriptorW, 'ftLastAccessTime'))
                            ConsoleWrite("$tFileDescriptorW.ftLastAccessTime: " & $tLASTACCESSTIME.dwLowDateTime & ", " & $tLASTACCESSTIME.dwHighDateTime & @CRLF)

                            $tLASTWRITETIME = DllStructCreate($tagFILETIME, DllStructGetPtr($tFileDescriptorW, 'ftLastWriteTime'))
                            ConsoleWrite("$tFileDescriptorW.ftLastWriteTime: " & $tLASTWRITETIME.dwLowDateTime & ", " & $tLASTWRITETIME.dwHighDateTime & @CRLF)

                            ConsoleWrite("$tFileDescriptorW.nFileSizeHigh: " & $tFileDescriptorW.nFileSizeHigh & @CRLF)
                            ConsoleWrite("$tFileDescriptorW.nFileSizeLow: " & $tFileDescriptorW.nFileSizeLow & @CRLF)
                            ConsoleWrite("$tFileDescriptorW.cFileName: " & $tFileDescriptorW.cFileName & @CRLF)
                        EndIf
                    Next
                EndIf

                _MemGlobalUnlock($hMemGlobalPtr)
                If $STGMEDIUM.UnkForRelease = 0 Then _MemGlobalFree($hMemGlobalPtr)


            EndIf
        EndIf

    EndIf



    ; --------------------------------------------------------
    ; now get the filecontents:

    For $i = 0 To $iDropCount - 1
        $FORMATETC = DllStructCreate($tagFORMATETC)
        DllStructSetData($FORMATETC, "Format", _RegisterFormat("FileContents")) ; we want filecontents now!
        DllStructSetData($FORMATETC, "Aspect", 1)
        DllStructSetData($FORMATETC, "lindex", $i) ; specify index!
        DllStructSetData($FORMATETC, "tymed", BitOR($TYMED_HGLOBAL, $TYMED_ISTREAM, $TYMED_ISTORAGE)) ; doesnt work with just TYMED_HGLOBAL

        If $IDataObj.QueryGetData($FORMATETC) = 0 Then
            ConsoleWrite('+> QueryGetData(...) => success for FileContents' & @CRLF) ;### Debug Console

            Local $STGMEDIUM = DllStructCreate($tagSTGMEDIUM)
            $iResult = $IDataObj.GetData($FORMATETC, $STGMEDIUM)


            If $iResult = 0 Then
                ConsoleWrite('GetData(...) => success for FileContents' & @CRLF) ;### Debug Console
                If $STGMEDIUM.tymed = 1 Then
                    Local $Ptr = _MemGlobalLock($STGMEDIUM.hGlobal)

                    $tagFILECONTENTS = "byte[" & _MemGlobalSize($STGMEDIUM.hGlobal) & "];"
                    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $tagFILECONTENTS = ' & $tagFILECONTENTS & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
                    $tFileContents = DllStructCreate($tagFILECONTENTS, $Ptr)

                    _MemGlobalUnlock($Ptr)
                    If $STGMEDIUM.UnkForRelease = 0 Then _MemGlobalFree($Ptr)

                EndIf
            Else
                ConsoleWrite('!> $IDataObj.GetData(...) => Error 0x' & Hex($iResult) & @CRLF & '>Error code: ' & @error & @CRLF)         ;### Debug Console
            EndIf
        Else
            ConsoleWrite("FileContents error @index " & $i & @CRLF)
        EndIf
    Next

;~     Return $vReturn
EndFunc   ;==>DragDropEvent_GetVirtualFile

Func _RegisterFormat($sFormat)
    ; copied from Clipboard.au3
    Local $aResult = DllCall("user32.dll", "uint", "RegisterClipboardFormatW", "wstr", $sFormat)
    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult[0]
EndFunc   ;==>_RegisterFormat

 

The output I am getting is this (as you can see, it errors when calling IDataObj.GetData() )

$tFileGroupDescriptor.cItems: 1


$tFileDescriptorW.dwFlags: 0
$tFileDescriptorW.clsid: {00000000-0000-0000-0000-000000000000}
$tFileDescriptorW.sizel: 0, 0
$tFileDescriptorW.pointl: 0, 0
$tFileDescriptorW.dwFileAttributes: 0
$tFileDescriptorW.ftFileCreationTime: 0, 0
$tFileDescriptorW.ftLastAccessTime: 0, 0
$tFileDescriptorW.ftLastWriteTime: 0, 0
$tFileDescriptorW.nFileSizeHigh: 0
$tFileDescriptorW.nFileSizeLow: 0
$tFileDescriptorW.cFileName: xxxyyyyzzzz this is a mail subject!.msg
+> QueryGetData(...) => success for FileContents

!> $IDataObj.GetData(...) => Error 0x80040064

I was following this example: https://www.codeproject.com/Articles/28209/Outlook-Drag-and-Drop-in-C

Interestingly enough, the QueryGetData at line 226 also fails, if the format specified in line 224 does not contain TYMED_ISTORAGE. (hGLOBAL doesn't work at all). 

 

All the implementations I found online seem to work. 

https://github.com/tonyfederer/OutlookFileDrag

https://stackoverflow.com/questions/4756845/how-to-implement-drag-drop-from-outlook-mail-or-thunderbird-to-a-delphi-form

https://mycsharp.de/forum/threads/114641/outlook-anhang-per-dragdrop-in-wpf-ausle

https://stackoverflow.com/questions/8709076/drag-and-drop-multiple-attached-file-from-outlook-to-c-sharp-window-form

Also, this is a nice summary of the sender's perspective: https://devblogs.microsoft.com/oldnewthing/20080318-00/?p=23083

Can anyone try help me with this? I feel like I've tried everyhting.

Best Regards,

Edited by SEuBo
Changed title / added links
Link to post
Share on other sites

0x80040064 stands for Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC))

Unfortunately this is all I know :(

My UDFs and Tutorials:

Spoiler

UDFs:
Active Directory (NEW 2021-06-05 - Version 1.5.4.0) - Download - General Help & Support - Example Scripts - Wiki
OutlookEX (NEW 2021-06-14 - Version 1.6.5.0) - Download - General Help & Support - Example Scripts - Wiki
OutlookEX_GUI (2021-04-13 - Version 1.4.0.0) - Download
Outlook Tools (2019-07-22 - Version 0.6.0.0) - Download - General Help & Support - Wiki
ExcelChart (2017-07-21 - Version 0.4.0.1) - Download - General Help & Support - Example Scripts
PowerPoint (2017-06-06 - Version 0.0.5.0) - Download - General Help & Support
Excel - Example Scripts - Wiki
Word - Wiki
Task Scheduler (2019-12-03 - Version 1.5.1.0) - Download - General Help & Support - Wiki

Tutorials:
ADO - Wiki, WebDriver - Wiki

 

Link to post
Share on other sites

Hey!

Thanks for the reply. That's too bad - I actually was hoping for an outlook professional like you to jump in and solve this mystery. :) 
I know that I could work around the issue by using your OutlookEx UDF; on drop, I'd just save the active mails to a temp file. 

But it would be so awesome to build a more general solution which would support all kind of Drag&Drop Sources. Maybe someone else has a bright idea!

I've read through the msdn documentation again and again, but I can't figure it out.

CFSTR_FILECONTENTS

Spoiler

This format identifier is used with the CFSTR_FILEDESCRIPTOR format to transfer data as if it were a file, regardless of how it is actually stored. The data consists of an STGMEDIUM structure that represents the contents of one file. The file is normally represented as a stream object, which avoids having to place the contents of the file in memory. In that case, the tymed member of the STGMEDIUM structure is set to TYMED_ISTREAM, and the file is represented by an IStream interface. The file can also be a storage or global memory object (TYMED_ISTORAGE or TYMED_HGLOBAL). The associated CFSTR_FILEDESCRIPTOR format contains a FILEDESCRIPTOR structure for each file that specifies the file's name and attributes.

The target treats the data associated with a CFSTR_FILECONTENTS format as if it were a file. When the target calls IDataObject::GetData to extract the data, it specifies a particular file by setting the lindex member of the FORMATETC structure to the zero-based index of the file's FILEDESCRIPTOR structure in the accompanying CFSTR_FILEDESCRIPTOR format. The target then uses the returned interface pointer or global memory handle to extract the data.

CFSTR_FILEDESCRIPTOR

Spoiler

This format identifier is used with the CFSTR_FILECONTENTS format to transfer data as a group of files. These two formats are the preferred way to transfer Shell objects that are not stored as file-system files. For example, these formats can be used to transfer a group of email messages as individual files, even though each email is actually stored as a block of data in a database. The data consists of an STGMEDIUM structure that contains a global memory object. The structure's hGlobal member points to a FILEGROUPDESCRIPTOR structure that is followed by an array containing one FILEDESCRIPTOR structure for each file in the group. For each FILEDESCRIPTOR structure, there is a separate CFSTR_FILECONTENTS format that contains the contents of the file. To identify a particular file's CFSTR_FILECONTENTS format, set the lIndex value of the FORMATETC structure to the zero-based index of the file's FILEDESCRIPTOR structure.

The CFSTR_FILEDESCRIPTOR format is commonly used to transfer data as if it were a group of files, regardless of how it is actually stored. From the target's perspective, each CFSTR_FILECONTENTS format represents a single file and is treated accordingly. However, the source can store the data in any way it chooses. While a CSFTR_FILECONTENTS format might correspond to a single file, it could also, for example, represent data extracted by the source from a database or text document.

 

 

If anyone sees the issue in the code from the first post - feel free to speak up :)

Link to post
Share on other sites

Does noone have an idea? :( 

I just can't understand why it's not working, because I figured if I drag and drop something from the explorer (instead of outlook), the GetData()-Method returns an IStorage-Interface (still to be implemented.). With outlook it just fails... Can't get my head around that.

Ideas appreciated!

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...