I am trying to view property names from an Outlook message using extended MAPI, ie the contents of a MAPINAMEID structure.
I get a list of property tags for a message and then I am passing the below code a single Tag (uTag) which has a value greater than 0x80000000.
GetNamesFromIDs(out IntPtr lppPropTags, ref Guid lpPropSetGuid, uint ulFlags,
out uint lpcPropNames, out IntPtr lpppPropNames);
The snippet of relevant code from something much larger.
IntPtr propNames;
SPropTagArray ptArray = new SPropTagArray
{
cValues = (uint)1,
aulPropTag = new uint[] { uTag },
};
IntPtr propTagArrayPtr = Marshal.AllocHGlobal(Marshal.SizeOf<SPropTagArray>());
Marshal.StructureToPtr(ptArray, propTagArrayPtr, true);
HRESULT hr = mapiObj_.GetNamesFromIDs(out propTagArrayPtr, Guid.Empty, 0, out uint pNames, out propNames);
if (hr == HRESULT.S_OK && propNames != IntPtr.Zero)
{
MAPINAMEID nameID = (MAPINAMEID)Marshal.PtrToStructure(propNames, typeof(MAPINAMEID));
LPGuid = (Guid)Marshal.PtrToStructure(nameID.pGuid, typeof(Guid));
ULKind = (uint)nameID.ulKind;
PropKind = nameID.Kind;
if ((KindTypes)ULKind == KindTypes.MNID_ID)
{
IID = nameID.Kind.lID;
}
else if ((KindTypes)ULKind == KindTypes.MNID_STRING)
{
Name = Marshal.PtrToStringUni(nameID.Kind.lpszNameW);
}
}
MAPINative.MAPIFreeBuffer(propTagArrayPtr);
MAPINative.MAPIFreeBuffer(propNames);
The structures look like this
[StructLayout(LayoutKind.Sequential)]
public struct SPropTagArray
{
public uint cValues;
public uint[] aulPropTag;
}
[StructLayout(LayoutKind.Sequential)]
struct MAPINAMEID
{
public IntPtr pGuid;
public int ulKind;
public Kind Kind;
}
[StructLayout(LayoutKind.Explicit)]
struct Kind
{
[FieldOffset(0)]
public int lID;
[FieldOffset(0)]
public IntPtr lpszNameW;
}
- Is my PtrToStructure for MAPINAMEID correct? I only give 1 tag in the SPropTagArray and pNames always equals 1.
- Is IntPtr pGuid actually a Guid or do I need a special structure for this? The results it produces cannot be found on the message.
- Is it possible that ulKind returns a number other than 0 or 1? (Assuming that is what it should return for MNID_STRING (1) and MNID_ID (2)
- Marshal.PtrToStringUni(nameID.Kind.lpszNameW) also is raising a Attempted to read or write protected memory. This is often an indication that other memory is corrupt. after a few calls.
Initially I was not even looking at the MAPINAMEID . I was simply passing the IntPtr propNames without touching it back into GetIDsFromNames with a CREATE flag to add the property to a new message. I noticed that some calls to GetIDsFromNames was failing with an Invalid args as the result. I assume this is because of something in the MAPINAMEID.
EDIT 1 - Based on Dmitry's comments As he wanted a little more context, this code is part of a VSTO Outlook addin which takes the MAPIOBJECT from OOM and then copies each individual property. Part of that copying includes the NamedProperties, however, for certain reasons I want to check a few that are added and remove them, thus the need to know the names.
I am now using the following, the first 2 arguments are by ref I am passing null for lpPropSetGuid
HRESULT GetNamesFromIDs(ref IntPtr lppPropTags, ref IntPtr lpPropSetGuid, uint ulFlags,out uint lpcPropNames, out IntPtr lpppPropNames);
I did try to use an unit[] for lppPropTags as I think was suggested but got an invalid args response.
This appears to work.
SPropTagArray propTagArray = new SPropTagArray
{
cValues = 1,
aulPropTag = new uint[1]
};
propTagArray.aulPropTag[0] = uTag;
IntPtr propTagArrayPtr = Marshal.AllocHGlobal(Marshal.SizeOf(propTagArray));
Marshal.StructureToPtr(propTagArray, propTagArrayPtr, false);
The most major changes I made were to MAPINAMEID
[StructLayout(LayoutKind.Sequential)]
private struct MAPINAMEID_A
{
public IntPtr lpguid;
public uint ulKind;
public IntPtr lpwstrName; // or lID
};
Documentation seems to say that I get back ONLY a GUID struct so I changed over to one
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct _GUID
{
public Int32 Data1;
public Int16 Data2;
public Int16 Data3;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Data4;
}
I also slightly altered how I get the MAPINAMEID (currently hard coded for 1 element only)
MAPINAMEID_A nameID = new MAPINAMEID_A();
for (int i = 0; i < 1; i++)
{
// Get the pointer at the current index
IntPtr ptrElement = Marshal.ReadIntPtr(mAPINAMEIDArray, i * IntPtr.Size);
// Marshal the pointer to a MAPINAMEID structure
nameID = Marshal.PtrToStructure<MAPINAMEID_A>(ptrElement);
}
_GUID DLPGuid = (_GUID)Marshal.PtrToStructure(nameID.lpguid, typeof(_GUID));
LPGuid = new Guid(DLPGuid.Data1, DLPGuid.Data2, DLPGuid.Data3,
DLPGuid.Data4[0], DLPGuid.Data4[1], DLPGuid.Data4[2], DLPGuid.Data4[3],
DLPGuid.Data4[4], DLPGuid.Data4[5], DLPGuid.Data4[6], DLPGuid.Data4[7]);
Currently no errors but not sure about cleaning up memory yet.
Dmitry my understanding (yet again from you) is that when I add a named property to a new message with CREATE in GetIDsFromNames that a new tag number is created. However from what i can tell is it possible that the same tag is generated when just copying these properties?