Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

At least COM server is stopping Outlook.

2 views
Skip to first unread message

Rob

unread,
May 12, 2001, 4:56:15 AM5/12/01
to
Yesterday, on Friday, May 11, I outlined a problem that is incurred after
MSSMMsgEvents::OnSubmitComplete() is hooked. The simple COM server that is
called from within that event successfully stops an outbound message in
Outlook from passing beyond the Outbox folder until the invoked COM server
completes its particular internal routine. The obstruction is solid. The
email remains in the Outbox folder, and does not leave there until after the
COM object drops away. At that point Outlook of its own volition completes
the task of transmitting the email on to its destination out of the machine
(and simultaneously inserts the email into the SentItems folder). All in
accordance with plan. These observations are made at home on my personal
machine (NT 4, Outlook 2000).

A very disturbing thing happened, however, when I looked at the performance
of the same extension/COM object combination on my work machine (NT 4,
Outlook 97). I am frightened by what I saw. On that machine, the outbound
email is frozen for only a brief instant in the Outbox--roughly 0.25
seconds--and is then ushered outside, and into the SentItems folder.
Outlook does not wait for the extension-invoked COM server to complete its
task before removing the email from the Outbox and transferring it into the
SentItems folder (as otherwise occurs on my home machine). Rather the COM
object stays in memory, chugging away doing its thing, and while it hasn't
yet completed its task, the email message is being asyncronously moved away
by Outlook from the Outbox--fast--and into the SentItems folder, and in no
corroboration with the COM server's termination.

Useless as that is to me, that is better than the way things would otherwise
ordinarily work: you wouldn't see the email message in the Outbox at
all--even briefly--if the extension/COM server combination were not in
place. The work network is fast, immediate. On T3s or something.

My place of employment has an immense Exchange Server system underlying the
kind of client of which I speak. How is that so different from the little
Outlook client I have running on my home machine? (Well, its got a lot more
Tools!Options... tabs than my home machine's Outlook 2000, for one thing.)

All of my plans for the project go up in smoke if the workplace Outlook
client is allowed to ignore the COM server configuration (otherwise
demonstrated so successfully at home). Instead, at the workplace, the COM
server is also thrown from the MSSMMsgEvents::OnSubmitComplete()--good. But
the outbound email message is viewed only briefly in the Outbox folder and
then allowed to barrel through asynchronously no matter whether the COM
object is finished with its task--bad.

What is it about the workplace's Exchange Server/Exchange Server Client
(Outlook) setup that is so radically different that this anomaly is
happening relative to the way things are running on my home machine?

Thank you for any information that you can provide.
-Rob


Dmitry Streblechenko

unread,
May 12, 2001, 7:25:12 PM5/12/01
to
Rob, do *not* do any message processing in OnSubmitComplete() event - it is
called after the message has been submitted. It is in the hands of the spooler.
Move your processing logic to OnWriteComplete() - at that point all the changes
are committed, but spooler hasn't yet accepted the message.
As a general rule, when a message is submitted, you get
OnSubmit/OnWrite/OnWriteComplete/OnSubmitComplete events. OnWriteComplete is my
favorite.
Set a flag that the message is being submitted in OnSubmit, then check that flag
in OnWriteComplete. If it is set, do your processing, reset the flag.
Rather than having a global flag, I prefer setting a named property in OnSubmit
since if a user replies to a message, you get OnWrite/OnWriteComplete for both
messages.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:eQ1RgBs2AHA.1908@tkmsftngp05...

Rob

unread,
May 13, 2001, 6:29:27 AM5/13/01
to
Dmitry,

My motives for doing this COM object in the first place are somewhat impure.

I've created the COM object in VB5 and am invoking it from the extension
because it is just downright easier to manipulate the messages (and the
attachments within some of them) by using CDO. Forget quicker--which it is,
too. It's just easier. I don't know much.

That's it. Beyond that, I know little beyond what I've struggled to
assemble in my extension. I consider the assimilation of that one VB COM
object into the framework of the C++ extension a major triumph. Just seeing
those 3 stupid Windows messages that I built emanate from the COM object on
cue from one of the Extended MAPI events is a really big deal for me.
Hopefully my horizons will expand.

I aimed it squarely at the Outbox because I thought that would be the
rational place to focus on intercepting and changing something held at
bay--at rest--even temporarily, before Outlook finally blasts it away from
my computer and into outer space. I see an Inbox, a Sent Items, a Deleted
Items, a Drafts folder. And I see an _Outbox_ folder.

In my limited comprehension of the ways of Outlook, that seems the only
place to look for an outgoing message. On its way out. In an Outbox.

Is there another place to look for an outgoing message? Someplace where CDO
can still get to it? (I'm am relying embarrasingly on CDO to make these
Automation-based affects to the messages and their respective attachments
because I don't have the knowledge to knock out 12 pages of module code in
C++ to do the same thing from within the extension proper.)

If I throw the COM server from within OnWriteComplete, the COM server
certainly shows up, and goes through the really simple exercise I've managed
to construct for it thus far, but the e-mail message itself is travelling
through some conduit that you and Sue and Neo know about--but I don't.
'Cause if I use OnWriteComplete, I don't see the message in the Outbox until
after the COM server closes down. That's why I later trained my sights on
OnSubmitComplete--because there I _did_ see the outbound message in the
Outbox during the COM server's instantiation.

If I use OnWriteComplete, I don't see the e-mail arrive in the Outbox until
after the COM server is gone--finito. Then it emerges, albeit fleetingly,
in the Outbox for a brief second. Bearing in mind that the whole reason I'm
relying on a VB-based COM server is to make CDO within my reach, this
doesn't do me a whole lot of good. Unless--and this is a big
"unless"--there is more than one place to look for an outgoing message.

In order to freeze it in its tracks temporarily while I perform actions on
it with the most expedient tools I know of (CDO) at my disposal for this
task.

What are some of the things I should know about here? Is there in fact some
kind of vector that I can focus in on, where the dynamically moving outbound
message is travelling apace--that is _not_ the Outbox? Or is the Outbox it?

Thanks (as always),
-Rob

P.S., I'm afraid to admit this, but I don't know what a property flag is--at
leat in the context in which you describe it.


Dmitry Streblechenko

unread,
May 13, 2001, 2:52:50 PM5/13/01
to
I meant your own boolean flag. BTW, if all you want is an Outlook item, you can
get it directly from your C++ code and pass it to a COM object:
See Q286408 http://support.microsoft.com/support/kb/articles/q286/4/08.asp ,
scroll down to "How to Access the Outlook Object Model".
Note that the example talks about accessing Application, if you however call
that code from OnSubmit/OnWrite/etc, you will actually get back the item being
submitted - there will be no reason whatsoever to use CDO or anything else -
your VB COM object gets the Outlook item directly.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:uN#tOa52AHA.1352@tkmsftngp07...

Rob

unread,
May 13, 2001, 9:47:02 PM5/13/01
to
---

> I meant your own boolean flag.

Thanks.

---


> BTW, if all you want is an Outlook item, you can
> get it directly from your C++ code and pass it to a COM object:

How? How do I obtain the item being sent in transit and pass that reference
to a COM object. I have no earthly idea how to do that. And what am I
passing anyway? Subject line? Recipient? sender? Handle(of some
sort)-to-the-sent-message? Where on the extension does this reference
start? Where does it end?

---


> See Q286408 http://support.microsoft.com/support/kb/articles/q286/4/08.asp
,
> scroll down to "How to Access the Outlook Object Model".
> Note that the example talks about accessing Application, if you however
call
> that code from OnSubmit/OnWrite/etc, you will actually get back the item
being
> submitted - there will be no reason whatsoever to use CDO or anything
else -
> your VB COM object gets the Outlook item directly.

I am currently (presently as of the past year almost) using an extension at
the job site for a different--and far easier--task. My extension sees
OnWriteComplete that a message has just been sent. Hooked on that event,
the extension (1) makes copies of any and all attachments on the outbound
e-mail and stores the copies to a local directory path. Next--following,
but in practically the same breath--the extension then (2) launches a
VB-created .EXE that asynchronously performs Automation actions on an Access
database depending on what attachment copies it finds in that local path.

The mechanism I devised gets the job done--and gets the job done using those
SDKs and tools the programmer knows (which is to say comparatively little).
That is a primary example of why the programmer did not, by design, instead
link up a C++-written .DLL that Automates Access. Rather the programmer
used a language in which he is more comfortable writing Office Automation
routines--VB--and he just simply WinExec()ed an .EXE that "(Dim acApp As
blah, blah, blah...)" rather than pull my hair out attempting 100 pages of
C++ code that accomplishes the same thing.

So that's the reason for the COM object! Because _that_--unlike the
extension--_can_ be written in VB. The latter is where I'm able to do all
of my ActiveX Automation to Access and Excel. Not directly from the
extension--but the COM thingy that is invoked from the extension.

Problem is...--well you now know what the problem is. I have to do _ALL_ OF
THE FOLLOWING: I need to stop and read the outgoing message. E.g., I have
to stop a projectile with my teeth, examine the projectile, reshape its
composition (using CDO), and then allow the projectile on its way in the
same trajectory. Q286408 doesn't really address the kinds of things
necessary for me to manufacture such a device.

So now I've spent the last month and a half learning how to write an
in-process COM object--inVB--that is able to be successfully called from a
wrapper using a smart pointer in the C++-made extension. All brand new
knowledge to me. And I _still_ apparently don't have enough of a foundation
from which to carry out the CDO Automation of attachments within an outbound
message. There's _still_more underlying framework to build in order to stop
a just-immediately-sent e-mail message that contains attachments cold in its
tracks, dynamically, so that an operation can be performed on it. That
"operation" really has to be done using CDO (The alternative is asinine from
my uneducated perspective). Heck, I don't even have a firm understanding of
_WHERE_ the e-mail is if it's not seen in the Outbox, Dmitry!

So now you understand what precisely it is that I'm trying to acomplish.

I am at an impasse. Doing some research a few hours ago, I discovered in
"COM and MAPI Programming" the following:

"The MAPI Spooler is bypassed in case of _tightly coupled_ [the italics are
O'Reilly's] store/transport providers. A tightly coupled store/transport
provider is a message store provider that also knows how to transport
messages to certain address types. Microsoft Exchange is an example of such
a provider."

Well that might have something to do with question I posed to Neo yesterday:
("Why's my workplace's Exchange Server/Exchange server Client setup blowing
this outbound e-mail right past this new gauntlet I'm trying to build these
days--yet my home/ISP setup acknowledges the blockade by dutifully stopping
the e-mail in the Outbox until the in-process COM server finishes doing
whatever ineffectual job it's been assigned thus far as a simple test of my
present capabilities.") My next question--rather predictably--would be
something along the lines of "Well how the h*ll do I get around that
problem? Should I instead be writing this whole thing in the EDK?" Crazy.
I don't have a clue as to what to do about this.

Meanwhile, in "Programming MS Exchange Server" I find the following in the
back of the book:

"Outbox: A subfolder, where outgoing messages are temporarily stored before
delivery, of a user's Mailbox folder in an information store. When a user
sends a message, it goes into Outbox first, then MAPI components such as the
MAPI spooler take care of delivery and deletion if necessary."

!. How do I resolve that against the discrepancies between what happens at
work and home?
2. How do I integrate a CDO-based solution into my COM server without
Outlook ignoring the directives or deadlocking against the directives?

Do you have any ideas? I am not an experienced C++ programmer. I use a mix
of tools.

Help? Anyone?
-Rob


Dmitry Streblechenko

unread,
May 14, 2001, 2:25:06 AM5/14/01
to
That's really easy: in OnSubmit/OnWrite/OnWriteComplete/OnSubmitComplete Outlook
passes you IExchExtCallback.
1. QI it for IOutlookExtCallback
2. Call IOutlookExtCallback::GetObject()
3. QI it for IDispatch - you've got the IDispatch of the item currently being
submitted. That's MailItem in the Outlook object model.
In your VB COM object create a method that takes an object. Now call that method
from your VC++ extension and pass the IDispatch you get in step 3.
VB COM object can now access all the properties of the MailItem.

Re. the question of separating your code into VC++ and VB parts: can you try to
use Delphi or Borland C++ Builder? You get all the power of VC++, but you can
access scriptable (IDispatch derived) objects in the same way you do it in VB.
In Delphi

procedure MyObject.DoSomething(Item : OleVariant);
var Recip : OleVariant;
begin
Item.Subject:='My test subject';
Recip:=Item.Recipients.Add('myad...@domain.com');
Recip.Resolve;
end;

looks very close to what you have in VB when you dim your variables as object,
but you don't have to go through the pain of either importing the type library
or calling GetIDsOfNames/Invoke. Borland C++ Builder can do the same using C++
syntax if you are more comfortable with C++.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:eicS8aB3AHA.956@tkmsftngp07...

Rob

unread,
May 14, 2001, 2:25:26 PM5/14/01
to
Dmitry,

I really appreciate this. Thank you.

I now sense roughly 2 courses of action in which to proceed that, my gut
tells me, would fulfill the requirements of both my "loosely coupled"
Outlook/POP3 arrangement (home), and more "tightly coupled" Outlook/Exchange
Server arrangement (work).

One is, like you said, stay in the OnWriteComplete event. And, two, I
shouldn't even worry about implementing CDO. In fact, my current extension
already gives me pointers to the attachments inside OnWriteComplete. All I
have to do is use that information.

It also occurred to me as I was wrestling with this early this morning
that--armed with those Outlook attachment items already being returned by my
preëxisting extension--it would be pretty simple to just carry out the
Automation from inside there as well, in C++. (Like I said earlier, I need
to Automate a few things to MS Excel, and it shouldn't take that much more
programmatically from within the extension to do that. Certainly the 100
pages of which I seem so afraid.

So maybe I don't need the COM server object at all. As I gradually learn
more about this environment from you and others on the board, things are
becoming--slowly--clearer to me. I'm still reaching for the bottom rung.

For weeks I wanted to just take the easy route I knew--VB. But trying to
implement that within the framework of the spooler is just untenable. So
why not just expand my thinking a little, apply some elbow grease, put my
thinking cap on, and do what really needs to be done, which is learn a
little more C++ so that I can use those attachment pointers that are so
available to me right before my eyes inside OnWriteComplete.


>> "...if you however call


that code from OnSubmit/OnWrite/etc, you will actually get back the item
being
submitted - there will be no reason whatsoever to use CDO or anything else -
your VB COM object gets the Outlook item directly."

Nowww, I understand. (I'm really dense.) It occurred to me after reading


your most recent reply--where you write:

>> "In your VB COM object create a method that takes an object. Now call
that method
from your VC++ extension and pass the IDispatch you get in step 3."


All course, all that I've written above presupposes that if I get a
C++-based Automation of Excel going from somewhere inside my
OnWriteComplete, that Outlook won't be chomping at the bit and wanting to
take action asynchronously beyond the steps in my Automation code that must
occur in sequence. (E.g., sending the message out the door before I'm
finished operating on it.)

Am I on the right track in my thinking here?

Thanks again,
-Rob

P.S., on an earlier reply, you suggested:

>> "Set a flag that the message is being submitted in OnSubmit, then check
that flag
in OnWriteComplete. If it is set, do your processing, reset the flag.
Rather than having a global flag, I prefer setting a named property in
OnSubmit
since if a user replies to a message, you get OnWrite/OnWriteComplete for
both
messages."

First, what is a "named property" in OnSubmit? What constitutes a "named
property"?

Otherwise, I don't know how you had boolean flags keeping order between
OnSubmit and OnWriteComplete processing logic on one of your systems. I
tried doing something like that, after your post, with what I'm writing and
have gotten nowhere. Maybe you have an example sitting around that
illustrates exactly how you're using flags on those two events. I tried
implemented them globally because I didn't understand your reference to a
"named property". I'd be curious to know what one is.)

Anyway, maybe I'll need to implement something like these flags in this
thing I'm working on. I don't know yet.


Rob

unread,
May 14, 2001, 2:45:54 PM5/14/01
to
In addition to what I've just written a few minutes ago, I need to ask the
following:

What does "QI" stand for?

Thanks again,
-Rob

---
"Dmitry Streblechenko" <dmi...@dimastr.com> wrote in message
news:edFcm6D3AHA.996@tkmsftngp03...

Dmitry Streblechenko

unread,
May 14, 2001, 4:30:05 PM5/14/01
to
QI = IUnknown::QueryInterface - it is a method found on all COM interfaces
since they all are derived from IUnknown. Essentially you can ask any interface
to give you back another interface if it is supported.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:e7xITUK3AHA.2092@tkmsftngp03...

Rob

unread,
May 14, 2001, 5:40:47 PM5/14/01
to
Thanks Dmitry,

Yes, I think I'm actually going to go ahead and use the in-process server I
made, after all. I put so much work into learning the rudiments of COM,
that it would be a shame not to employ it. It might be nice to further
componentize the project.

It was a revelation to learn that an Outbound message doesn't have to move
into the Outbox folder first, in order to be operated upon by an
externalized system of methods. That, quite to the contrary, the MailItem
and its attachments are available as quite very tangible entities in their
own rights before the Outbox is encountered, and not something not quite
congealed.

I'm glad, also, that you had the foresight to lately suggest a methodology
that presupposes the usage of the COM object. I had thought that you were
using a shorthand/abbreviation for "QueryInterface," but I wasn't sure.

I don't know enough about C++ to proceed from here, though. When you say
that

> Outlook passes you IExchExtCallback.
> 1. QI it for IOutlookExtCallback

>...

How do I do that? This is what is on my extension now:

----------------------------
///////////////////////////////////////
// MSSMExt::QueryInterface
///////////////////////////////////////
STDMETHODIMP MSSMExt::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
HRESULT hResult = S_OK;

*ppvObj = NULL;

if ((IID_IUnknown == riid) || (IID_IExchExt == riid))
*ppvObj = (LPUNKNOWN)this;
else if (IID_IExchExtMessageEvents == riid)
*ppvObj = (LPUNKNOWN) m_pMSSMMsgEvents;
else
hResult = E_NOINTERFACE;

if (NULL != *ppvObj) ((LPUNKNOWN)*ppvObj)->AddRef();

return hResult;
}

----------------------------
And this; here is what a loop-iterated pointer to each attachment looks like
in the code:

pRows->aRow[i].lpProps[0].Value.lpszA


How do I programmatically integrate the two? I've searched high and low and
have come up with nothing by the way of sample code.

Thank you again,
-Rob

---
"Dmitry Streblechenko" <dmi...@dimastr.com> wrote in message

news:#ymLYSL3AHA.1436@tkmsftngp07...

Dmitry Streblechenko

unread,
May 14, 2001, 5:49:25 PM5/14/01
to
No, I meant something like this (Delphi). Don't forget that all Delphi interface
pointers are smart pointers, in C++ you need to explicitly Release() them if you
are not using smart pointers.

function YourExtension.OnWriteComplete(lpeecb :
IExchExtCallback):HResult;stdcall;
var OlItem : IDispatch;
begin
OlItem:=GetOutlookObject(lpeecb);
if OlItem <> nil then begin
YourVBCOMObject.SomeMethod(OlItem);
end;
end;

GetOutlookObject function is defined as follows:

function GetOutlookObject(lpeecb : IExchExtCallback) : IDispatch;
var OEC:IOutlookExtCallback;
Dummy:IUnknown;
DummyDispatch:IDispatch;
begin
Result:=nil;
if S_OK = lpeecb.QueryInterface(IOutlookExtCallback, OEC) then begin
if S_OK = OEC.GetObject(IMAPIProp(Dummy)) then begin
if S_OK = Dummy.QueryInterface(IDispatch, DummyDispatch) then begin
Result:=DummyDispatch;
DummyDispatch:=nil;
end;
Dummy:=nil;
end;
OEC:=nil;
end;
end;

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:eUpvA2L3AHA.1784@tkmsftngp02...

Rob

unread,
May 15, 2001, 2:30:07 PM5/15/01
to
Dmitry,

Are both of those two Delphi functions intended to tbe written inside a
Delphi COM server?

Or is half of that snip--or even the two--eupemistically written as a
diagram, for me to translate into C++ to be written inside of the extension?

I'm not very good at puzzles where I don't understand the rules.

Thanks again,
-Rob

---
"Dmitry Streblechenko" <dmi...@dimastr.com> wrote in message

news:#ZJdt#L3AHA.2152@tkmsftngp07...

Dmitry Streblechenko

unread,
May 15, 2001, 5:42:13 PM5/15/01
to
YourExtension.OnWriteComplete function is your implementation of
OnWriteComplete(). It calls GetOutlookObject() function (below) and passes the
returned value to your VB COM object.
GetOutlookObject() function is a generic function that given IExchExtCallback
returns IDispatch for the current context (MailItem if called from
OnWriteComplete).
The source is in Delphi, but it shouldn't be too hard to translate it to C++.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:OBIFGwW3AHA.1592@tkmsftngp04...

Rob

unread,
May 16, 2001, 2:53:06 AM5/16/01
to
Dmitry,

I really want to thank you for this. The way I understand your post is that
both functions reside in my preëxisting C++ extension. I'll translate from
the Delphi accordingly. I hope.

So, therefore, in addition to the myriad expressions in my OnWriteComplete,
I will add the translated lines of code you have supplied for that
particular event. I will create an entirely new GetOutlookObject function
somewhere else on the .CPP.

I would also like to, again, let you and everyone else on this board know
how much I value the work that you all are doing for students (like myself)
of this stuff. My questions are numerous (and voluminous) sometimes, and it
takes a steadfast party of MVPs to patiently deal with them. (No reply
necessary.)

My thanks again,
-Rob


"Dmitry Streblechenko" <dmi...@dimastr.com> wrote in message

news:#pRrSfY3AHA.1484@tkmsftngp03...

Rob

unread,
May 17, 2001, 1:38:40 PM5/17/01
to
Dmitry,

I'm kind of snagged on this. I have an:

///////////////////////////////////////
// Called by Exchange when it starts
///////////////////////////////////////
LPEXCHEXT CALLBACK ExchEntryPoint(void)
{
return new MSSMExt;
}

I have no earthly idea if this is the callback function of consequence to
which you refer. Is it another Outlook callback I should be dealing with?
Here's what I've got so far:

STDMETHODIMP MSSMMsgEvents::OnWriteComplete(LPEXCHEXTCALLBACK lpeecb,
ULONG ulFlags)
{
// ...

IDispatch* olItem;
olItem = GetOutlookObject(lpeecb);

if(olItem != NULL)
{
_Doohickey->DoSomething(olItem);
}
}


IDispatch* GetOutlookObject(LPEXCHEXTCALLBACK lpeecb)
{
long Result;
LPEXCHEXT OEC;
IUnknown* Dummy;
IDispatch* DummyDispatch;


Result = NULL;

if(S_OK = lpeecb->QueryInterface(---------What is this?---------,
OEC))
{
if(S_OK = OEC->GetObject(IMAPIProp(Dummy)))
{
if(S_OK = Dummy->QueryInterface(IDispatch, DummyDispatch)
{
Result = DummyDispatch;
DummyDispatch = NULL;
}
Dummy = NULL;
}
OEC = NULL;
}

return;
}

/////////////////////////////////////////////////////////

I don't know if the first four declarations are correct in called function.
I don't have a clue about what is euphemistically referred to as
"IOutlookExtCallback." I don't know what to transpose that placeholder
with.

Help?

Thanks,
-Rob


Dmitry Streblechenko

unread,
May 17, 2001, 2:17:10 PM5/17/01
to
1. You need to use IID_IOutlookExtCallback in C++.
2. Everywhere you have SomeObj = NULL; replace it with SomeObj.Release().
Assigning to nullin Delphi automatically releases the interfaces, in case of C++
it only happens if you use a smart pointer
3. Change
Dummy->QueryInterface(IDispatch
to
Dummy->QueryInterface(IID_IDispatch
4. Replace declaration
LPEXCHEXT OEC;
with
LPOutlookExtCallback OEC;

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:uoH37cv3AHA.160@tkmsftngp04...

Rob

unread,
May 18, 2001, 12:42:59 PM5/18/01
to
Thanks, Dmitry.

But I'm meeting with a little failure.:

1. . I don't know what the "IID_OutlookExtCallback" is. There is nothing,
no callback, that includes the word "Outlook" in it, at all. If you mean
something that relates to it, I've tried any number of "IID"s and
"Callback"s, with no positive results at the debugger.

2. I'm getting error lines on the debugger that say "left of '->Release'
must point to class/struct/union" everywhere that I've replaced "Result =
NULL" with "Result.Release()" or Result -> Release().

I think the bigger problem at this time is the Callback. I have absolutely
zero idea what I'm looking for. Therefore I will post the following. Here
is my entire .CPP--top to bottom:

---

#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IMAPITable
#define INITGUID

#include "scansend.h"
#include <stdio.h>
#include <time.h>
#include <mapiguid.H>

// The precompiler directive (that follows) is responsible for the
compiler's auto-creation of basetsd.h and vbwidget.tlb:
#import "c:/winnt/system32/widget/vbwidget.tlb" no_namespace
#include "basetsd.h"

char fname[]="C:/anexecutable.exe";
FILE *fout;

static HINSTANCE ghInstDLL = NULL;


///////////////////////////////////////
// DLL Initialization
///////////////////////////////////////
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID
lpvReserved)
{
if (DLL_PROCESS_ATTACH == fdwReason)
{
ghInstDLL = hinstDLL;
}
return TRUE;
}

///////////////////////////////////////
// Called by Exchange when it starts
///////////////////////////////////////
LPEXCHEXT CALLBACK ExchEntryPoint(void)
{
return new MSSMExt;
}


/////////////////////////////////////////////////////////////////////
// MSSMExt::MSSMExt
/////////////////////////////////////////////////////////////////////
MSSMExt::MSSMExt()
{
m_cRef = 1;
m_pMSSMMsgEvents = new MSSMMsgEvents(this);
};


///////////////////////////////////////
// MSSMExt::QueryInterface
///////////////////////////////////////
STDMETHODIMP MSSMExt::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
HRESULT hResult = S_OK;

*ppvObj = NULL;

if ((IID_IUnknown == riid) || (IID_IExchExt == riid))
*ppvObj = (LPUNKNOWN)this;
else if (IID_IExchExtMessageEvents == riid)
*ppvObj = (LPUNKNOWN) m_pMSSMMsgEvents;
else
hResult = E_NOINTERFACE;

if (NULL != *ppvObj) ((LPUNKNOWN)*ppvObj)->AddRef();

return hResult;
}


///////////////////////////////////////
// MSSMExt::Install
///////////////////////////////////////
STDMETHODIMP MSSMExt::Install(LPEXCHEXTCALLBACK peecb, ULONG eecontext,
ULONG ulFlags)
{
ULONG ulBuildVersion;
HRESULT hr;

m_context = eecontext;

peecb->GetVersion(&ulBuildVersion, EECBGV_GETBUILDVERSION);
if (EECBGV_BUILDVERSION_MAJOR != (ulBuildVersion &
EECBGV_BUILDVERSION_MAJOR_MASK))
{
return S_FALSE;
}

switch (eecontext)
{
case EECONTEXT_SENDNOTEMESSAGE:
case EECONTEXT_SENDPOSTMESSAGE:
case EECONTEXT_SENDRESENDMESSAGE:
hr = S_OK;
break;
default:
hr = S_FALSE;
break;
}
return hr;
}

///////////////////////////////////////
// MSSMMsgEvents::QueryInterface
///////////////////////////////////////
STDMETHODIMP MSSMMsgEvents::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
*ppvObj = NULL;
if (riid == IID_IExchExtMessageEvents)
{
*ppvObj = (LPVOID)this;
AddRef();
return S_OK;
}
if (riid == IID_IUnknown)
{
*ppvObj = (LPVOID) m_pExchExt; // return parent interface
m_pExchExt->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnRead
///////////////////////////////////////
STDMETHODIMP MSSMMsgEvents::OnRead(LPEXCHEXTCALLBACK lpeecb)
{
return S_FALSE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnReadComplete
///////////////////////////////////////
STDMETHODIMP MSSMMsgEvents::OnReadComplete(LPEXCHEXTCALLBACK lpeecb, ULONG
ulFlags)
{
return S_FALSE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnWrite
///////////////////////////////////////
STDMETHODIMP MSSMMsgEvents::OnWrite(LPEXCHEXTCALLBACK lpeecb)
{
return S_FALSE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnWriteComplete
///////////////////////////////////////


STDMETHODIMP MSSMMsgEvents::OnWriteComplete(LPEXCHEXTCALLBACK lpeecb, ULONG
ulFlags)
{

// this method is called after all message properties are written


HRESULT hr;
LPADRLIST FAR lpal = NULL;
LPMDB FAR lpmdb = NULL;
LPMAPIPROP FAR lpmp = NULL;
IMessage *pMsg = NULL;
LPUNKNOWN FAR lpUnk = NULL;
IStream *pstr = NULL;
STATSTG stat;
DWORD velkost;//, q;
LARGE_INTEGER nula = {0L, 0L};


IDispatch* olItem;
olItem = GetOutlookObject(lpeecb);


hr = lpeecb->GetObject(&lpmdb, &lpmp);
if (hr != S_OK) {
// goto error_return;
}
pMsg = (IMessage *) lpmp;
hr = pMsg->OpenProperty(PR_BODY, (LPCIID) &IID_IStream, 0,
MAPI_MODIFY, &lpUnk);
if (hr != S_OK) {
// goto error_return;
}
pstr = (IStream *) lpUnk;

hr = pstr->Stat(&stat, STATFLAG_NONAME);
if (hr != S_OK) {
// goto error_return;
}
velkost = stat.cbSize.LowPart;
hr = pstr->Seek(nula, STREAM_SEEK_SET, NULL); // seek to begin
if (hr != S_OK) {
// goto error_return;
}


SizedSPropTagArray(1,g_sptMsgProps) = {1,
PR_HASATTACH};
LPSPropValue pProps = NULL; HRESULT hRes = 0; ULONG cVals =
0;
if (FAILED(hRes = pMsg->GetProps((LPSPropTagArray) &g_sptMsgProps,
0,
&cVals,
&pProps))) goto Quit;
else
hRes = S_OK;
if (PR_HASATTACH == pProps[0].ulPropTag && pProps[0].Value.b) {
LPMAPITABLE pAttTbl = NULL; LPSRowSet pRows = NULL;
static SizedSPropTagArray(2,sptCols) = {2,PR_ATTACH_LONG_FILENAME,
PR_ATTACH_NUM};
if (SUCCEEDED(hRes = pMsg -> OpenProperty(PR_MESSAGE_ATTACHMENTS,
&IID_IMAPITable,
0,
0,
(LPUNKNOWN *) &pAttTbl)))
{ if (SUCCEEDED(hRes = pAttTbl -> SetColumns(
(LPSPropTagArray) &sptCols,
TBL_BATCH))) {
if (SUCCEEDED(hRes = HrQueryAllRows(pAttTbl,
(LPSPropTagArray) &sptCols,
NULL,
NULL,
0,
&pRows))) {
for (ULONG i = 0; i < pRows -> cRows; i++) {
LPATTACH lpAttach = NULL;
// Verify we received a filename from GetProps
if (! PR_ATTACH_FILENAME ==
pRows->aRow[i].lpProps[0].ulPropTag)
break;
// Verify we received an Attachment Index from GetProps
if (! PR_ATTACH_NUM == pRows->aRow[i].lpProps[1].ulPropTag)
break; // Open the attachment
if (SUCCEEDED(hRes = pMsg->OpenAttach (
pRows->aRow[i].lpProps[1].Value.l,
NULL, MAPI_BEST_ACCESS, &lpAttach)))
{ LPSTREAM pStrmSrc = NULL, pStrmDest =
NULL;
STATSTG StatInfo;
// Open the property of the attachment
// containing the file data
if (FAILED(hRes = lpAttach->OpenProperty(
PR_ATTACH_DATA_BIN,
(LPIID)&IID_IStream,
0,
MAPI_MODIFY,
(LPUNKNOWN *)&pStrmSrc)))
break;
// Open an IStream interface and create the file at the
// same time. This code will create the file in the
// current directory.

if (FAILED(hRes = OpenStreamOnFile(
MAPIAllocateBuffer,
MAPIFreeBuffer,
STGM_CREATE | STGM_READWRITE,
pRows->aRow[i].lpProps[0].Value.lpszA,
NULL,
&pStrmDest)))

break;
pStrmSrc -> Stat(&StatInfo, STATFLAG_NONAME);
hRes = pStrmSrc -> CopyTo(pStrmDest,
StatInfo.cbSize,
NULL,
NULL);
// Commit changes to new stream
pStrmDest -> Commit(0);
// Release each of our streams
pStrmDest -> Release();
pStrmSrc -> Release();


}
// Release the attachment
lpAttach ->
; } } }
} FreeProws(pRows); if (pAttTbl)
pAttTbl -> Release(); } Quit:
MAPIFreeBuffer((LPVOID) pProps);


return S_FALSE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnCheckNames
///////////////////////////////////////
STDMETHODIMP MSSMMsgEvents::OnCheckNames(LPEXCHEXTCALLBACK lpeecb)
{
return S_FALSE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnCheckNamesComplete
///////////////////////////////////////
STDMETHODIMP MSSMMsgEvents::OnCheckNamesComplete(LPEXCHEXTCALLBACK lpeecb,
ULONG ulFlags)
{
return S_FALSE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnSubmit
///////////////////////////////////////
STDMETHODIMP MSSMMsgEvents::OnSubmit(LPEXCHEXTCALLBACK lpeecb)
{
return S_FALSE;
}


///////////////////////////////////////
// MSSMMsgEvents::OnSubmitComplete
///////////////////////////////////////
STDMETHODIMP_ (VOID) MSSMMsgEvents::OnSubmitComplete(LPEXCHEXTCALLBACK
lpeecb, ULONG ulFlags)
{
return;
}


///////////////////////////////////////
//
///////////////////////////////////////


IDispatch* GetOutlookObject(LPEXCHEXTCALLBACK lpeecb)
{
long Result;

IID_IExchExt OEC;

IUnknown* Dummy;
IDispatch* DummyDispatch;


Result->Release();

if(S_OK = lpeecb->QueryInterface(IID_IExchExt, OEC))
{
if(S_OK = OEC->GetObject(IMAPIProp(Dummy)))
{
if(S_OK = Dummy->QueryInterface(IID_IDispatch, DummyDispatch)
{
Result = DummyDispatch;
DummyDispatch->Release();
}
Dummy->Release();
}
OEC->Release();
}

return;
}


---
Thanks, again,
-Rob

---

"Dmitry Streblechenko" <dmi...@dimastr.com> wrote in message

news:eGuIV2v3AHA.1916@tkmsftngp07...

Dmitry Streblechenko

unread,
May 18, 2001, 8:30:18 PM5/18/01
to
1. See http://support.microsoft.com/support/kb/articles/q286/4/08.asp for the
definition of the OutlookExtCallback interface and IID_OutlookExtCallback GUID.
2. "Result" is Delphi specific - it is whatever the function will return. In C++
you need the following

IDispatch *res;
...
res = whatever;
...
return res;
}

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

"Rob" <_@_._> wrote in message news:ucXdJi73AHA.600@tkmsftngp05...

Rob

unread,
May 18, 2001, 9:38:48 PM5/18/01
to
Dmitry,

I sincerely cannot thank you enough. I will do my very best to pull my
share of the weight, and will study up this weekend on the contents of the
link you've provided and the code passage you've written, both generously.

"Teach a man to fish... ."

Thanks again,
-Rob

---
"Dmitry Streblechenko" <dmi...@dimastr.com> wrote in message

news:#bkMbr$3AHA.600@tkmsftngp05...

Rob

unread,
May 19, 2001, 3:46:58 AM5/19/01
to
Dmitry,

I'm sorry. I can't even concentrate on the Release thing right now because
I'm have so much trouble just getting the other things off the ground.
Here's the latest incarnation--followed by its debug report:
---

IDispatch* GetOutlookObject(LPEXCHEXTCALLBACK lpeecb)
{
long Result;

IOutlookExtCallback* OEC;
IUnknown* Dummy;
IDispatch* DummyDispatch;


Result->Release();

if(S_OK == lpeecb->QueryInterface(IID_IOutlookExtCallback, (void **) OEC))
{
if(S_OK == OEC->GetObject(IMAPIProp(Dummy)))
{
if(S_OK == Dummy->QueryInterface(IID_Dispatch, DummyDispatch)


{
Result = DummyDispatch;
DummyDispatch->Release();
}
Dummy->Release();
}
OEC->Release();
}

return;
}

---
My debug looks like below. (Line 363 is "if(S_OK ==
OEC->GetObject(IMAPIProp(Dummy)))":

---
r:\scansend_3\scansend.cpp(363) : error C2440: 'type cast' : cannot convert
from 'struct IUnknown *' to 'struct IMAPIProp'
No constructor could take the source type, or constructor overload
resolution was ambiguous
r:\scansend_3\scansend.cpp(365) : error C2065: 'IID_Dispatch' : undeclared
identifier
r:\scansend_3\scansend.cpp(366) : error C2143: syntax error : missing ')'
before '{'
r:\scansend_3\scansend.cpp(367) : error C2440: '=' : cannot convert from
'struct IDispatch *' to 'long'
This conversion requires a reinterpret_cast, a C-style cast or
function-style cast
r:\scansend_3\scansend.cpp(375) : error C2561: 'GetOutlookObject' : function
must return a value
r:\scansend_3\scansend.cpp(351) : see declaration of
'GetOutlookObject'


---
This is so incomrehensible to me I'm paralyzed by it. Forget about the
clients this whole thing was once intended for. Maybe their children can
use it in their businesses later this century.

-Rob
---


"Dmitry Streblechenko" <dmi...@dimastr.com> wrote in message

news:#bkMbr$3AHA.600@tkmsftngp05...

Rob

unread,
May 21, 2001, 10:51:10 AM5/21/01
to
Dmitry,

I think I've worked out the problems in my C++ code. It compiles great.
I'll start a new thread later today or tomorrow.

Thank you again for supplying the Object Pascal code that I needed in order
to do the above, as well as the C++ tips.

-Rob


0 new messages