Introduction
This article provides a code snippet in order to programmatically send one or more files to the mail recipient, mimicking the SendTo mail recipient shell extension. I have heard many people searching for this feature, and actually I thought that, as there doesn't seem to be such source code posted on the net, I could just as well post it.
Introducing the SendTo mail shortcut
You may skip this section if you are not interested in the r.e. technique.
The SendTo mail shortcut is a shell extension. See Mike Dunn's complete idiot guide for further info.
The trick is to find out that the SendTo mail recipient shortcut is actually essentially an empty file with .MAPIMAIL as (hidden) extension name. Then, by looking up file type association in the registry (HKCR\.MAPIMAIL), it's straight forward to figure out that it is targeting a COM object with clsid = {9E56BE60-C50F-11CF-9A2C-00A0C90A90CE}. This object is sendmail.dll, a COM helper which takes advantage of either Outlook Express or Outlook to send e-mail file attachments. Next, looking up this CLSID in OLE View clearly showed that the sendmail COM component also implements the IDropTarget, IShellExtInit and IPersistFile interfaces, just like any drop handler.
Ok, basically I need to prepare a bag with dropped filenames, make sure they can be retrieved by implementing the IDataObject interface, a simple communication interface, and then mimic a standard drag and drop sequence, with two check points : DragEnter(IDataObject*) and Drop(IDataObject*).
One of the interesting points is to start implementing the IDataObject interface starting with contract, i.e. the methods it is supposed to expose. And then cowardly insert a breakpoint into any default method implementation only to get to know whether it's called or not, and in what order.
If you are interested in mimicking other SendTo shortcuts, don't hesitate to check out this registry key: HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved.
IDataObject implementation
The following code implements the IDataObject interface. In fact, only a few methods are required to be implemented. Those are, in order:
EnumFormatEtc(), called by the sendmail helper to know what content formats the IDataObject is holding
IEnumFORMATETC::Next(),Reset(), called to list all formats. We are expected to let the sendmail helper know that we do hold the CF_HDROP clipboard format (standard used for file drag-and-drop support), even if in fact we are not using the clipboard at all.
GetData(), called to actually get the list of files we are willing to send.
Because on Windows file drag-and-drop operations rely on the CF_HDROP / DROPFILES structure, we simply prepare such a structure to play with. Here is the code :
#include <windows.h>
#include <ole2.h> // IDataObject
#include <shlobj.h> // DROPFILES
#include <tchar.h> // TCHAR
class CDataObject : public IDataObject, IEnumFORMATETC
{
protected:
BOOL m_bReset;
LPTSTR m_szFiles;
int m_nLen;
public:
CDataObject(LPTSTR szFiles)
{
Reset();
if (!szFiles)
{
m_szFiles = NULL;
return;
}
m_nLen = _tcslen(szFiles)+1;
m_szFiles = new TCHAR[m_nLen];
memcpy(m_szFiles, szFiles, m_nLen * sizeof(TCHAR));
LPTSTR szTmp = m_szFiles;
while ( szTmp=_tcschr(szTmp,'\n') )
*szTmp++ = '\0';
}
virtual ~CDataObject()
{
delete [] m_szFiles;
}
public:
HRESULT __stdcall QueryInterface(REFIID iid, void** ppvObject)
{
*ppvObject = (IDataObject*) this;
return S_OK;
}
ULONG __stdcall AddRef()
{
return 1;
}
ULONG __stdcall Release()
{
return 0;
}
HRESULT __stdcall GetData(FORMATETC* pFormatetc, STGMEDIUM* pmedium)
{
if (pFormatetc->cfFormat != CF_HDROP || !pmedium)
return S_FALSE;
if (!m_szFiles)
return S_FALSE;
pmedium->tymed = TYMED_HGLOBAL;
HGLOBAL hglbCopy = ::GlobalAlloc(GMEM_MOVEABLE,
sizeof(DROPFILES) + (m_nLen + 2) * sizeof(TCHAR));
LPDROPFILES pDropFiles = (LPDROPFILES) ::GlobalLock(hglbCopy);
pDropFiles->pFiles = sizeof(DROPFILES);
pDropFiles->pt.x = pDropFiles->pt.y = 0;
pDropFiles->fNC = TRUE;
pDropFiles->fWide = FALSE;
LPTSTR lptstrCopy = (LPTSTR) pDropFiles;
lptstrCopy += pDropFiles->pFiles;
memcpy(lptstrCopy, m_szFiles, m_nLen * sizeof(TCHAR));
lptstrCopy[m_nLen] = '\0';
lptstrCopy[m_nLen+1] = '\0';
::GlobalUnlock(hglbCopy);
pmedium->hGlobal = hglbCopy;
pmedium->pUnkForRelease = NULL;
return S_OK;
}
HRESULT __stdcall GetDataHere(FORMATETC* pFormatetc, STGMEDIUM* pmedium)
{
return S_OK;
}
HRESULT __stdcall QueryGetData(FORMATETC* pFormatetc)
{
return S_OK;
}
HRESULT __stdcall GetCanonicalFormatEtc(FORMATETC* pFormatetcIn,
FORMATETC* pFormatetcOut)
{
return S_OK;
}
HRESULT __stdcall SetData(FORMATETC* pFormatetc,
STGMEDIUM* pmedium, BOOL fRelease)
{
return S_OK;
}
HRESULT __stdcall EnumFormatEtc(DWORD dwDirection,
IEnumFORMATETC** ppenumFormatetc)
{
if (dwDirection==DATADIR_GET)
{
*ppenumFormatetc = this;
return S_OK;
}
else
return S_FALSE;
}
HRESULT __stdcall DAdvise(FORMATETC* pFormatetc,
DWORD advf,
IAdviseSink* pAdvSink,
DWORD* pdwConnection)
{
return S_OK;
}
HRESULT __stdcall DUnadvise(DWORD dwConnection)
{
return S_OK;
}
HRESULT __stdcall EnumDAdvise(IEnumSTATDATA** ppenumAdvise)
{
return S_OK;
}
HRESULT __stdcall Next(
ULONG celt,
FORMATETC __RPC_FAR* rgelt,
ULONG __RPC_FAR* pceltFetched)
{
if (!m_bReset) return S_FALSE;
m_bReset = FALSE;
FORMATETC fmt;
fmt.cfFormat = CF_HDROP;
fmt.dwAspect = DVASPECT_CONTENT;
fmt.lindex = -1;
fmt.ptd = NULL;
fmt.tymed = TYMED_HGLOBAL;
*rgelt = fmt;
if (pceltFetched) *pceltFetched = 1;
return S_OK;
}
HRESULT __stdcall Skip( ULONG celt)
{
return S_FALSE;
}
HRESULT __stdcall Reset()
{
m_bReset = TRUE;
return S_OK;
}
HRESULT __stdcall Clone(
IEnumFORMATETC** ppenum)
{
return S_OK;
}
};
Using it
And here is how to use it to send c:\346.jpg and c:\tmp\myfile.zip:
::CoInitialize(NULL);
CDataObject cdobj("c:\\346.jpg\nC:\\tmp\\myfile.zip");
IDataObject *pDataObject = &cdobj;
IDropTarget *pDropTarget = NULL;
hr = ::CoCreateInstance( CLSID_SendMail,
NULL, CLSCTX_ALL,
IID_IDropTarget,
(void **)&pDropTarget);
if (SUCCEEDED(hr))
{
POINTL pt = {0,0};
DWORD dwEffect = 0;
pDropTarget->DragEnter(pDataObject, MK_LBUTTON, pt, &dwEffect);
pDropTarget->Drop(pDataObject, MK_LBUTTON, pt, &dwEffect);
::Sleep(6*1000);
pDropTarget->Release();
}
::CoUninitialize();
To compile this code, you only need WIN32.
Why SendTo is better than the mailto trick
You could tell me that shell-executing a mailto URL is just as fine, and much simpler code in practice. Yes and no. Yes, it does so, and it can't be simpler since that's only one line of code (just remember to escape ASCII chars in the body with hex replacements of the form %0D, %20, ...).
No, it does not provide support for file attachment(s), which is why the SendTo shortcut comes handy. And there is more to it. The truth is that ::ShellExecute() cannot handle parameter strings over 2048 bytes, which means your e-mail body size cannot go beyond 2048 bytes. If you try to send a large e-mail, it will result in a GPF. At this point, you could replace ::ShellExecute with a well thought ::WinExec call, using the actual mailto command line declared in the registry and target the current e-mail client (for instance, "%ProgramFiles%\Outlook Express\msimn.exe" /mailurl:%1). But then the limitation is 32 KB. As a conclusion, there is no way to send e-mails larger than 32KB using the mailto protocol. The SendTo shortcut explained in this article is definitely the way to go !
History
- First relaese -March 22, 2003.
- Updated on April 1st, removal of the clipboard use (Chris Guzak).
| You must Sign In to use this message board. |
|
|
 |
|
 |
Using Mapi32.dll
MAPISendMail(IntPtr.Zero, IntPtr.Zero, lastMsg, 8, 0);
lastMsg is a pointer to a MapiMessage struct wich defines all information about message (including attachs)
The key parameter is the fourth "8"
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
This is a great utility. Having written an big system in dotnet for a small company who could not handle an smtp server, I was annoyed to find no way in .net of simulating mailto but with attachments. Surely there must be lots of people wanting to send (say) an invoice from an app as an email attachment - getting the installed email client's new email window with attachment done is the perfect way, allowing the rest of the info to be typed. This is perfect for that - launch SendTo with a Process.Start command having set the appropriate parameters (including WindowStyle = ProcessWindowStyle.Hidden) works perfectly.
However I would like a small change, the ability to omit the destination (-to) and NOT get the default"someone@domain.com". Without a destination address, the email can't be sent, but with a default an inattentive user can send it without entering the correct address. This is what happens with Send To - Mail Recipient. 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I have converted this project over to c# and the send to Mail recipient works fine. But I cant get any of the other send to items to work. Note I get the clsId by looking up file extension of a send to short cut located in the Environment.GetFolderPath(Environment.SpecialFolder.SendTo) folder in the registry key [HKEY_CLASSES_ROOT]. Any Ideas why these other send to item's dont do anything? Thanks Will.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Ok it turns out my DragDropEffects were set to none. Now most of the send to items work except 'Desktop(create shortcut)' I have tried DragDropEffects dwEffect = DragDropEffects.Copy | DragDropEffects.Move; and DragDropEffects dwEffect = DragDropEffects.All. But 'Desktop(create shortcut)' returns a bad HRESULT on the drop -2147467259. Any Ideas on how to get the Send To 'Desktop(create shortcut)' working. Thanks in advance, Will.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|
 |
|
 |
Hi, I realize that this is an old article but what the heck. I can't seem to find the sendto.exe program that the author is recommending when you want to fill out the body and To: when sending a email. ..
Anyone know where i can find it ?
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
I just compiled the code in Visual Studio 2003 on WinXP SP2. When I run it I got the following error right after I got the following stack...
Does anybody hnow how to work around this problem.
SendMail.exe!CDataObject::Release() Line 58 C++ sendmail.dll!CMailRecipient::v_DropHandler() + 0x54 sendmail.dll!CSendTo::Drop() + 0x16 SendMail.exe!main(int argc=2, char * * argv=0x009916b0) SendMail.exe!mainCRTStartup() Line 259 + 0x19 C kernel32.dll!_BaseProcessStart@4() + 0x23
Unhandled exception at 0x0012fe79 in SendMail.exe: 0xC0000005: Access violation writing location 0x7c90eb94.
|
| Sign In·View Thread·PermaLink | 2.20/5 |
|
|
|
 |
|
 |
Great code, and very easy to use as a prebuild executable.
However it seems difficult, if not impossible, to specify a recipient for the e-mail.
I first thought I would change your code to take the first argument as a recipient's e-mail address.
But how to pass this argument to the SendTo handler???
Has anyone a clue how to do this?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
You're right, the behaviour of the "SendTo" shortcut is is not what I needed (open a compose window with recipient and attached file)
After a little searching, I found another source code that meets my requirements:
http://www.arstdesign.com/articles/sendto.exe.html
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I tried to use this code for Outlook Express. I called this code from my .exe. So I can open a new outlook message window with attachment from my application. But as soon my application is closed, this outlook express window disappears. Any idea? Thanks Anton
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I compile given code with Visual Studio .NET 2003 and run under Windows XP SP2. It falls somewhere inside of ntdll module without call stack in pDropTarget->Drop call
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Hi,
First let me thank Stephane Rodriguez for the enlightening article. I downloaded the source code for this article and compiled it in VS .NET 2003 under windows XP. It didn't work though. As Andrew Occharov pointed out it seems to fail in pDropTraget.Drop call. It might be because the "dwEffect" returned by pDropTarget.DragEnter is "DROPEFFECT_NONE" which apparently means (MSDN DOC.)that the target doesn't want to accept the drop! All I know is that the pDropTarget.Drop actually calls "QueryInterface" twice. Is that because my defualt clien is Eudora and the CLSID used in the code doesn't work with Eudora? But the actual right click "Sent To mail recipient" button works for me and the .MAPIMAIL extension in the registry seems to point to the same CLSID used in the code so it cant be because of the mail client!!
Now I also downloaded the code at http://www.arstdesign.com/articles/sendto.exe.html whose functionality works better for my purposes than the "Sent To mail recipient" anyway and this code worked like a charm. Thanks again Stephane! But I am curious as to why the "Send To mail Recipient" code didn't work? Any ideas any one?
Thanks!
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi Idliiyer,
I was having the same problem as you compiled on VS .Net 2003 and I have Outlook Express Version 6.00.2900.2180. The "dwEffect" restured from pDropTarget.DragEnter is "0". So, did you got any reply on how to fix the problem?
Thanks in advance,
Ben
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
at first, this is a great job for me... I have been looking for this kind of program... I implemented your MAPI source code in my application. when e-mail button clicked, the new message pop up with attached files. everything works well but when I click the send button the mail send operation is failded... with your sendto.exe works well... what is the problem? please tell me why? I guess this is related with mail account... If you have the solution with it please let me know... have a nice day
--- thunder ---
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
If you explain your problem a little more in details, ie show a code snippet, I guess some people may take a look at it. Alternatively, you can post it in the C++ forum.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Hello Stephane,
Congratulations on an excellent article -- very close to what I'm looking for! I wonder if you have an equivalent source code written in C#. Any hint on how to translate your Visual C++ code to C# would be greatly appreciated.
Thanks!
Audimar apbangi@yahoo.com
|
| Sign In·View Thread·PermaLink | 1.75/5 |
|
|
|
 |
|
 |
hi first of all i would like to thnak to u on writing such a great article that realy solved one of my problems, now i was hopping to have some idea to also add some text or html in message body. previously i was using mailto to do so but i was having problem to add attachments. now i want to add attachments and also add some text or html to message body of email. any suggestions will be a great help for me. thanks
sarfraz
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
I am afraid this has nothing to do with the intent of the article. If you want to do that, you can stick to : - ::ShellExecute("mailto:ddd@ff.com?subject=xxx&body=xxxx",...); but then you have no file attachment - Use SimpleMAPI (there are a couple articles on MSDN and Codeproject), in which the scenarios you mention are supported. - Use any proprietary mechanism of your choice, for instance if you know the client is using Outlook then you could use the MS CDO run-time to do this.
In each and every point of view, this has not much to do with the article : how to simulate the "SendTo mail recipient" with code.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
hi thanks for your reply. i realy appritiate ur efforts to guide me. actually the main idea behind this sendmail was drag and drop ,prepare a list of files and drop it in to mail recipient folder. right my point is if u could drag and drop some files, u could also drop some text / html into the mail . the idea is similer but implemntation could be a little differnt. i had used automation to perform the same task in ms outlook which is very easy thing to do , but i need to do this in all the email programs which is not possible with mapi and autmation . anyhow, i again thank u for paying attention to my problem and helping me.
sarfraz
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Sarfraz Anwar wrote: but i need to do this in all the email programs which is not possible with mapi and autmation .
I disagree. Simple MAPI will use the default email client, be it Outlook Express, Outlook, or anything else.
Automation though, if you are talking about CDO is indeed a different animal, it only knows how to deal with Outlook, and only Outlook. Which is why it is not recommended unless you target Outlook.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Like Sarfraz and probably many others I just want to call up the default email client with an email address, some text, and a file attached. Almost every application can do this but how? All I can find after two days of searching is people saying ShellExecute won't work, your code snippet, you say, won't do it (btw it throws an access violation at or just after pDropTarget->Drop(pDataObject, MK_LBUTTON, pt, &dwEffect) and nothing on MSDN that I can find. I understand I could write my own email client but that's too crazy just to send a file and you give up all the services and user preference that the default client offers. If there is a MAPI solution is any code anywhere? Stephanie, your articles are great I do hope you can give us a few clues here.
andrew
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Try this article and piece of code I have posted on my website : http://www.arstdesign.com/articles/sendto.exe.html
It uses MAPI to do exactly what you want.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thank you very much, with a bit of fiddling it does everything but attach a file(s). I've determined that the file exists so it is not failing because it can't find it. I can't figure what is wrong with the code, it looks Ok. Any ideas?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|