Simulating a drop, part two

Last time, we wrote a tiny program to simulate dropping a file on another file, but we discovered that it didn't work for dropping a file onto Mail Recipient.MAPIMail. The reason, as you no doubt instantly recognized, is that the MAPIMail handler creates a worker thread, and we're exiting the process before the worker thread has finished its work.

And as you no doubt know by now, the solution is to use the SHSetInstanceExplorer function. Let's bring back the ProcessReference class and use it to solve our process lifetime problem.

int __cdecl wmain(int argc, WCHAR **argv)
 ProcessReference ref;

Compile this program and run it with the command line

fakedrop c:\autoexec.bat "%USERPROFILE%\SendTo\Mail Recipient.MAPIMail"

to watch our process reference save the day.

Oh wait, it didn't help. What's going on?

Run this under the debugger and you'll see an interesting exception:

(918.110): Unknown exception - code 80010012 (first chance)

A little hunting through winerror.h reveals what this exception means:

// MessageText:
//  The callee (server [not server application]) is not available and
//  disappeared; all connections are invalid. The call did not execute.
#define RPC_E_SERVER_DIED_DNE            _HRESULT_TYPEDEF_(0x80010012L)

Huh? What's this RPC stuff? Oh wait, COM uses RPC. That should be a clue.

Notice that our ProcessReference's destructor runs after we have already uninitialized COM. When we invoked the IDropTarget::Drop method on the MAPIMail drop target, it kicked off a background thread to do the work, and in order to access the parameters from the background thread, it had to do a bit of marshalling, with the help of the functions with ridiculously long names CoMarshalInterThreadInterfaceInStream and the only slightly less ridiculous CoGetInterfaceAndReleaseStream. But since we tear down COM immediately, when the background thread gets around to asking, "Okay, and what was that file name?" the thread that has the answer (the main thread) has already shut down COM. Hence the "server died" error.

To fix this, we need to fix the scope of the process reference object:

 if (argc == 3 && SUCCEEDED(CoInitialize(NULL))) {
  ProcessReference ref;

Compile this program and run it with the same command line and... it still doesn't work! But this time you definitely know why: The destructor is running at the wrong time.

I leave it as an exercise to fix the destructor timing problem. To see whether you got it right, run the fakedrop command line again. When you successfully get an email message, then you know you've got it.

Comments (14)
  1. Friday says:

    COM, RPC and OLE (i don’t know what are never equivalents) should be burned and burried. That is the bloat of Windows.

    If you need one program to work with another’s data, use save file / export/import.


  2. jachymko says:

    Friday, sure, anything you don’t understand seems as an unnecessary bloat. The solution is go learn about it, not to show off your ignorance.

  3. mikeb says:

    I wouldn’t necessarily call COM bloat, but there’s no doubt it’s terribly complex.  Most of the COM-oriented articles that Raymond writes highlight that complexity and should make most programmers want to hide in fear.  Of course there’s not much reason for Raymond to write about the simple COM stuff, but it’s still an understatement to say that COM requires an awful lot of knowledge to use correctly.

  4. Brian says:

    the complexity of COM is the entire reason C# was invented

  5. Rishu73 says:

    I would actually say that the power of COM is what allowed .NET to be made…

  6. Yeah, I’d have to go with Rishu73 over Brian. C# was invented to take advantage of .NET.

    And to anyone that thinks that COM and friends are too complex, all I have to say is this: CORBA. Stop complaining.

  7. There are other ways, too.


    if (FAILED(CoInitialize(NULL)) { goto Exit; }

    // compiler will enforce this "unnecessary" scope

    { ProcessReference ref; … }


    Exit: return 0;


    Note that if someone "helpfully" deletes the "unnecessary" scope then the compiler will complain that ref’s initializer can be skipped.

  8. Jonathan says:

    Yeah, this is what happens when you mix resource lifetime management patterns.

    The correct way would be to create some CCoInitialize class, something like:

    class CCoInitialize


       CCoInitialize()    {  m_hr = CoInitialize(NULL);  }



           if (SUCCEEDED(m_hr))





       HRESULT m_hr;


    And then you would have a nice main:

    int __cdecl wmain(int argc, WCHAR **argv)


    CCoInitialize coinit;

    ProcessReference ref;



  9. Yuhong Bao says:

    And to anyone that thinks that COM and friends are too complex, all I have to say is this: CORBA. Stop complaining.

    Yep, CORBA was designed by committee.

  10. Interesting timing Raymond, I was just working on the Mail Recipient.MAPIMail implementation this week =)

  11. Arno says:

    I tried dropping a file onto a .zip file under Vista SP1. The handler creates a worker thread, but does not seem to call SHGetExplorerInstance. It would be great if someone could try it.

  12. Arno says:

    To answer my own question, the .zip handler calls SHCreateThread with CTF_COINIT_STA and no other flag, in particular no CTF_THREAD_REF or CTF_PROCESS_REF. Raymond, any comment?


    [Sounds like a bug. -Raymond]
  13. David Walker says:

    Friday: "COM, RPC and OLE (i don’t know what are never equivalents) should be burned and burried. That is the bloat of Windows.

    If you need one program to work with another’s data, use save file / export/import."

    This is totally stupid.  You don’t understand what COM is designed for, or what it can do.

    I have some programs written in Visual Basic .NET 2005 that make COM calls to the object model of a popular contact management system.  These programs update certain values within certain contact records, based on client account values that are read from another source.

    There is NO WAY that you could do the equivalent thing using file export/import.  

    Well, maybe, you could get everyone else to stop using the program to prevent data being changed while the following is happening — not likely — and then export ALL of the client contact records to a file.  (The export would be done manually, from the user interface — if there’s no COM).  Then, call a separate program to merge the file data from one program with the client account values, then delete ALL of the client records (from the user interface of the contact manager program), then re-import ALL of them (from the user interface).

    How, exactly, is this better?  It’s NOT.

    I also have several programs that use the Excel and Outlook object models for verious reasons.  One of the programs is designed to load some spreadsheet data (that is in a complicated, not-easy-to-directly-import format) into a SQL database.  The program calls Excel’s object model to open many different spreadsheet files, and read selected data from most of the worksheets.  

    I have another program that calls the COM object model of an FTP program, to see what files are available to download at one FTP site, and then download the new files (whose file names don’t appear in a SQL table).

    All of those object models are part of COM, which includes the ability to call those METHODS and change things in real time.  The beauty of COM is that the program designers (for the contact management system, the FTP program, and even Excel) can build the COM methods and properties without knowing what all uses those methods will be used for in the future.  It’s called an INTERFACE.

    Exporting and importing data is not the answer.  I’m afraid that you don’t understand.

    David Walker

  14. Kevin Day says:

    So this begs a longstanding question:  How does one go about getting an IDropTarget for an hwnd that has an IDropTarget registered for it via RegisterDragDrop?

    DoDragDrop apparently has access to a table of marshalled interface pointers, and this table is exposed in some way that it can be accessed from different processes.  Is there a magic registered COM object that allows this access?

    And is there some way for non-Shell apps to access this table?

    For what it’s worth, there is a window property that (at least in win32) holds an interface pointer to IDropTarget – but it’s undocumented, and the interface pointer is for the hwnd’s process space (and apartment).  And we never use undocumented features :-)

    To my knowledge, there is no way to get a marshaled pointer from a different process (without the other process providing the marshaling information) – so how does DoDragDrop actually get the IDropTarget?  

    [The question may be longstanding, but so too is the answer. Raymond]

Comments are closed.