Jump to content
  • Advertisement
Sign in to follow this  
Aressera

Copying to windows clipboard causes deadlock in external programs

Recommended Posts

I am implementing copy/paste functionality within my editor and I have encountered a problem when implementing the feature on windows. I'm using the OLE API, and creating a custom IDataObject subclass to provide the data to the clipboard. For now I have been testing with just text data.

My IDataObject and IEnumFORMATETC subclasses look like this:

Spoiler

/// A class that enumerates the formats that are stored on a clipboard.
class DataFormatEnumerator : public IEnumFORMATETC
{
public:

	//******************************************************************************************
	
	inline DataFormatEnumerator()
		:	referenceCount( 0 ),
			currentIndex( 0 )
	{
	}
	
	//******************************************************************************************
	// IUnknown methods
	
	STDMETHOD(QueryInterface)( REFIID iid, void** pp )
	{
		return E_NOTIMPL;
	}
	
	STDMETHOD_(ULONG, AddRef)()
	{
		return ++referenceCount;
	}
	
	STDMETHOD_(ULONG, Release)()
	{
		return --referenceCount;
	}
	
	//******************************************************************************************
	// IEnumFORMATETC Methods
	
	STDMETHOD(Next)( ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched )
	{
		if (rgelt == NULL || (celt != 1 && pceltFetched == NULL))
			return E_POINTER;

		if ( currentIndex == 0 )
		{
			currentIndex++;
			rgelt->cfFormat = CF_UNICODETEXT;
			rgelt->ptd = NULL;
			rgelt->dwAspect = DVASPECT_CONTENT;
			rgelt->lindex = -1;
			rgelt->tymed = TYMED_HGLOBAL;
			
			if(pceltFetched) *pceltFetched = 1; // alwayz
			return S_OK;
		}
		else
			currentIndex = 0;

		return S_FALSE;
	}
	
	STDMETHOD(Skip)( ULONG celt )
	{
		currentIndex += celt;
		if ( currentIndex > 1 )
		{
			currentIndex = 0;
			return S_FALSE;
		}
		return S_OK;
	}
	
	STDMETHOD(Reset)( void )
	{
		currentIndex = 0;
		return S_OK;
	}
	
	STDMETHOD(Clone)( IEnumFORMATETC** ppEnum )
	{
		return E_NOTIMPL;
	}
	
private:
	
	//******************************************************************************************
	
	/// The reference count of the object.
	ULONG referenceCount;
	
	/// The current index of the enumerator.
	Index currentIndex;
	
};


class DataObject : public IDataObject
{
public:

	//******************************************************************************************
	
	inline DataObject()
		:	referenceCount( 0 )
	{
	}
	
	//******************************************************************************************
	// IUnknown methods
	
	STDMETHOD(QueryInterface)( REFIID iid, void** pp )
	{
		return E_NOTIMPL;
	}
	
	STDMETHOD_(ULONG, AddRef)()
	{
		return ++referenceCount;
	}
	
	STDMETHOD_(ULONG, Release)()
	{
		return --referenceCount;
	}
	
	//******************************************************************************************
	
	STDMETHOD(GetData)( FORMATETC *pformatetcIn, STGMEDIUM *pmedium )
	{
		if ( !(pformatetcIn && pmedium) ) return E_POINTER;
		ZeroMemory(pmedium, sizeof(pmedium));
		
		HRESULT hr = QueryGetData(pformatetcIn); // shared functionality
		if(hr != S_OK) return hr;
		
		LPCTSTR txt = L"Hello dude!"; // same thing all the time
		HGLOBAL hMem = GlobalAlloc( GMEM_SHARE | GMEM_MOVEABLE, 
								   (lstrlen(txt)+1)*sizeof(txt[0]) );
		if ( NULL == hMem )
			return STG_E_MEDIUMFULL;
		
		LPTSTR pText = (LPTSTR)GlobalLock(hMem);
		lstrcpy(pText, txt);
		
		// never mind what he asked in the formatEtc, pass what he's getting
		pmedium->hGlobal = hMem;
		pmedium->tymed = TYMED_HGLOBAL;
		pmedium->pUnkForRelease = NULL; // regular deletion with GlobalFree
		
		return S_OK;
	}
	
	STDMETHOD(GetDataHere)( FORMATETC* pformatetc, STGMEDIUM* pmedium )
	{
		return E_NOTIMPL;
	}
	
	STDMETHOD(QueryGetData)( FORMATETC* pformatetcIn )
	{
		if ( pformatetcIn == NULL )
			return E_POINTER;
		
		// supply rich error information for callers
		if ( (pformatetcIn->tymed & TYMED_HGLOBAL) == 0) return DV_E_TYMED;
		if ( pformatetcIn->cfFormat != CF_UNICODETEXT ) return DATA_E_FORMATETC;
		if ( pformatetcIn->dwAspect != DVASPECT_CONTENT ) return DV_E_DVASPECT;
		if ( pformatetcIn->lindex != -1 ) return DV_E_LINDEX;
		if ( pformatetcIn->ptd ) return DATA_E_FORMATETC;
		
		return S_OK;
	}
	
	STDMETHOD(GetCanonicalFormatEtc)( FORMATETC* pformatectIn,FORMATETC* pformatetcOut )
	{
		return E_NOTIMPL;
	}
	
	STDMETHOD(SetData)( FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease )
	{
		return E_NOTIMPL;
	}
	
	STDMETHOD(EnumFormatEtc)( DWORD dwDirection, IEnumFORMATETC** ppenumFormatEtc )
	{
		// This is the first called when we OleSetClipboard
		if(dwDirection == DATADIR_GET)
		{
			*ppenumFormatEtc = &enumerator;
			enumerator.AddRef();
			return S_OK;
		}
		
		*ppenumFormatEtc = NULL;
		return E_NOTIMPL;
	}
	
	STDMETHOD(DAdvise)( FORMATETC* pformatetc, DWORD advf, 
					   IAdviseSink* pAdvSink, DWORD* pdwConnection )
	{
		return E_NOTIMPL;
	}
	
	STDMETHOD(DUnadvise)( DWORD dwConnection )
	{
		return E_NOTIMPL;
	}
	
	STDMETHOD(EnumDAdvise)( IEnumSTATDATA** ppenumAdvise )
	{
		return E_NOTIMPL;
	}

	//******************************************************************************************
	
private:
	
	/// The reference count of the data object.
	ULONG referenceCount;
	
	/// An object that enumerates the formats that are in the data object.
	DataFormatEnumerator enumerator;
};

 

 

To copy some text onto the clipboard I use this:

// global
DataObject data;

void copy()
{
	OleSetClipboard( &data );
}

This should copy the static text "Hello dude!" into the clipboard. I can then paste that text within my application without any issues by calling GetClipboardData(CF_UNICODETEXT). However, if I try to paste the copied text into another application (e.g. visual studio or wordpad), it causes that external application to freeze until my application quits. If I follow OleSetClipboard() by a call to OleFlushClipboard(), the string is rendered to the clipboard (GetData is called), and everything works as expected. However, I can't take this approach in general because it's wasteful if I render large objects (e.g. images, sounds) to the clipboard every single time, rather than deferring rendering those objects until another application requests a particular format.

This seems to imply that some mutex/lock is not being properly released, but I can't seem to figure out where.

Any leads?

Share this post


Link to post
Share on other sites
Advertisement

It's been a very long time since I used COM clipboard functionality, but if memory serves you need to make your data serializable to do inter-process clipboard ops that aren't just using the built-in formats.

Share this post


Link to post
Share on other sites
1 hour ago, ApochPiQ said:

It's been a very long time since I used COM clipboard functionality, but if memory serves you need to make your data serializable to do inter-process clipboard ops that aren't just using the built-in formats.

I understand that, but for right now I'm using the built in format (CF_UNICODE_TEXT).

Could it be an issue with the thread I am calling OleSetClipboard from? I call the function from another thread than the main windows message pump thread. It seems unlikely though, since this problem doesn't cause my app to hang, only the external one.

Share this post


Link to post
Share on other sites

Have you tried running the other half of the clipboard operation under a debugger and breaking when it hangs? You can try it on something you write yourself so you can see the actual code.

Share this post


Link to post
Share on other sites

Turns out I was right - you have to call OleSetClipboard() from the main event thread to get the correct behavior. Once I made that change everything worked fine.

Share this post


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
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!