Part 3 - Implementing IDataObject
OLE Drag and Drop
Updated 6 Dec 2006
Many thanks to Davide Chiodi from Italy who has very kindly converted the data-obect code into a Pure C implementation - the download link is available at the bottom of this article!
In the last part of the tutorial we looked at how to access the Windows clipboard using OLE and the IDataObject. In this part we will be implementing the IDataObject interface, and using our completed data object to store the text "Hello World" into the Windows clipboard.
Creating a COM interface - IDataObject
In order to create our own COM object, we need to define a C++ class which implements all of these functions, and in order for the COM virtual-function table to be automatically included for us, we will use C++ class inheritance:
class CDataObject : public IDataObject
{
public:
// IUnknown members
HRESULT __stdcall QueryInterface (REFIID iid, void ** ppvObject);
ULONG __stdcall AddRef (void);
ULONG __stdcall Release (void);
// IDataObject members
HRESULT __stdcall GetData (FORMATETC *pFormatEtc, STGMEDIUM *pmedium);
HRESULT __stdcall GetDataHere (FORMATETC *pFormatEtc, STGMEDIUM *pmedium);
HRESULT __stdcall QueryGetData (FORMATETC *pFormatEtc);
HRESULT __stdcall GetCanonicalFormatEtc (FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut);
HRESULT __stdcall SetData (FORMATETC *pFormatEtc, STGMEDIUM *pMedium, BOOL fRelease);
HRESULT __stdcall EnumFormatEtc (DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc);
HRESULT __stdcall DAdvise (FORMATETC *pFormatEtc, DWORD advf, IAdviseSink *, DWORD *);
HRESULT __stdcall DUnadvise (DWORD dwConnection);
HRESULT __stdcall EnumDAdvise (IEnumSTATDATA **ppEnumAdvise);
// Constructor / Destructor
CDataObject(FORMATETC *fmtetc, STGMEDIUM *stgmed, int count);
~CDataObject();
private:
// any private members and functions
LONG m_lRefCount;
int LookupFormatEtc(FORMATETC *pFormatEtc);
};
Notice that all of the IDataObject members have been listed - even the IUnknown interface members. This is because we are now implementing an entire COM object, so every member function must be included in the correct order.
With the IUnknown functions already visited in a previous tutorial, we can move onto the IDataObject functions. There is some good news and some bad news. The good news is, not all of the functions need to be implemented! Out of the nine functions, only three are required for OLE drag and drop, so this cuts down our work enormously.
The bad news is once we've implemented the IDataObject methods, we need to implement an entirely separate COM interface - the IEnumFORMATETC interface. We're a little way off this step yet, so let's start with simply allocating a new instance of IDataObject.
Constructing IDataObject
The IDataObject's main task is to allow a "consumer" to query it for data. These queries will take the form of calls to either QueryData or EnumFormatEtc. Therefore the IDataObject needs to know what data formats it should store, and when a consumer asks for the data, it should be able to provide it.
We therefore need to find some method to populate the IDataObject with real pieces of data and also tell it what the data is, in the form of FORMATETC structures.
The IDataObject will be populated with data during the call to it's C++ class constructor. For more flexibility it may make sense to use the IDataObject::SetData routine to perform this task, but for our simple implementation using the constructor makes sense for now.
CDataObject::CDataObject(FORMATETC *fmtetc, STGMEDIUM *stgmed, int count)
{
// reference count must ALWAYS start at 1
m_lRefCount = 1;
m_nNumFormats = count;m_pFormatEtc = new FORMATETC[count];
m_pStgMedium = new STGMEDIUM[count];for(int i = 0; i < count; i++)
{
m_pFormatEtc[i] = fmtetc[i];
m_pStgMedium[i] = stgmed[i];
}
}
The constructor performs two important tasks. The first is to initialize the COM object's reference count to 1. I see alot of incorrect COM code where reference counts begin at zero. The COM specifications clearly state that a COM object must begin life with a reference count of 1. If you think about it, a reference count of zero means that the COM object should be deleted, so it should never be initialized to this value.
The second task is to make a private copy of the FORMATETC and STGMEDIUM structures specified in the class constructor. The data object won't take ownership of the data inside each STGMEDIUM structure, it will merely reference it, and duplicate the data only when requested during a call to GetData.
Creating IDataObject
Now that we have a well-defined constructor for IDataObject, we can write a wrapper function which will hide the class details:
HRESULT CreateDataObject(FORMATETC *fmtetc, STGMEDIUM *stgmeds, UINT count, IDataObject **ppDataObject)
{
if(ppDataObject == 0)
return E_INVALIDARG;*ppDataObject = new CDataObject(fmtetc, stgmeds, count);
return (*ppDataObject) ? S_OK : E_OUTOFMEMORY;
}
So creating an IDataObject is now very simple:
FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgmed = { TYMED_HGLOBAL, { 0 }, 0 };
stgmed.hGlobal = StringToHandle("Hello, World!");
IDataObject *pDataObject;
CreateDataObject(&fmtetc, &stgmed, 1, &pDataObject);
Alot of implementations of IDataObject include alot of application-specific code inside the interface which performs the memory allocations. The idea behind this implementation is to provide a generic IDataObject which can be used in a variety of different applications. OK, so a little bit of work needs to be done up-front to create the FORMATETC and STGMEDIUM structures before creating the data object, but this can be easily isolated and doesn't pollute the interface code.
IDataObject::QueryGetData
This member function is called whenever an application wants to test our IDataObject to see if it contains a specific type of data. A pointer to a FORMATETC structure is passed as an argument, and it is the task of IDataObject::QueryGetData to inspect this structure and return a value to indicate if the requested data is available or not.
HRESULT __stdcall CDataObject::QueryGetData(FORMATETC *pFormatEtc)
{
return (LookupFormatEtc(pFormat) == -1) ? DV_E_FORMATETC : S_OK;
}
The QueryGetData function is very simple in this case. We pass off all the work to a private helper function - LookupFormatEtc:
int CDataObject::LookupFormatEtc(FORMATETC *pFormatEtc)
{
// check each of our formats in turn to see if one matches
for(int i = 0; i < m_nNumFormats; i++)
{
if((m_pFormatEtc[i].tymed & pFormatEtc->tymed) &&
m_pFormatEtc[i].cfFormat == pFormatEtc->cfFormat &&
m_pFormatEtc[i].dwAspect == pFormatEtc->dwAspect)
{
// return index of stored format
return i;
}
}// error, format not found
return -1;
}
The helper function above tries to match the specified FORMATETC structure against one of the available structures belonging to our data object. If it finds one that matches, it simply returns an index to the appropriate entry in the m_pFormatEtc array. If no match is found, an error value of -1 is returned.
Note the use of the bitwise-AND operator in the if-clause:
if( m_pFormatEtc[i].tymed & pFormatEtc->tymed )
The AND operator is used here because the FORMATETC::tymed member is actually a bit-flag which can contain more than one value. For example, the caller of QueryGetData could quite legitimetly specify a FORMATETC::tymed value of (TYMED_HGLOBAL | TYMED_ISTREAM), which basically means "Do you support HGLOBAL or IStream?".
IDataObject::GetData
The GetData function is similar in many ways to QueryGetData, the exception being that if the requested data format is supported, it must be returned into the specified storage-medium structure.
HRESULT __stdcall CDataObject::GetData (FORMATETC *pFormatEtc, STGMEDIUM *pStgMedium)
{
int idx;
// try to match the specified FORMATETC with one of our supported formats
if((idx = LookupFormatEtc(pFormatEtc)) == -1)
return DV_E_FORMATETC;// found a match - transfer data into supplied storage medium
pMedium->tymed = m_pFormatEtc[idx].tymed;
pMedium->pUnkForRelease = 0;
// copy the data into the caller's storage medium
switch(m_pFormatEtc[idx].tymed)
{
case TYMED_HGLOBAL:pMedium->hGlobal = DupGlobalMem(m_pStgMedium[idx].hGlobal);
break;default:
return DV_E_FORMATETC;
}
return S_OK;
}
The same internal helper function LookupFormatEtc is used to check if the requested data format is supported. If it is, then the appropriate STGMEDIUM data is copied into the caller-supplied structure.
Note that call to the DupGlobalMem routine. This is a helper function which returns a duplicate of the specified HGLOBAL memory handle, and is required because each call to GetData must result in a fresh copy of the data.
HGLOBAL DupGlobalMemMem(HGLOBAL hMem)
{
DWORD len = GlobalSize(hMem);
PVOID source = GlobalLock(hMem);
PVOID dest = GlobalAlloc(GMEM_FIXED, len);memcpy(dest, source, len);
GlobalUnlock(hMem);
return dest;
}
We will need similar routines to support the other TYMED_xxx storage types. For now the only additional format I imagine being implemented is IStream.
IDataObject::EnumFormatEtc
This is the last member that requires any real programming effort. Its unfortunate that it whilst this member function is so simple to implement, it also requires us to start writing the IEnumFORMATETC object as well.
HRESULT __stdcall CDataObject::EnumFormatEtc (DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc)
{
// only the get direction is supported for OLE
if(dwDirection == DATADIR_GET)
{
// for Win2k+ you can use the SHCreateStdEnumFmtEtc API call, however
// to support all Windows platforms we need to implement IEnumFormatEtc ourselves.
return CreateEnumFormatEtc(m_NumFormats, m_FormatEtc, ppEnumFormatEtc);
}
else
{
// the direction specified is not supported for drag+drop
return E_NOTIMPL;
}
}
If you look at the code comment above, you can see mention of the SHCreateStdEnumFmtEtc API call. What this does is create an IEnumFORMATETC interface on our behalf, requiring no work from ourselves. Unfortunately this API is only available on Windows 2000 and above, so we have to provide an alternative method to create an IEnumFORMATETC object.
Therefore in the next tutorial we will provide a full implementation of CreateEnumFormatEtc, a replacement for the Shell API call.
Unsupported IDataObject functions
There are still a number of IDataObject functions that need to be implemented. Whilst every function must be a valid routine, there is a simple method to indicate to OLE that we don't support the functionality that these routines might offer outside the world of drag and drop.
The IDataObject::DAdvise, IDataObject::EnumDAdvise and IDataObject::DUnadvise functions simply need to return the value OLE_E_ADVISENOTSUPPORTED.
HRESULT CDataObject::DAdvise (FORMATETC *pFormatEtc, DWORD advf, IAdviseSink *pAdvSink,
DWORD *pdwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
}HRESULT CDataObject::DUnadvise (DWORD dwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
}HRESULT CDataObject::EnumDAdvise (IEnumSTATDATA **ppEnumAdvise)
{
return OLE_E_ADVISENOTSUPPORTED;
}
IDataObject::GetDataHere can only be implemented if the IStream and IStorage interfaces are supported by the data object. In our case we only support HGLOBAL data, so returning DATA_E_FORMATETC seems a sensible choice.
HRESULT CDataObject::GetDataHere (FORMATETC *pFormatEtc, STGMEDIUM *pMedium)
{
return DATA_E_FORMATETC;
}
IDataObject::SetData and IDataObject::GetCanonicalFormatEtc are also simple to implement - the value E_NOTIMPL can be returned in this case. One special note about GetCanonicalFormatEtc - even though we return an error value, the output FORMATETC structure's "ptd" member (the pointer-to-DVTARGETDEVICE) must be set to zero:
HRESULT CDataObject::GetCanonicalFormatEtc (FORMATETC *pFormatEct, FORMATETC *pFormatEtcOut)
{
// Apparently we have to set this field to NULL even though we don't do anything else
pFormatEtcOut->ptd = NULL;
return E_NOTIMPL;
}HRESULT CDataObject::SetData (FORMATETC *pFormatEtc, STGMEDIUM *pMedium, BOOL fRelease)
{
return E_NOTIMPL;
}
Adding Data to the Clipboard
OK, so here's a little program to add "Hello World" to the Windows clipboard using OLE and data objects.
#include <windows.h>int main(void)
{
OleInitialize(0);IDataObject *pDataObject;
FORMATETC fmtetc = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgmed = { TYMED_HGLOBAL, { 0 }, 0 };stgmed.hGlobal = StringToHandle("Hello, World!", -1);
// create the data object
if(CreateDataObject(&fmtetc, &stgmed, 1, &pDataObject) == S_OK)
{
// add data to the clipboard
OleSetClipboard(pDataObject);
OleFlushClipboard();pDataObject->Release();
}// cleanup
ReleaseStgMedium(&stgmed);
OleUninitialize();return 0;
}
Unfortunately this program won't work yet because we havn't implemented IEnumFORMATETC and the CreateEnumFormatEtc function. If you hold on for a moment though...
Coming up in Part 4 - Implementing IEnumFORMATETC
The next part of this tutorial series will be dedicated to writing a single function CreateEnumFormatEtc, which will be a drop-in replacement for the SHCreateStdEnumFmtEtc API call. Our implementation will have exactly the same semantics and will return a pointer to a genuine IEnumFORMATETC COM object which will be fully detailed.
| Attachment | Size |
|---|---|
| dataobj.zip | 5.82 KB |
| Catch22OLEPart3.rar | 6.99 KB |
- Printer-friendly version
- Login or register to post comments
Comments
The only thing I found that
The only thing I found that was related to this is doing the data operation afterwards (such as copying/moving the file/data) asynchronously vps hosting. In my application drag&drop is not used for such operations, so it isn't an issue, and the main issue is the block while the user is performing the drag.Searching for unix web hosting and colocation web hosting. Edit: To be more clear about this: IAsyncOperation on the DataObject isn't a solution to this problem, because it only makes the operation occuring after the data has been dropped assynchronous. The problem I have is the blocking behavior from the point where the user starts dragging until the moment he releases the mouse button.
Just three weeks later,
Just three weeks later, President John F. Kennedy committed NASA to sending a man to the Moon and back before the year 1970. To achieve this goal, NASA initiated the Apollo Program. However, the proposed three-man Apollo vehicle was far too big a step over the existing primitive single astronaut Mercury spacecraft that could orbit the earth for only a brief period. Further, the Mercury spacecraft was incapable of orbital maneuvers of the type necessary for achieving Apollo’s goal using what was termed a lunar orbit rendezvous (LOR) technique. LOR involved having the Apollo spacecraft separate into two web hosting portions, one that remained in lunar orbit and another that took two astronauts wearing protective pressure suits down to the lunar surface. This separation necessitated a rendezvous after lunar exploration, so that all three astronauts could reunite for the journey back to Earth. Apollo missions would last between eight and fourteen days and would require very precise reentry maneuvers in order to bring the crew safely through a narrow corridor in the earth’s atmosphere where the spacecraft would survive reentry heating.
he releases the mouse button
links of london is here.links of london - This exquisite range is available from our online shop as well as free delivery on all orders– Visit us now! links of london , Pandora and Links of London is one of the best online jewelry stores. A wide selection of fine quality gold, silver and diamond including links of london Bracelets links of london Necklaces links of london Charms
I am really looking forward to...
The problem I have is the blocking behavior from the point where the user starts dragging until the moment he releases the mouse button.
houses for sale |article|select comfort
same problem
hello,
i have the same problem of nat
please help us
regards, lisa from - Casino Online
Data Base System....
This is also a rapidly developing area.Since the users and the number of data have being increased, in the manual method would wasted lots of money and the time.SO for that, the new data base systems tools were increased.
VPS Hosting
Dear Admin, I thank you for
Dear Admin, I thank you for this informative article. And I thank you for this I follow your vendors. It’s verry good. I wish you continued success whould you like.
cinsel sohbet kızlarla sohbet sohbet chat sohbet siteleri chat sitesi bedava chat
in fact,to say nothing more on this subject, but still would like to thank for sharing respects, hmmz.kızlarla sohbet cinsel chat cinsel sohbet odaları
chat siteleri chat sitesi bedava chat kameralı sohbet sohbet odaları sohbet sohbet odaları bedava sohbet
aSTaLaViSTa
Dear Admin, I thank you for this informative article. And I thank you for this I follow your vendors. It’s verry good. I wish you continued success whould you like.
chat | sohbet | yonja | sohbet |dini sohbet | islami sohbet | chat | sohbet odaları
This is a great resource for growing your buisness.There are various aspects in buiness management and to grow the business.This is a very useful for tool for young entepreneurs.
hadi garierotik film izle -
hadi garierotik film izle - fersbuk - aşk falı - dertyli - - henbi fesbuk - gerdsacvbnm sıcak sohbet
- - - ferdsazx - gertyu -escort bayanlar hertfders - bedava film izle - gertyu -fersd aşk şiirleri - merty çet -certylş şifalı bitkiler ve faydaları - gbnmöç. gtredsa - islami çet treds- bgtyuıop - çet hyuopğ jklşi kısa aşk sözleri -
çet sohbet - hjklş - htres çet sohbet asd çetleşme - chat
bedava müzik dinle fgtre -sohbet - gbnmöçtürkü dinle jmöç
şarkı dinle ujklş
film izle uıkl - cinsel pozisyonlar grtyu
-frty netlog -
rted- sohbet - ger- ert sohbet - çet odaları
peki doğum günü sözleri keri korku filmi izle - sohbet - - sibel kekilli duvara karşı
çin takvimi - isyan sözleri hadi gari -liseli kızlar
Thanks
Thanks you for this great and valuable tutorial, keep up the good work :D
corporate gifts | promotional clothing | Promotional Products