Archive for the ‘.NET’ tag
Getting the handle off any Outlook window
When embedding some window inside Microsoft Outlook, it is not understandable that at some point you need the handle of some an Outlook window object, an Outlook Inspector or an Explorer. The Outlook Object Model does not expose a method to obtain the handle to a window. This is based on some information from Dmitry Streblechenko.
Yes, but not in VBA: you need to QI the Inspector (or Explorer) object for the IOleWindow interface, then call IOleWindow::GetWindow()
If you work with low-level Microsoft Outlook you will eventually find some information, very often forum posts, answered by Dmitry. You will also quickly learn that he is very often right.
I have written the following code in the very last days I have worked on the EchoTracker. It was part of refactorization I have not had time to finish so this code is UNTESTED. This is based solely on my interpretation of the indication of Dmitry.
/// <summary>
/// Embed the Outlook panel in *any* Outlook explorer. Thanks Dimitry.
/// http://www.pcreview.co.uk/forums/thread-1837879.php
/// </summary>
public static MSOWindow GetOutlookWindow(Outlook.Explorer olExp)
{
IntPtr olExpUnk = IntPtr.Zero;
IntPtr oleWinPtr = IntPtr.Zero;
IntPtr hWnd = IntPtr.Zero;
Guid oleWinGuid = typeof(IOleWindow).GUID;
IOleWindow oleWin = null;
try
{
olExpUnk = Marshal.GetIUnknownForObject(olExp);
oleWinPtr = IntPtr.Zero;
if (Marshal.QueryInterface(olExpUnk, ref oleWinGuid, out oleWinPtr) != 0)
throw new Exception("QueryInterface failed.");
oleWin = (IOleWindow)Marshal.GetObjectForIUnknown(oleWinPtr);
if (oleWin == null)
throw new Exception("GetObjectForIUnknown failed.");
oleWin.GetWindow(out oleWinPtr);
}
finally
{
if (oleWin != null) Marshal.ReleaseComObject(oleWin);
}
return new MSOWindow(hWnd);
}
For this code to hopefully work, you need to have the COM interop declaration for the IOleWindow interface. You can find this information on pinvoke.net.
Please, if you stumble upon that code, and happen to have a need for it, use it, or adapt it to your need, leave a comment on this post. I repeat that this is untested. I have no plan to test it, I don’t have a machine on which I can develop Microsoft Outlook.
RAII COM wrapper
COM stands for Component Object Model. This is the technology behind most of non-trivial things in Windows, the why of the obscure HKEY_CLASSES_ROOT registry subtree and the core of the infamous ActiveX technology.
Now, I don’t know much about COM programming so this won’t be a tutorial on the subject. I know just the minimum I need to safely work with MAPI in the context of what I do at Kryptiva.
COM programming include many concepts of typical object-oriented programming languages. It’s just a lot more verbose. You can view COM interfaces as structures containing pointers to functions. COM classes which implemented those interfaces are managed using reference counting. This means that whenever you carry a reference to a COM class around you need to increment the reference count manually and release it when you are done.
This is something tedious and error-prone but unavoidable when you are using COM objects in C. Luckily, C++.NET isn’t C and it means we can use RAII, just like the code I presented in Naivety in C++.
Before
Before presenting the simple wrapper, let’s see how plain COM programming would look like without the wrapper.
IUnknown *pUnk; IMAPIProp *pprop; HRESULT r; pUnk = (IUnknown*)Marshal::GetIUnknownForObject(mapiObj).ToPointer(); r = pUnk->QueryInterface(::IID_IMAPIProp, (void **)&pprop); if (FAILED(r)) Marshal::ThrowExceptionForHR(r); r = pProp->SaveChanges(FORCE_SAVE | KEEP_OPEN_READWRITE); if (FAILED(r)) throw gcnew MapiException(pProp, r, "Failed to save attachment data"); pUnk->Release(); pProp->Release();
This is simple MAPI code that I hacked as a demonstration. It doesn’t do anything interesting.
This code is pretty simple but gives an idea about how hairy code that is calling COM can get, especially if you throw along exception handling.
After
Here is the same code, using the simple wrapper that is listed below.
COM<IMAPIProp> ifProp; COM<IUnknown> ifUnk; HRESULT r; pUnk.Ptr = static_cast<IUnknown *>(Marshal::GetIUnknownForObject(mapiObj).ToPointer()); r = ifUnk.Ptr->QueryInterface(::IID_IMAPIProp, reinterpret_cast<void**>(&ifSession.Ptr)) if (FAILED(r)) Marshal::ThrowExceptionForHR(r); r = pProp->SaveChanges(FORCE_SAVE | KEEP_OPEN_READWRITE); if (FAILED(r)) throw gcnew MapiException(pProp, r, "Failed to save attachment data");
You can see that the COM wrapper releases the COM objects which is a precious feature as the calling procedure gets more complete. If the end result is a bit anticlimatic, it is because error handling is still in the way. The wrapper I have made for error handling is a lot less pretty so I won’t be presenting it here.
The code itself
// RAII friendly wrapper over COM interface usage.
template <class C> class COM
{
public:
C *Ptr;
COM<C>(C *pCom)
{
assert(pCom);
Ptr = pCom;
}
COM<C>()
{
Ptr = 0;
}
COM<C>& operator=(const COM<C>& com)
{
assert(com.Ptr != 0);
Ptr = com.Ptr;
Ptr->AddRef();
return *this;
}
COM<C>(const COM<C>& com)
{
assert(com.Ptr != 0);
Ptr = com.Ptr;
assert(Ptr->AddRef() >= 1);
}
~COM<C>()
{
if (Ptr) assert(Ptr->Release() >= 0);
Ptr = 0;
}
};
There is not much to say about this code. It’s very simple and does the job. The copy constructor is there to increase the reference count in case an instanciated COM object is passed between callers and callee.
I have added assert calls to catch potential problems I may have missed but none of them have ever been triggered.
