AndyJump

I'm creating an addin for exporting emails to msg files. My code will grab the 'mailItem.MAPIOBJECT' pass it to some managed C++/Cli which uses System::IntPtr ipMsg = System::Runtime::InteropServices::Marshal::GetIUnknownForObject(iMessage); to get an IntPtr, and then I'm using LPMESSAGE iMessageToSave = (LPMESSAGE)(ipMsg.ToPointer()); to get the native object. The native object is then passed into a purely native method for saving the LPMESSAGE to an .msg file.

This all works fine untill I move this process into another thread so that the GUI thread can be kept unblocked.

At that point I get:

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

when calling 'pIMessageToSave->CopyTo()', where pIMessageToSave is the object thats been passed around like a hot potato.

This, I think, means that pIMessageToSave doesn't like operating in a thread thats different from the main outlook one.

Am I even close with that guess Can anyone help me

I hope so because this is driving me nuts trying to solve it Wink

p.s. I'm a C# dev who does a bit of C++/Cli when he has to so please excuse my ignorance if I'm missing something very basic and obvious.



Re: Visual Studio Tools for Office Threading in an Outlook Addin

Geoff Darst - MSFT

Hi,

You can't pass interface pointers across apartment boundaries. When you are dealing with managed code, COM interop takes care of all of the marshalling for you. However, since you are grabbing the native object, you have to marshal it yourself. You need to call CoMarshalThreadInterfaceInStream (or CoMarshalInterface if you need more control) followed by CoGetInterfaceAndReleaseStream to unmarshal the pointer in the thread you want to make the call in. See http://msdn2.microsoft.com/en-us/library/ms680112.aspx for more information and http://msdn2.microsoft.com/en-us/library/ms693316.aspx for more documentation on CoMarshalThreadInterfaceInStream.

Sincerely,

Geoff Darst

Microsoft VSTO Team





Re: Visual Studio Tools for Office Threading in an Outlook Addin

AndyJump

Thanks for that Smile

I'm just about to leave work but i'll try it out first thing on monday and post back on how it went.

Many thanks for the fast reply Big Smile





Re: Visual Studio Tools for Office Threading in an Outlook Addin

AndyJump

I'm currently working through the solution but I've hit a snag again :/

Heres the code I'm using on the C# side:

Code Snippet

MSOutlook.MAPIFolder inbox = this.GetNamespace("MAPI").GetDefaultFolder(MSOutlook.OlDefaultFolders.olFolderInbox);

foreach (object itemObject in inbox.Items)

{

MSOutlook.MailItem mailItem = itemObject as MSOutlook.MailItem;

if (mailItem != null && mailItem.Class == MSOutlook.OlObjectClass.olMail)

{

Guid IID_IMessage = new Guid("{00020307-0000-0000-C000-000000000046}");

IStream output;

int hr = CoMarshalInterThreadInterfaceInStream(ref IID_IMessage, mailItem.MAPIOBJECT, out output);

System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(this.ArchiveTestThread));

th.Start(output);

break;

}

}

[DllImport("ole32.dll")]

private static extern int CoMarshalInterThreadInterfaceInStream([In] ref Guid riid,

[MarshalAs(UnmanagedType.IUnknown)] object pUnk,

out IStream ppStm);

This all appears to be working fine and giving me the IStream object, which I am then passing to my worker thread.

Over on the native side I'm then using:

Code Snippet

static int SaveIMessageToMsg2(System::Runtime::InteropServices::ComTypes::IStream^ iStream, System::String^ path)

{

HRESULT hResult;

System::IntPtr ipStrm = System::Runtime::InteropServices::Marshal::GetIUnknownForObject(iStream);

LPSTREAM lps = (LPSTREAM)ipStrm.ToPointer();

LPMESSAGE lpmOutput;

hResult = CoGetInterfaceAndReleaseStream(lps, IID_IMessage, (void **)&lpmOutput);

Which gives me:

An unhandled exception of type 'System.StackOverflowException' occurred in X.ExtendedMapi.DLL

when I call 'CoGetInterfaceAndReleaseStream'.





Re: Visual Studio Tools for Office Threading in an Outlook Addin

AndyJump

I solved the problem by using QueryInterface to explicitly retrive the right Interface and then rewinding the stream to pos 0.

Here's the amended method incase anyone has the same issue:

Code Snippet

static int SaveIMessageToMsg2(System::Runtime::InteropServices::ComTypes::IStream^ iStream, System::String^ path)

{

HRESULT hResult = S_OK;

//AJ: Retrive the stream

System::IntPtr ipStrm = System::Runtime::InteropServices::Marshal::GetIUnknownForObject(iStream);

LPSTREAM lps = NULL;

hResult = ((IUnknown*)ipStrm.ToPointer())->QueryInterface(IID_IStream,(void**)&lps);

if(hResult == S_OK)

{

//AJ: Return stream position to 0

LARGE_INTEGER pos;

pos.HighPart = 0;

pos.LowPart = 0;

hResult = lps->Seek(pos,0,NULL);

if(hResult == S_OK)

{

//AJ: Retrive the IMessage

LPMESSAGE lpmOutput;

hResult = CoGetInterfaceAndReleaseStream(lps, IID_IMessage, (void **)&lpmOutput);

if(hResult == S_OK)

{

LPCTSTR outputPath = SaveToMsg::ConvertStringToLPCTSTR(path);

if(hResult == S_OK)

hResult = NativeSaveToMSG(lpmOutput, outputPath);

}

}

}

return hResult;

}