Bad bug when writing RTF directy to an Outlook Item

  • Thread starter Thread starter clc
  • Start date Start date
C

clc

Setting:
Outlook 2003 (SP2)
Windows XP (SP2)
VS2005 C++
Outlook Object Modal 11
MAPI

Problem:
Whenever I save RTF directly to the JournalItem using the code below it is as if the item is left open and not saved. Two symptoms:
1) Opening and changing the item again using only Outlook Object Model I get the error "0xfbc40109 - The operation cannot be performed because the message has been changed."
2) Even if no errors occur, the JournalItem is not updated. It is as if the items is updated with the new data, but not saved or committed. The message still appears to be opened.

Please help!
Ben

Code:

If I create an intem in outlook like this (code shortened for readability)

// Create the Journalitem in our example and set no other properties because we just want a valid EntryID.
_JournalItemPtr pItem pItem = (_JournalItemPtr) m_pStore->polApp->CreateItem( olJournalItem );
pItem->Save();
pItem->Close(olSave);
pItem = NULL;

// Now we use our own routine to get a ptr to the item from Outlook and fill in the details of the Item
pItem = pFolder->FindItem( pEntry ); // UPDATE
pItem->Start = ...mydate
pItem->Subject = ...mysubject
pitem->Save()
pItem->Close(olSave)

// We use MAPI to save our RTF code to the same message zbBody is a stream to a raw RTF code
hr = m_pStore->SetMapiItemRTFBody( (LPTSTR) pItem->GetEntryID(), zbBody);


HRESULT CStore::SetMapiItemRTFBody(LPCTSTR szEID, const ZBlob& zb )
{
HRESULT hr = NOERROR;
BOOL bUpdated = false;
LPBYTE lpbMsgID = NULL;
LPMESSAGE lppMsg = NULL;
LPSTREAM lpRTFCompressed = NULL;
LPSTREAM lpRTFStream = NULL;
LPSPropValue pPropSuppMask = NULL;
ULONG cbWritten = 0;
ULONG cbMsgID = 0;
ULONG uType = 0;
if (!IsHexEntryIDValid(CW2A(szEID)))
return ResultFromScode(E_INVALIDARG);

cbMsgID = (ULONG) _tcslen(szEID) / 2;

hr = MAPIAllocateBuffer( cbMsgID, (void **) &lpbMsgID );
if (FAILED(hr)) return hr;

if( !FBinFromHex((LPTSTR) (LPSTR) CW2A(szEID), lpbMsgID) )
{
hr = ResultFromScode(E_INVALIDARG);
goto Quit;
}
hr = m_pMapiStore->OpenEntry(cbMsgID, (LPENTRYID) lpbMsgID, NULL, MAPI_BEST_ACCESS | MAPI_NO_CACHE, &uType, (LPUNKNOWN *) &lppMsg);
if( FAILED(hr) )
goto Quit;

// Call the message's IMAPIProp::OpenProperty method to open the PR_RTF_COMPRESSED property,
// specifying IID_IStream as the interface identifier and setting the MAPI_CREATE flag.
hr = lppMsg->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, 0, MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN *) &lpRTFCompressed);
if (FAILED(hr))
goto Quit;

hr = HrGetOneProp(lppMsg, PR_STORE_SUPPORT_MASK, &pPropSuppMask);
if (FAILED(hr))
goto Quit;

// Call the WrapCompressedRTFStream function, passing the STORE_UNCOMPRESSED_RTF flag if the STORE_UNCOMPRESSED_RTF
// bit is set in the message store's PR_STORE_SUPPORT_MASK property and get IStream pointer for uncompressed RTF,
// to which we will write
hr = WrapCompressedRTFStream(lpRTFCompressed, MAPI_MODIFY | (pPropSuppMask->Value.l & STORE_UNCOMPRESSED_RTF), &lpRTFStream);

// Call either IStream::Write or IStream::CopyTo to write the message text to the stream returned
// from WrapCompressedRTFStream. Write the text that was passed to the uncompressed RTF strea
hr = lpRTFStream->Write( zb.GetPtr(), zb.GetSize(), &cbWritten );
if (FAILED(hr))
goto Quit;

// Call the Commit and Release methods on the stream returned from the OpenProperty method
hr = lpRTFStream->Commit(STGC_OVERWRITE);
if (FAILED(hr))
goto Quit;

// Sync the RTF and plain text properties of the Message
hr = RTFSync(lppMsg, RTF_SYNC_RTF_CHANGED, &bUpdated);
if (FAILED(hr))
goto Quit;

// If the message was updated, save changes to the message
if (bUpdated)
{
hr = lppMsg->SaveChanges(0); // KEEP_OPEN_READWRITE);
if( hr == MAPI_E_OBJECT_CHANGED )
hr = lppMsg->SaveChanges(FORCE_SAVE);
lppMsg->SubmitMessage(0);
}

Quit:
if( pPropSuppMask)
MAPIFreeBuffer(pPropSuppMask);

UlRelease(lppMsg);
UlRelease(lpRTFStream);
UlRelease(lpRTFCompressed);
MAPIFreeBuffer(lpbMsgID);

return hr;
}
 
Do not reopen the message using IMsgStore::OpenEntry. You can retrieve
IMessage from the Outlook Object Model using the JournalItem.MAPIOBJECT
property.
Why do you call IMessage::SubmitMessage()?

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

Setting:
Outlook 2003 (SP2)
Windows XP (SP2)
VS2005 C++
Outlook Object Modal 11
MAPI

Problem:
Whenever I save RTF directly to the JournalItem using the code below it is
as if the item is left open and not saved. Two symptoms:
1) Opening and changing the item again using only Outlook Object Model I get
the error "0xfbc40109 - The operation cannot be performed because the
message has been changed."
2) Even if no errors occur, the JournalItem is not updated. It is as if the
items is updated with the new data, but not saved or committed. The message
still appears to be opened.

Please help!
Ben

Code:

If I create an intem in outlook like this (code shortened for readability)

// Create the Journalitem in our example and set no other properties because
we just want a valid EntryID.
_JournalItemPtr pItem pItem = (_JournalItemPtr)
m_pStore->polApp->CreateItem( olJournalItem );
pItem->Save();
pItem->Close(olSave);
pItem = NULL;

// Now we use our own routine to get a ptr to the item from Outlook and fill
in the details of the Item
pItem = pFolder->FindItem( pEntry ); // UPDATE
pItem->Start = ...mydate
pItem->Subject = ...mysubject
pitem->Save()
pItem->Close(olSave)

// We use MAPI to save our RTF code to the same message zbBody is a stream
to a raw RTF code
hr = m_pStore->SetMapiItemRTFBody( (LPTSTR) pItem->GetEntryID(), zbBody);


HRESULT CStore::SetMapiItemRTFBody(LPCTSTR szEID, const ZBlob& zb )
{
HRESULT hr = NOERROR;
BOOL bUpdated = false;
LPBYTE lpbMsgID = NULL;
LPMESSAGE lppMsg = NULL;
LPSTREAM lpRTFCompressed = NULL;
LPSTREAM lpRTFStream = NULL;
LPSPropValue pPropSuppMask = NULL;
ULONG cbWritten = 0;
ULONG cbMsgID = 0;
ULONG uType = 0;
if (!IsHexEntryIDValid(CW2A(szEID)))
return ResultFromScode(E_INVALIDARG);
cbMsgID = (ULONG) _tcslen(szEID) / 2;
hr = MAPIAllocateBuffer( cbMsgID, (void **) &lpbMsgID );
if (FAILED(hr)) return hr;
if( !FBinFromHex((LPTSTR) (LPSTR) CW2A(szEID), lpbMsgID) )
{
hr = ResultFromScode(E_INVALIDARG);
goto Quit;
}
hr = m_pMapiStore->OpenEntry(cbMsgID, (LPENTRYID) lpbMsgID, NULL,
MAPI_BEST_ACCESS | MAPI_NO_CACHE, &uType, (LPUNKNOWN *) &lppMsg);
if( FAILED(hr) )
goto Quit;
// Call the message's IMAPIProp::OpenProperty method to open the
PR_RTF_COMPRESSED property,
// specifying IID_IStream as the interface identifier and setting the
MAPI_CREATE flag.
hr = lppMsg->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, 0,
MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN *) &lpRTFCompressed);
if (FAILED(hr))
goto Quit;
hr = HrGetOneProp(lppMsg, PR_STORE_SUPPORT_MASK, &pPropSuppMask);
if (FAILED(hr))
goto Quit;
// Call the WrapCompressedRTFStream function, passing the
STORE_UNCOMPRESSED_RTF flag if the STORE_UNCOMPRESSED_RTF
// bit is set in the message store's PR_STORE_SUPPORT_MASK property and
get IStream pointer for uncompressed RTF,
// to which we will write
hr = WrapCompressedRTFStream(lpRTFCompressed, MAPI_MODIFY |
(pPropSuppMask->Value.l & STORE_UNCOMPRESSED_RTF), &lpRTFStream);
// Call either IStream::Write or IStream::CopyTo to write the message
text to the stream returned
// from WrapCompressedRTFStream. Write the text that was passed to the
uncompressed RTF strea
hr = lpRTFStream->Write( zb.GetPtr(), zb.GetSize(), &cbWritten );
if (FAILED(hr))
goto Quit;
// Call the Commit and Release methods on the stream returned from the
OpenProperty method
hr = lpRTFStream->Commit(STGC_OVERWRITE);
if (FAILED(hr))
goto Quit;
// Sync the RTF and plain text properties of the Message
hr = RTFSync(lppMsg, RTF_SYNC_RTF_CHANGED, &bUpdated);
if (FAILED(hr))
goto Quit;
// If the message was updated, save changes to the message
if (bUpdated)
{
hr = lppMsg->SaveChanges(0); // KEEP_OPEN_READWRITE);
if( hr == MAPI_E_OBJECT_CHANGED )
hr = lppMsg->SaveChanges(FORCE_SAVE);
lppMsg->SubmitMessage(0);
}
Quit:
if( pPropSuppMask)
MAPIFreeBuffer(pPropSuppMask);
UlRelease(lppMsg);
UlRelease(lpRTFStream);
UlRelease(lpRTFCompressed);
MAPIFreeBuffer(lpbMsgID);
return hr;
}
 
Dmitry,

It didn't even occur to me to use the MAPIOBJECT and it DID solve part of the problem.

Now the message body is updated correctly with raw RTF text as expected and it correctly shows up in the Journal within outlook both in Autopreview mode and in the reading pane. But the changes are not reflected when I open the actual message which is really weird. I've modified the my code to the following shortened version.

// Create the Journalitem in our example and set no other properties because we just want a valid EntryID.
_JournalItemPtr pItem pItem = (_JournalItemPtr)
m_pStore->polApp->CreateItem( olJournalItem );
pItem->Save();
pItem->Close(olSave);
pItem = NULL;

// Now we use our own routine to get a ptr to the item from Outlook and fill in the details of the Item
pItem = pFolder->FindItem( pEntry ); // UPDATE

pItem->Start = ...mydate

pItem->Subject = ...mysubject

// Write RTF body
BOOL bUpdated = false;
LPMESSAGE lppMsg = NULL;
LPSTREAM lpRTFCompressed = NULL;
LPSTREAM lpRTFStream = NULL;
LPSPropValue pPropSuppMask = NULL;
ULONG cbWritten = 0;
ULONG uType = 0;

IUnknown* pUnk = pItem->GetMAPIOBJECT();
hr = pUnk->QueryInterface(IID_IMessage, (void**) &lppMsg);

// Call the message's IMAPIProp::OpenProperty method to open the PR_RTF_COMPRESSED property,
// specifying IID_IStream as the interface identifier and setting the MAPI_CREATE flag.
hr = lppMsg->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, 0, MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN *) &lpRTFCompressed);
if (FAILED(hr))
goto Quit;

hr = HrGetOneProp(lppMsg, PR_STORE_SUPPORT_MASK, &pPropSuppMask);
if (FAILED(hr))
goto Quit;

// Call the WrapCompressedRTFStream function, passing the STORE_UNCOMPRESSED_RTF flag if the STORE_UNCOMPRESSED_RTF
// bit is set in the message store's PR_STORE_SUPPORT_MASK property and get IStream pointer for uncompressed RTF,
// to which we will write
hr = WrapCompressedRTFStream(lpRTFCompressed, MAPI_MODIFY | (pPropSuppMask->Value.l & STORE_UNCOMPRESSED_RTF), &lpRTFStream);

// Call either IStream::Write or IStream::CopyTo to write the message text to the stream returned
// from WrapCompressedRTFStream. Write the text that was passed to the uncompressed RTF strea
hr = lpRTFStream->Write( zbBody.GetPtr(), zbBody.GetSize(), &cbWritten );
if (FAILED(hr))
goto Quit;

// Call the Commit and Release methods on the stream returned from the OpenProperty method
hr = lpRTFStream->Commit(STGC_OVERWRITE);
if (FAILED(hr))
goto Quit;

// Sync the RTF and plain text properties of the Message
hr = RTFSync(lppMsg, RTF_SYNC_RTF_CHANGED, &bUpdated);
if (FAILED(hr))
goto Quit;

// If the message was updated, save changes to the message
if (bUpdated)
hr = lppMsg->SaveChanges(KEEP_OPEN_READWRITE);

Quit:
if( pPropSuppMask)
MAPIFreeBuffer(pPropSuppMask);
UlRelease(lppMsg);
UlRelease(lpRTFStream);
UlRelease(lpRTFCompressed);
UlRelease(pUnk);
// Done writing RTF body

pitem->Save();
pItem->Close(olSave);
pItem = NULL
 
That means that Outlook is still holding a reference to the item. It will
see the changes when thee item is completely dereferenced.
Can you see the changes after you restart Outlook?

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

Dmitry,

It didn't even occur to me to use the MAPIOBJECT and it DID solve part of
the problem.

Now the message body is updated correctly with raw RTF text as expected and
it correctly shows up in the Journal within outlook both in Autopreview mode
and in the reading pane. But the changes are not reflected when I open the
actual message which is really weird. I've modified the my code to the
following shortened version.

// Create the Journalitem in our example and set no other properties because
we just want a valid EntryID.
_JournalItemPtr pItem pItem = (_JournalItemPtr)
m_pStore->polApp->CreateItem( olJournalItem );
pItem->Save();
pItem->Close(olSave);
pItem = NULL;

// Now we use our own routine to get a ptr to the item from Outlook and fill
in the details of the Item
pItem = pFolder->FindItem( pEntry ); // UPDATE

pItem->Start = ...mydate

pItem->Subject = ...mysubject

// Write RTF body
BOOL bUpdated = false;
LPMESSAGE lppMsg = NULL;
LPSTREAM lpRTFCompressed = NULL;
LPSTREAM lpRTFStream = NULL;
LPSPropValue pPropSuppMask = NULL;
ULONG cbWritten = 0;
ULONG uType = 0;
IUnknown* pUnk = pItem->GetMAPIOBJECT();
hr = pUnk->QueryInterface(IID_IMessage, (void**) &lppMsg);
// Call the message's IMAPIProp::OpenProperty method to open the
PR_RTF_COMPRESSED property,
// specifying IID_IStream as the interface identifier and setting the
MAPI_CREATE flag.
hr = lppMsg->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, 0,
MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN *) &lpRTFCompressed);
if (FAILED(hr))
goto Quit;
hr = HrGetOneProp(lppMsg, PR_STORE_SUPPORT_MASK, &pPropSuppMask);
if (FAILED(hr))
goto Quit;
// Call the WrapCompressedRTFStream function, passing the
STORE_UNCOMPRESSED_RTF flag if the STORE_UNCOMPRESSED_RTF
// bit is set in the message store's PR_STORE_SUPPORT_MASK property and
get IStream pointer for uncompressed RTF,
// to which we will write
hr = WrapCompressedRTFStream(lpRTFCompressed, MAPI_MODIFY |
(pPropSuppMask->Value.l & STORE_UNCOMPRESSED_RTF), &lpRTFStream);
// Call either IStream::Write or IStream::CopyTo to write the message
text to the stream returned
// from WrapCompressedRTFStream. Write the text that was passed to the
uncompressed RTF strea
hr = lpRTFStream->Write( zbBody.GetPtr(), zbBody.GetSize(),
&cbWritten );
if (FAILED(hr))
goto Quit;
// Call the Commit and Release methods on the stream returned from the
OpenProperty method
hr = lpRTFStream->Commit(STGC_OVERWRITE);
if (FAILED(hr))
goto Quit;
// Sync the RTF and plain text properties of the Message
hr = RTFSync(lppMsg, RTF_SYNC_RTF_CHANGED, &bUpdated);
if (FAILED(hr))
goto Quit;
// If the message was updated, save changes to the message
if (bUpdated)
hr = lppMsg->SaveChanges(KEEP_OPEN_READWRITE);

Quit:
if( pPropSuppMask)
MAPIFreeBuffer(pPropSuppMask);
UlRelease(lppMsg);
UlRelease(lpRTFStream);
UlRelease(lpRTFCompressed);
UlRelease(pUnk);
// Done writing RTF body
pitem->Save();
pItem->Close(olSave);
pItem = NULL
 
You're on to something. It does show up correctly if I restart outlook. I
mean kill it from within task manager and then restart it. That does the
trick. So you are suggesting that outlook is holding on a reference to it
someplace? I would think that when I clear the smart pointer _JournalItemPtr
by reseting it to NULL after I am done with it, it should be released... In
essence I am doing this

_JournalItemPtr pItem = Getmypointer(EntryID)...
// Update my data
pItem->Subject = "My new subject"
pItem->body = "my rtf code"
pItem->save() // this alone should save and release the
item
pItem->Close(olSave) // I go one step further and close it again
pItem = NULL // This should release all of my references to
the item so why and where would
// outlook hold on to the reference
to my message?

Thanks
Ben
 
Dmitry,

This seems to have fixed it but I don't like it. I don't think it is right
and it might cause other problems down the road. Same example, but by
placing an extral pItem->close(olSave) after pitem->save() seems to have
solved it.

_JournalItemPtr pItem = Getmypointer(EntryID)...
// Update my data
pItem->Subject = "My new subject"
if( there is a body )
{
pItem->body = "my rtf code"
pItem->save() // works but should be unnecessary
pItem->Close(olSave) // go figure!
}
pItem->save() // this alone should save and release the
item
pItem->Close(olSave) // I go one step further and close it again
pItem = NULL // This should release all of my references to
the item so why and where would
// outlook hold on to the reference
to my message?

Thanks
Ben
 
When it is selected it appears to work fine. When it is not, it does not
work fine. But calling Save and Close(olSave) twice in concession seams to
fix the problem all together. Like I said, I don't like it but it works. I
hope to find the real cause for the problem and fix it right.
 
I reference LinksPtr and release it for the assigned contacts to an item.
I'll go through my code again and see if I may not releasing it properly

Thanks for you help Dmitry!!!!
 
I was thinking about an implicit variable, something like
MailItem.Attachments.Item
here Attachments collection will be stored in an implicit variable that you
have no control over.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool
 
Well it works for now. I'll spend more time on it later if it becomes an issue. Thank you very kindly for your help. Do you know Pocket Outlook Object Model too? I'm having a weird problem in RELEASE mode that I do not see in DEBUG mode. If you think you might know the answer please take a look at the message in Microsoft.public.pocketpc.developer with the subject "+POOM +CoCreateInstance = 0x80040154 or REGDB_E_CLASSNOTREG" posted today.
 
No, I never touched the Pocket PC Outlook, sorry.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool
Well it works for now. I'll spend more time on it later if it becomes an issue. Thank you very kindly for your help. Do you know Pocket Outlook Object Model too? I'm having a weird problem in RELEASE mode that I do not see in DEBUG mode. If you think you might know the answer please take a look at the message in Microsoft.public.pocketpc.developer with the subject "+POOM +CoCreateInstance = 0x80040154 or REGDB_E_CLASSNOTREG" posted today.
 
Back
Top