使用Extended MAPI GetNamesFromIDs获取Outlook属性名的技术问题
关于Extended MAPI GetNamesFromIDs及GetIDsFromNames的问题解答
1. MAPINAMEID的PtrToStructure转换正确性
如果仅传入1个标签,pNames设为1是符合要求的,但核心是你的托管结构体定义必须和原生MAPI的MAPINAMEID严格匹配,尤其是union部分。原生结构定义如下:
typedef struct _MAPINAMEID { LPGUID lpguid; ULONG ulKind; union { ULONG ulID; LPWSTR lpszName; } Kind; } MAPINAMEID;
对应的C#结构体需要用显式布局处理union,示例代码:
[StructLayout(LayoutKind.Explicit)] public struct MAPINAMEID_Union { [FieldOffset(0)] public uint ulID; [FieldOffset(0)] public IntPtr lpszNameW; } [StructLayout(LayoutKind.Sequential)] public struct MAPINAMEID { public IntPtr lpguid; public uint ulKind; public MAPINAMEID_Union Kind; }
转换时直接用Marshal.PtrToStructure<MAPINAMEID>(ptr)即可,只要结构体定义正确,单个标签的场景下转换结果是可靠的。
2. IntPtr lpGuid与Guid类型的对应
lpguid是指向原生GUID的指针,直接用Marshal.PtrToStructure<Guid>(nameID.lpguid)转换即可。如果转换后找不到对应属性,可能是两个原因:
- 结构体定义错误导致指针指向了无效内存,转换出的Guid是错的;
- 该属性使用了自定义Guid,而非MAPI预定义的
PS_MAPI/PS_PUBLIC_STRINGS等,需要确认原邮件对象的上下文是否正确(比如是否在目标邮件的IMessage接口上调用的GetNamesFromIDs)。
3. ulKind的有效值范围
根据MAPI官方规范,ulKind只有两个有效值:
MNID_ID(值为0):对应union里的ulID;MNID_STRING(值为1):对应union里的lpszName。
不存在值为2的情况,如果你读到了其他值,几乎可以肯定是结构体转换错误导致的内存越界读取。
4. 读取lpszNameW时的受保护内存错误
这个错误的核心原因是内存已被释放或指针无效:
- GetNamesFromIDs返回的
MAPINAMEID数组由MAPI分配,使用完毕后必须调用MAPIFreeBuffer释放整个数组内存;如果在释放后再读取lpszNameW,就会触发受保护内存错误; - 结构体定义错误会导致
lpszNameW指向错误的内存地址,读取时直接触发权限错误。
建议:在调用MAPIFreeBuffer前完成字符串读取,且仅读取一次,不要重复使用已释放的指针。
关于GetIDsFromNames CREATE标志的标签一致性问题
使用CREATE标志调用GetIDsFromNames时,在同一个邮箱商店内,相同的命名属性(相同Guid+相同名称/ID)会生成与原邮件相同的标签。
MAPI的命名属性标签分配规则是:同一个商店内,相同的属性标识(Guid+名称/ID)会映射到同一个0x8000以上的标签值。调用带CREATE标志的GetIDsFromNames时,如果属性已存在,会直接返回已有的标签;如果不存在,则创建并分配一个新标签(但后续再调用同一个属性仍会返回这个新标签)。
内存清理注意事项
- GetNamesFromIDs返回的
MAPINAMEID数组内存必须通过MAPIFreeBuffer释放; - 数组内的
lpszNameW指针指向的是数组内部的内存块,不需要单独释放,释放整个数组即可; - 调用GetIDsFromNames时,传入的
MAPINAMEID数组如果是你自己分配的,也要确保正确释放托管内存。
内容的提问来源于stack exchange,提问作者darbid
相关产品推荐
相关产品推荐

