mirror of
https://github.com/reactos/wine.git
synced 2024-11-25 20:59:54 +00:00
1122 lines
34 KiB
C
1122 lines
34 KiB
C
/*
|
|
* MAPI Utility functions
|
|
*
|
|
* Copyright 2004 Jon Griffiths
|
|
* Copyright 2009 Owen Rudge for CodeWeavers
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
|
|
#define COBJMACROS
|
|
#define NONAMELESSUNION
|
|
#define NONAMELESSSTRUCT
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winreg.h"
|
|
#include "winuser.h"
|
|
#include "winerror.h"
|
|
#include "winternl.h"
|
|
#include "objbase.h"
|
|
#include "shlwapi.h"
|
|
#include "wine/debug.h"
|
|
#include "wine/unicode.h"
|
|
#include "mapival.h"
|
|
#include "xcmc.h"
|
|
#include "msi.h"
|
|
#include "util.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(mapi);
|
|
|
|
static const BYTE digitsToHex[] = {
|
|
0,1,2,3,4,5,6,7,8,9,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,14,15,
|
|
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
|
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,10,11,12,13,
|
|
14,15 };
|
|
|
|
MAPI_FUNCTIONS mapiFunctions;
|
|
|
|
/**************************************************************************
|
|
* ScInitMapiUtil (MAPI32.33)
|
|
*
|
|
* Initialise Mapi utility functions.
|
|
*
|
|
* PARAMS
|
|
* ulReserved [I] Reserved, pass 0.
|
|
*
|
|
* RETURNS
|
|
* Success: S_OK. Mapi utility functions may be called.
|
|
* Failure: MAPI_E_INVALID_PARAMETER, if ulReserved is not 0.
|
|
*
|
|
* NOTES
|
|
* Your application does not need to call this function unless it does not
|
|
* call MAPIInitialize()/MAPIUninitialize().
|
|
*/
|
|
SCODE WINAPI ScInitMapiUtil(ULONG ulReserved)
|
|
{
|
|
if (mapiFunctions.ScInitMapiUtil)
|
|
return mapiFunctions.ScInitMapiUtil(ulReserved);
|
|
|
|
FIXME("(0x%08x)stub!\n", ulReserved);
|
|
if (ulReserved)
|
|
return MAPI_E_INVALID_PARAMETER;
|
|
return S_OK;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* DeinitMapiUtil (MAPI32.34)
|
|
*
|
|
* Uninitialise Mapi utility functions.
|
|
*
|
|
* PARAMS
|
|
* None.
|
|
*
|
|
* RETURNS
|
|
* Nothing.
|
|
*
|
|
* NOTES
|
|
* Your application does not need to call this function unless it does not
|
|
* call MAPIInitialize()/MAPIUninitialize().
|
|
*/
|
|
VOID WINAPI DeinitMapiUtil(void)
|
|
{
|
|
if (mapiFunctions.DeinitMapiUtil)
|
|
mapiFunctions.DeinitMapiUtil();
|
|
else
|
|
FIXME("()stub!\n");
|
|
}
|
|
|
|
typedef LPVOID *LPMAPIALLOCBUFFER;
|
|
|
|
/**************************************************************************
|
|
* MAPIAllocateBuffer (MAPI32.12)
|
|
* MAPIAllocateBuffer@8 (MAPI32.13)
|
|
*
|
|
* Allocate a block of memory.
|
|
*
|
|
* PARAMS
|
|
* cbSize [I] Size of the block to allocate in bytes
|
|
* lppBuffer [O] Destination for pointer to allocated memory
|
|
*
|
|
* RETURNS
|
|
* Success: S_OK. *lppBuffer is filled with a pointer to a memory block of
|
|
* length cbSize bytes.
|
|
* Failure: MAPI_E_INVALID_PARAMETER, if lppBuffer is NULL.
|
|
* MAPI_E_NOT_ENOUGH_MEMORY, if the memory allocation fails.
|
|
*
|
|
* NOTES
|
|
* Memory allocated with this function should be freed with MAPIFreeBuffer().
|
|
* Further allocations of memory may be linked to the pointer returned using
|
|
* MAPIAllocateMore(). Linked allocations are freed when the initial pointer
|
|
* is feed.
|
|
*/
|
|
SCODE WINAPI MAPIAllocateBuffer(ULONG cbSize, LPVOID *lppBuffer)
|
|
{
|
|
LPMAPIALLOCBUFFER lpBuff;
|
|
|
|
TRACE("(%d,%p)\n", cbSize, lppBuffer);
|
|
|
|
if (mapiFunctions.MAPIAllocateBuffer)
|
|
return mapiFunctions.MAPIAllocateBuffer(cbSize, lppBuffer);
|
|
|
|
if (!lppBuffer)
|
|
return E_INVALIDARG;
|
|
|
|
lpBuff = HeapAlloc(GetProcessHeap(), 0, cbSize + sizeof(*lpBuff));
|
|
if (!lpBuff)
|
|
return MAPI_E_NOT_ENOUGH_MEMORY;
|
|
|
|
TRACE("initial allocation:%p, returning %p\n", lpBuff, lpBuff + 1);
|
|
*lpBuff++ = NULL;
|
|
*lppBuffer = lpBuff;
|
|
return S_OK;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* MAPIAllocateMore (MAPI32.14)
|
|
* MAPIAllocateMore@12 (MAPI32.15)
|
|
*
|
|
* Allocate a block of memory linked to a previous allocation.
|
|
*
|
|
* PARAMS
|
|
* cbSize [I] Size of the block to allocate in bytes
|
|
* lpOrig [I] Initial allocation to link to, from MAPIAllocateBuffer()
|
|
* lppBuffer [O] Destination for pointer to allocated memory
|
|
*
|
|
* RETURNS
|
|
* Success: S_OK. *lppBuffer is filled with a pointer to a memory block of
|
|
* length cbSize bytes.
|
|
* Failure: MAPI_E_INVALID_PARAMETER, if lpOrig or lppBuffer is invalid.
|
|
* MAPI_E_NOT_ENOUGH_MEMORY, if memory allocation fails.
|
|
*
|
|
* NOTES
|
|
* Memory allocated with this function and stored in *lppBuffer is freed
|
|
* when lpOrig is passed to MAPIFreeBuffer(). It should not be freed independently.
|
|
*/
|
|
SCODE WINAPI MAPIAllocateMore(ULONG cbSize, LPVOID lpOrig, LPVOID *lppBuffer)
|
|
{
|
|
LPMAPIALLOCBUFFER lpBuff = lpOrig;
|
|
|
|
TRACE("(%d,%p,%p)\n", cbSize, lpOrig, lppBuffer);
|
|
|
|
if (mapiFunctions.MAPIAllocateMore)
|
|
return mapiFunctions.MAPIAllocateMore(cbSize, lpOrig, lppBuffer);
|
|
|
|
if (!lppBuffer || !lpBuff || !--lpBuff)
|
|
return E_INVALIDARG;
|
|
|
|
/* Find the last allocation in the chain */
|
|
while (*lpBuff)
|
|
{
|
|
TRACE("linked:%p->%p\n", lpBuff, *lpBuff);
|
|
lpBuff = *lpBuff;
|
|
}
|
|
|
|
if (SUCCEEDED(MAPIAllocateBuffer(cbSize, lppBuffer)))
|
|
{
|
|
*lpBuff = ((LPMAPIALLOCBUFFER)*lppBuffer) - 1;
|
|
TRACE("linking %p->%p\n", lpBuff, *lpBuff);
|
|
}
|
|
return *lppBuffer ? S_OK : MAPI_E_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* MAPIFreeBuffer (MAPI32.16)
|
|
* MAPIFreeBuffer@4 (MAPI32.17)
|
|
*
|
|
* Free a block of memory and any linked allocations associated with it.
|
|
*
|
|
* PARAMS
|
|
* lpBuffer [I] Memory to free, returned from MAPIAllocateBuffer()
|
|
*
|
|
* RETURNS
|
|
* S_OK.
|
|
*/
|
|
ULONG WINAPI MAPIFreeBuffer(LPVOID lpBuffer)
|
|
{
|
|
LPMAPIALLOCBUFFER lpBuff = lpBuffer;
|
|
|
|
TRACE("(%p)\n", lpBuffer);
|
|
|
|
if (mapiFunctions.MAPIFreeBuffer)
|
|
return mapiFunctions.MAPIFreeBuffer(lpBuffer);
|
|
|
|
if (lpBuff && --lpBuff)
|
|
{
|
|
while (lpBuff)
|
|
{
|
|
LPVOID lpFree = lpBuff;
|
|
|
|
lpBuff = *lpBuff;
|
|
|
|
TRACE("linked:%p->%p, freeing %p\n", lpFree, lpBuff, lpFree);
|
|
HeapFree(GetProcessHeap(), 0, lpFree);
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WrapProgress@20 (MAPI32.41)
|
|
*/
|
|
HRESULT WINAPI WrapProgress(PVOID unk1, PVOID unk2, PVOID unk3, PVOID unk4, PVOID unk5)
|
|
{
|
|
/* Native does not implement this function */
|
|
return MAPI_E_NO_SUPPORT;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* HrThisThreadAdviseSink@8 (MAPI32.42)
|
|
*
|
|
* Ensure that an advise sink is only notified in its originating thread.
|
|
*
|
|
* PARAMS
|
|
* lpSink [I] IMAPIAdviseSink interface to be protected
|
|
* lppNewSink [I] Destination for wrapper IMAPIAdviseSink interface
|
|
*
|
|
* RETURNS
|
|
* Success: S_OK. *lppNewSink contains a new sink to use in place of lpSink.
|
|
* Failure: E_INVALIDARG, if any parameter is invalid.
|
|
*/
|
|
HRESULT WINAPI HrThisThreadAdviseSink(LPMAPIADVISESINK lpSink, LPMAPIADVISESINK* lppNewSink)
|
|
{
|
|
if (mapiFunctions.HrThisThreadAdviseSink)
|
|
return mapiFunctions.HrThisThreadAdviseSink(lpSink, lppNewSink);
|
|
|
|
FIXME("(%p,%p)semi-stub\n", lpSink, lppNewSink);
|
|
|
|
if (!lpSink || !lppNewSink)
|
|
return E_INVALIDARG;
|
|
|
|
/* Don't wrap the sink for now, just copy it */
|
|
*lppNewSink = lpSink;
|
|
IMAPIAdviseSink_AddRef(lpSink);
|
|
return S_OK;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* FBinFromHex (MAPI32.44)
|
|
*
|
|
* Create an array of binary data from a string.
|
|
*
|
|
* PARAMS
|
|
* lpszHex [I] String to convert to binary data
|
|
* lpOut [O] Destination for resulting binary data
|
|
*
|
|
* RETURNS
|
|
* Success: TRUE. lpOut contains the decoded binary data.
|
|
* Failure: FALSE, if lpszHex does not represent a binary string.
|
|
*
|
|
* NOTES
|
|
* - lpOut must be at least half the length of lpszHex in bytes.
|
|
* - Although the Mapi headers prototype this function as both
|
|
* Ascii and Unicode, there is only one (Ascii) implementation. This
|
|
* means that lpszHex is treated as an Ascii string (i.e. a single NUL
|
|
* character in the byte stream terminates the string).
|
|
*/
|
|
BOOL WINAPI FBinFromHex(LPWSTR lpszHex, LPBYTE lpOut)
|
|
{
|
|
LPSTR lpStr = (LPSTR)lpszHex;
|
|
|
|
TRACE("(%p,%p)\n", lpszHex, lpOut);
|
|
|
|
while (*lpStr)
|
|
{
|
|
if (lpStr[0] < '0' || lpStr[0] > 'f' || digitsToHex[lpStr[0] - '0'] == 0xff ||
|
|
lpStr[1] < '0' || lpStr[1] > 'f' || digitsToHex[lpStr[1] - '0'] == 0xff)
|
|
return FALSE;
|
|
|
|
*lpOut++ = (digitsToHex[lpStr[0] - '0'] << 4) | digitsToHex[lpStr[1] - '0'];
|
|
lpStr += 2;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* HexFromBin (MAPI32.45)
|
|
*
|
|
* Create a string from an array of binary data.
|
|
*
|
|
* PARAMS
|
|
* lpHex [I] Binary data to convert to string
|
|
* iCount [I] Length of lpHex in bytes
|
|
* lpszOut [O] Destination for resulting hex string
|
|
*
|
|
* RETURNS
|
|
* Nothing.
|
|
*
|
|
* NOTES
|
|
* - lpszOut must be at least 2 * iCount + 1 bytes characters long.
|
|
* - Although the Mapi headers prototype this function as both
|
|
* Ascii and Unicode, there is only one (Ascii) implementation. This
|
|
* means that the resulting string is not properly NUL terminated
|
|
* if the caller expects it to be a Unicode string.
|
|
*/
|
|
void WINAPI HexFromBin(LPBYTE lpHex, int iCount, LPWSTR lpszOut)
|
|
{
|
|
static const char hexDigits[] = { "0123456789ABCDEF" };
|
|
LPSTR lpStr = (LPSTR)lpszOut;
|
|
|
|
TRACE("(%p,%d,%p)\n", lpHex, iCount, lpszOut);
|
|
|
|
while (iCount-- > 0)
|
|
{
|
|
*lpStr++ = hexDigits[*lpHex >> 4];
|
|
*lpStr++ = hexDigits[*lpHex & 0xf];
|
|
lpHex++;
|
|
}
|
|
*lpStr = '\0';
|
|
}
|
|
|
|
/*************************************************************************
|
|
* SwapPlong@8 (MAPI32.47)
|
|
*
|
|
* Swap the bytes in a ULONG array.
|
|
*
|
|
* PARAMS
|
|
* lpData [O] Array to swap bytes in
|
|
* ulLen [I] Number of ULONG element to swap the bytes of
|
|
*
|
|
* RETURNS
|
|
* Nothing.
|
|
*/
|
|
VOID WINAPI SwapPlong(PULONG lpData, ULONG ulLen)
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < ulLen; i++)
|
|
lpData[i] = RtlUlongByteSwap(lpData[i]);
|
|
}
|
|
|
|
/*************************************************************************
|
|
* SwapPword@8 (MAPI32.48)
|
|
*
|
|
* Swap the bytes in a USHORT array.
|
|
*
|
|
* PARAMS
|
|
* lpData [O] Array to swap bytes in
|
|
* ulLen [I] Number of USHORT element to swap the bytes of
|
|
*
|
|
* RETURNS
|
|
* Nothing.
|
|
*/
|
|
VOID WINAPI SwapPword(PUSHORT lpData, ULONG ulLen)
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < ulLen; i++)
|
|
lpData[i] = RtlUshortByteSwap(lpData[i]);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* MNLS_lstrlenW@4 (MAPI32.62)
|
|
*
|
|
* Calculate the length of a Unicode string.
|
|
*
|
|
* PARAMS
|
|
* lpszStr [I] String to calculate the length of
|
|
*
|
|
* RETURNS
|
|
* The length of lpszStr in Unicode characters.
|
|
*/
|
|
ULONG WINAPI MNLS_lstrlenW(LPCWSTR lpszStr)
|
|
{
|
|
TRACE("(%s)\n", debugstr_w(lpszStr));
|
|
return strlenW(lpszStr);
|
|
}
|
|
|
|
/*************************************************************************
|
|
* MNLS_lstrcmpW@8 (MAPI32.63)
|
|
*
|
|
* Compare two Unicode strings.
|
|
*
|
|
* PARAMS
|
|
* lpszLeft [I] First string to compare
|
|
* lpszRight [I] Second string to compare
|
|
*
|
|
* RETURNS
|
|
* An integer less than, equal to or greater than 0, indicating that
|
|
* lpszLeft is less than, the same, or greater than lpszRight.
|
|
*/
|
|
INT WINAPI MNLS_lstrcmpW(LPCWSTR lpszLeft, LPCWSTR lpszRight)
|
|
{
|
|
TRACE("(%s,%s)\n", debugstr_w(lpszLeft), debugstr_w(lpszRight));
|
|
return strcmpW(lpszLeft, lpszRight);
|
|
}
|
|
|
|
/*************************************************************************
|
|
* MNLS_lstrcpyW@8 (MAPI32.64)
|
|
*
|
|
* Copy a Unicode string to another string.
|
|
*
|
|
* PARAMS
|
|
* lpszDest [O] Destination string
|
|
* lpszSrc [I] Source string
|
|
*
|
|
* RETURNS
|
|
* The length lpszDest in Unicode characters.
|
|
*/
|
|
ULONG WINAPI MNLS_lstrcpyW(LPWSTR lpszDest, LPCWSTR lpszSrc)
|
|
{
|
|
ULONG len;
|
|
|
|
TRACE("(%p,%s)\n", lpszDest, debugstr_w(lpszSrc));
|
|
len = (strlenW(lpszSrc) + 1) * sizeof(WCHAR);
|
|
memcpy(lpszDest, lpszSrc, len);
|
|
return len;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* MNLS_CompareStringW@12 (MAPI32.65)
|
|
*
|
|
* Compare two Unicode strings.
|
|
*
|
|
* PARAMS
|
|
* dwCp [I] Code page for the comparison
|
|
* lpszLeft [I] First string to compare
|
|
* lpszRight [I] Second string to compare
|
|
*
|
|
* RETURNS
|
|
* CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN, indicating that
|
|
* lpszLeft is less than, the same, or greater than lpszRight.
|
|
*/
|
|
INT WINAPI MNLS_CompareStringW(DWORD dwCp, LPCWSTR lpszLeft, LPCWSTR lpszRight)
|
|
{
|
|
INT ret;
|
|
|
|
TRACE("0x%08x,%s,%s\n", dwCp, debugstr_w(lpszLeft), debugstr_w(lpszRight));
|
|
ret = MNLS_lstrcmpW(lpszLeft, lpszRight);
|
|
return ret < 0 ? CSTR_LESS_THAN : ret ? CSTR_GREATER_THAN : CSTR_EQUAL;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* FEqualNames@8 (MAPI32.72)
|
|
*
|
|
* Compare two Mapi names.
|
|
*
|
|
* PARAMS
|
|
* lpName1 [I] First name to compare to lpName2
|
|
* lpName2 [I] Second name to compare to lpName1
|
|
*
|
|
* RETURNS
|
|
* TRUE, if the names are the same,
|
|
* FALSE, Otherwise.
|
|
*/
|
|
BOOL WINAPI FEqualNames(LPMAPINAMEID lpName1, LPMAPINAMEID lpName2)
|
|
{
|
|
TRACE("(%p,%p)\n", lpName1, lpName2);
|
|
|
|
if (!lpName1 || !lpName2 ||
|
|
!IsEqualGUID(lpName1->lpguid, lpName2->lpguid) ||
|
|
lpName1->ulKind != lpName2->ulKind)
|
|
return FALSE;
|
|
|
|
if (lpName1->ulKind == MNID_STRING)
|
|
return !strcmpW(lpName1->Kind.lpwstrName, lpName2->Kind.lpwstrName);
|
|
|
|
return lpName1->Kind.lID == lpName2->Kind.lID ? TRUE : FALSE;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* IsBadBoundedStringPtr@8 (MAPI32.71)
|
|
*
|
|
* Determine if a string pointer is valid.
|
|
*
|
|
* PARAMS
|
|
* lpszStr [I] String to check
|
|
* ulLen [I] Maximum length of lpszStr
|
|
*
|
|
* RETURNS
|
|
* TRUE, if lpszStr is invalid or longer than ulLen,
|
|
* FALSE, otherwise.
|
|
*/
|
|
BOOL WINAPI IsBadBoundedStringPtr(LPCSTR lpszStr, ULONG ulLen)
|
|
{
|
|
if (!lpszStr || IsBadStringPtrA(lpszStr, -1) || strlen(lpszStr) >= ulLen)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* FtAddFt@16 (MAPI32.121)
|
|
*
|
|
* Add two FILETIME's together.
|
|
*
|
|
* PARAMS
|
|
* ftLeft [I] FILETIME to add to ftRight
|
|
* ftRight [I] FILETIME to add to ftLeft
|
|
*
|
|
* RETURNS
|
|
* The sum of ftLeft and ftRight
|
|
*/
|
|
LONGLONG WINAPI MAPI32_FtAddFt(FILETIME ftLeft, FILETIME ftRight)
|
|
{
|
|
LONGLONG *pl = (LONGLONG*)&ftLeft, *pr = (LONGLONG*)&ftRight;
|
|
|
|
return *pl + *pr;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* FtSubFt@16 (MAPI32.123)
|
|
*
|
|
* Subtract two FILETIME's together.
|
|
*
|
|
* PARAMS
|
|
* ftLeft [I] Initial FILETIME
|
|
* ftRight [I] FILETIME to subtract from ftLeft
|
|
*
|
|
* RETURNS
|
|
* The remainder after ftRight is subtracted from ftLeft.
|
|
*/
|
|
LONGLONG WINAPI MAPI32_FtSubFt(FILETIME ftLeft, FILETIME ftRight)
|
|
{
|
|
LONGLONG *pl = (LONGLONG*)&ftLeft, *pr = (LONGLONG*)&ftRight;
|
|
|
|
return *pr - *pl;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* FtMulDw@12 (MAPI32.124)
|
|
*
|
|
* Multiply a FILETIME by a DWORD.
|
|
*
|
|
* PARAMS
|
|
* dwLeft [I] DWORD to multiply with ftRight
|
|
* ftRight [I] FILETIME to multiply with dwLeft
|
|
*
|
|
* RETURNS
|
|
* The product of dwLeft and ftRight
|
|
*/
|
|
LONGLONG WINAPI MAPI32_FtMulDw(DWORD dwLeft, FILETIME ftRight)
|
|
{
|
|
LONGLONG *pr = (LONGLONG*)&ftRight;
|
|
|
|
return (LONGLONG)dwLeft * (*pr);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* FtMulDwDw@8 (MAPI32.125)
|
|
*
|
|
* Multiply two DWORD, giving the result as a FILETIME.
|
|
*
|
|
* PARAMS
|
|
* dwLeft [I] DWORD to multiply with dwRight
|
|
* dwRight [I] DWORD to multiply with dwLeft
|
|
*
|
|
* RETURNS
|
|
* The product of ftMultiplier and ftMultiplicand as a FILETIME.
|
|
*/
|
|
LONGLONG WINAPI MAPI32_FtMulDwDw(DWORD dwLeft, DWORD dwRight)
|
|
{
|
|
return (LONGLONG)dwLeft * (LONGLONG)dwRight;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* FtNegFt@8 (MAPI32.126)
|
|
*
|
|
* Negate a FILETIME.
|
|
*
|
|
* PARAMS
|
|
* ft [I] FILETIME to negate
|
|
*
|
|
* RETURNS
|
|
* The negation of ft.
|
|
*/
|
|
LONGLONG WINAPI MAPI32_FtNegFt(FILETIME ft)
|
|
{
|
|
LONGLONG *p = (LONGLONG*)&ft;
|
|
|
|
return - *p;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* UlAddRef@4 (MAPI32.128)
|
|
*
|
|
* Add a reference to an object.
|
|
*
|
|
* PARAMS
|
|
* lpUnk [I] Object to add a reference to.
|
|
*
|
|
* RETURNS
|
|
* The new reference count of the object, or 0 if lpUnk is NULL.
|
|
*
|
|
* NOTES
|
|
* See IUnknown_AddRef.
|
|
*/
|
|
ULONG WINAPI UlAddRef(void *lpUnk)
|
|
{
|
|
TRACE("(%p)\n", lpUnk);
|
|
|
|
if (!lpUnk)
|
|
return 0UL;
|
|
return IUnknown_AddRef((LPUNKNOWN)lpUnk);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* UlRelease@4 (MAPI32.129)
|
|
*
|
|
* Remove a reference from an object.
|
|
*
|
|
* PARAMS
|
|
* lpUnk [I] Object to remove reference from.
|
|
*
|
|
* RETURNS
|
|
* The new reference count of the object, or 0 if lpUnk is NULL. If lpUnk is
|
|
* non-NULL and this function returns 0, the object pointed to by lpUnk has
|
|
* been released.
|
|
*
|
|
* NOTES
|
|
* See IUnknown_Release.
|
|
*/
|
|
ULONG WINAPI UlRelease(void *lpUnk)
|
|
{
|
|
TRACE("(%p)\n", lpUnk);
|
|
|
|
if (!lpUnk)
|
|
return 0UL;
|
|
return IUnknown_Release((LPUNKNOWN)lpUnk);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* UFromSz@4 (MAPI32.133)
|
|
*
|
|
* Read an integer from a string
|
|
*
|
|
* PARAMS
|
|
* lpszStr [I] String to read the integer from.
|
|
*
|
|
* RETURNS
|
|
* Success: The integer read from lpszStr.
|
|
* Failure: 0, if the first character in lpszStr is not 0-9.
|
|
*
|
|
* NOTES
|
|
* This function does not accept whitespace and stops at the first non-digit
|
|
* character.
|
|
*/
|
|
UINT WINAPI UFromSz(LPCSTR lpszStr)
|
|
{
|
|
ULONG ulRet = 0;
|
|
|
|
TRACE("(%s)\n", debugstr_a(lpszStr));
|
|
|
|
if (lpszStr)
|
|
{
|
|
while (*lpszStr >= '0' && *lpszStr <= '9')
|
|
{
|
|
ulRet = ulRet * 10 + (*lpszStr - '0');
|
|
lpszStr++;
|
|
}
|
|
}
|
|
return ulRet;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* OpenStreamOnFile@24 (MAPI32.147)
|
|
*
|
|
* Create a stream on a file.
|
|
*
|
|
* PARAMS
|
|
* lpAlloc [I] Memory allocation function
|
|
* lpFree [I] Memory free function
|
|
* ulFlags [I] Flags controlling the opening process
|
|
* lpszPath [I] Path of file to create stream on
|
|
* lpszPrefix [I] Prefix of the temporary file name (if ulFlags includes SOF_UNIQUEFILENAME)
|
|
* lppStream [O] Destination for created stream
|
|
*
|
|
* RETURNS
|
|
* Success: S_OK. lppStream contains the new stream object
|
|
* Failure: E_INVALIDARG if any parameter is invalid, or an HRESULT error code
|
|
* describing the error.
|
|
*/
|
|
HRESULT WINAPI OpenStreamOnFile(LPALLOCATEBUFFER lpAlloc, LPFREEBUFFER lpFree,
|
|
ULONG ulFlags, LPWSTR lpszPath, LPWSTR lpszPrefix,
|
|
LPSTREAM *lppStream)
|
|
{
|
|
WCHAR szBuff[MAX_PATH];
|
|
DWORD dwMode = STGM_READWRITE, dwAttributes = 0;
|
|
HRESULT hRet;
|
|
|
|
TRACE("(%p,%p,0x%08x,%s,%s,%p)\n", lpAlloc, lpFree, ulFlags,
|
|
debugstr_a((LPSTR)lpszPath), debugstr_a((LPSTR)lpszPrefix), lppStream);
|
|
|
|
if (mapiFunctions.OpenStreamOnFile)
|
|
return mapiFunctions.OpenStreamOnFile(lpAlloc, lpFree, ulFlags, lpszPath, lpszPrefix, lppStream);
|
|
|
|
if (lppStream)
|
|
*lppStream = NULL;
|
|
|
|
if (ulFlags & SOF_UNIQUEFILENAME)
|
|
{
|
|
FIXME("Should generate a temporary name\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (!lpszPath || !lppStream)
|
|
return E_INVALIDARG;
|
|
|
|
/* FIXME: Should probably munge mode and attributes, and should handle
|
|
* Unicode arguments (I assume MAPI_UNICODE is set in ulFlags if
|
|
* we are being passed Unicode strings; MSDN doesn't say).
|
|
* This implementation is just enough for Outlook97 to start.
|
|
*/
|
|
MultiByteToWideChar(CP_ACP, 0, (LPSTR)lpszPath, -1, szBuff, MAX_PATH);
|
|
hRet = SHCreateStreamOnFileEx(szBuff, dwMode, dwAttributes, TRUE,
|
|
NULL, lppStream);
|
|
return hRet;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* UlFromSzHex@4 (MAPI32.155)
|
|
*
|
|
* Read an integer from a hexadecimal string.
|
|
*
|
|
* PARAMS
|
|
* lpSzHex [I] String containing the hexadecimal number to read
|
|
*
|
|
* RETURNS
|
|
* Success: The number represented by lpszHex.
|
|
* Failure: 0, if lpszHex does not contain a hex string.
|
|
*
|
|
* NOTES
|
|
* This function does not accept whitespace and stops at the first non-hex
|
|
* character.
|
|
*/
|
|
ULONG WINAPI UlFromSzHex(LPCWSTR lpszHex)
|
|
{
|
|
LPCSTR lpStr = (LPCSTR)lpszHex;
|
|
ULONG ulRet = 0;
|
|
|
|
TRACE("(%s)\n", debugstr_a(lpStr));
|
|
|
|
while (*lpStr)
|
|
{
|
|
if (lpStr[0] < '0' || lpStr[0] > 'f' || digitsToHex[lpStr[0] - '0'] == 0xff ||
|
|
lpStr[1] < '0' || lpStr[1] > 'f' || digitsToHex[lpStr[1] - '0'] == 0xff)
|
|
break;
|
|
|
|
ulRet = ulRet * 16 + ((digitsToHex[lpStr[0] - '0'] << 4) | digitsToHex[lpStr[1] - '0']);
|
|
lpStr += 2;
|
|
}
|
|
return ulRet;
|
|
}
|
|
|
|
/************************************************************************
|
|
* FBadEntryList@4 (MAPI32.190)
|
|
*
|
|
* Determine is an entry list is invalid.
|
|
*
|
|
* PARAMS
|
|
* lpEntryList [I] List to check
|
|
*
|
|
* RETURNS
|
|
* TRUE, if lpEntryList is invalid,
|
|
* FALSE, otherwise.
|
|
*/
|
|
BOOL WINAPI FBadEntryList(LPENTRYLIST lpEntryList)
|
|
{
|
|
ULONG i;
|
|
|
|
if (IsBadReadPtr(lpEntryList, sizeof(*lpEntryList)) ||
|
|
IsBadReadPtr(lpEntryList->lpbin,
|
|
lpEntryList->cValues * sizeof(*lpEntryList->lpbin)))
|
|
return TRUE;
|
|
|
|
for (i = 0; i < lpEntryList->cValues; i++)
|
|
if(IsBadReadPtr(lpEntryList->lpbin[i].lpb, lpEntryList->lpbin[i].cb))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* CbOfEncoded@4 (MAPI32.207)
|
|
*
|
|
* Return the length of an encoded string.
|
|
*
|
|
* PARAMS
|
|
* lpSzEnc [I] Encoded string to get the length of.
|
|
*
|
|
* RETURNS
|
|
* The length of the encoded string in bytes.
|
|
*/
|
|
ULONG WINAPI CbOfEncoded(LPCSTR lpszEnc)
|
|
{
|
|
ULONG ulRet = 0;
|
|
|
|
TRACE("(%s)\n", debugstr_a(lpszEnc));
|
|
|
|
if (lpszEnc)
|
|
ulRet = (((strlen(lpszEnc) | 3) >> 2) + 1) * 3;
|
|
return ulRet;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* cmc_query_configuration (MAPI32.235)
|
|
*
|
|
* Retrieves the configuration information for the installed CMC
|
|
*
|
|
* PARAMS
|
|
* session [I] MAPI session handle
|
|
* item [I] Enumerated variable that identifies which
|
|
* configuration information is being requested
|
|
* reference [O] Buffer where configuration information is written
|
|
* config_extensions[I/O] Path of file to create stream on
|
|
*
|
|
* RETURNS
|
|
* A CMD define
|
|
*/
|
|
CMC_return_code WINAPI cmc_query_configuration(
|
|
CMC_session_id session,
|
|
CMC_enum item,
|
|
CMC_buffer reference,
|
|
CMC_extension *config_extensions)
|
|
{
|
|
FIXME("stub\n");
|
|
return CMC_E_NOT_SUPPORTED;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* FGetComponentPath (MAPI32.254)
|
|
* FGetComponentPath@20 (MAPI32.255)
|
|
*
|
|
* Return the installed component path, usually to the private mapi32.dll.
|
|
*
|
|
* PARAMS
|
|
* component [I] Component ID
|
|
* qualifier [I] Application LCID
|
|
* dll_path [O] returned component path
|
|
* dll_path_length [I] component path length
|
|
* install [I] install mode
|
|
*
|
|
* RETURNS
|
|
* Success: TRUE.
|
|
* Failure: FALSE.
|
|
*
|
|
* NOTES
|
|
* Previously documented in Q229700 "How to locate the correct path
|
|
* to the Mapisvc.inf file in Microsoft Outlook".
|
|
*/
|
|
BOOL WINAPI FGetComponentPath(LPCSTR component, LPCSTR qualifier, LPSTR dll_path,
|
|
DWORD dll_path_length, BOOL install)
|
|
{
|
|
BOOL ret = FALSE;
|
|
HMODULE hmsi;
|
|
|
|
TRACE("%s %s %p %u %d\n", component, qualifier, dll_path, dll_path_length, install);
|
|
|
|
if (mapiFunctions.FGetComponentPath)
|
|
return mapiFunctions.FGetComponentPath(component, qualifier, dll_path, dll_path_length, install);
|
|
|
|
dll_path[0] = 0;
|
|
|
|
hmsi = LoadLibraryA("msi.dll");
|
|
if (hmsi)
|
|
{
|
|
UINT (WINAPI *pMsiProvideQualifiedComponentA)(LPCSTR, LPCSTR, DWORD, LPSTR, LPDWORD);
|
|
|
|
pMsiProvideQualifiedComponentA = (void *)GetProcAddress(hmsi, "MsiProvideQualifiedComponentA");
|
|
if (pMsiProvideQualifiedComponentA)
|
|
{
|
|
static const char * const fmt[] = { "%d\\NT", "%d\\95", "%d" };
|
|
char lcid_ver[20];
|
|
UINT i;
|
|
|
|
for (i = 0; i < sizeof(fmt)/sizeof(fmt[0]); i++)
|
|
{
|
|
/* FIXME: what's the correct behaviour here? */
|
|
if (!qualifier || qualifier == lcid_ver)
|
|
{
|
|
sprintf(lcid_ver, fmt[i], GetUserDefaultUILanguage());
|
|
qualifier = lcid_ver;
|
|
}
|
|
|
|
if (pMsiProvideQualifiedComponentA(component, qualifier,
|
|
install ? INSTALLMODE_DEFAULT : INSTALLMODE_EXISTING,
|
|
dll_path, &dll_path_length) == ERROR_SUCCESS)
|
|
{
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (qualifier != lcid_ver) break;
|
|
}
|
|
}
|
|
FreeLibrary(hmsi);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* HrQueryAllRows (MAPI32.75)
|
|
*/
|
|
HRESULT WINAPI HrQueryAllRows(LPMAPITABLE lpTable, LPSPropTagArray lpPropTags,
|
|
LPSRestriction lpRestriction, LPSSortOrderSet lpSortOrderSet,
|
|
LONG crowsMax, LPSRowSet *lppRows)
|
|
{
|
|
if (mapiFunctions.HrQueryAllRows)
|
|
return mapiFunctions.HrQueryAllRows(lpTable, lpPropTags, lpRestriction, lpSortOrderSet, crowsMax, lppRows);
|
|
|
|
FIXME("(%p, %p, %p, %p, %d, %p): stub\n", lpTable, lpPropTags, lpRestriction, lpSortOrderSet, crowsMax, lppRows);
|
|
*lppRows = NULL;
|
|
return MAPI_E_CALL_FAILED;
|
|
}
|
|
|
|
static HMODULE mapi_provider;
|
|
static HMODULE mapi_ex_provider;
|
|
|
|
/**************************************************************************
|
|
* load_mapi_provider
|
|
*
|
|
* Attempts to load a MAPI provider from the specified registry key.
|
|
*
|
|
* Returns a handle to the loaded module in `mapi_provider' if successful.
|
|
*/
|
|
static void load_mapi_provider(HKEY hkeyMail, LPCWSTR valueName, HMODULE *mapi_provider)
|
|
{
|
|
static const WCHAR mapi32_dll[] = {'m','a','p','i','3','2','.','d','l','l',0 };
|
|
|
|
DWORD dwType, dwLen = 0;
|
|
LPWSTR dllPath;
|
|
|
|
/* Check if we have a value set for DLLPath */
|
|
if ((RegQueryValueExW(hkeyMail, valueName, NULL, &dwType, NULL, &dwLen) == ERROR_SUCCESS) &&
|
|
((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)) && (dwLen > 0))
|
|
{
|
|
dllPath = HeapAlloc(GetProcessHeap(), 0, dwLen);
|
|
|
|
if (dllPath)
|
|
{
|
|
RegQueryValueExW(hkeyMail, valueName, NULL, NULL, (LPBYTE)dllPath, &dwLen);
|
|
|
|
/* Check that this value doesn't refer to mapi32.dll (eg, as Outlook does) */
|
|
if (lstrcmpiW(dllPath, mapi32_dll) != 0)
|
|
{
|
|
if (dwType == REG_EXPAND_SZ)
|
|
{
|
|
DWORD dwExpandLen;
|
|
LPWSTR dllPathExpanded;
|
|
|
|
/* Expand the path if necessary */
|
|
dwExpandLen = ExpandEnvironmentStringsW(dllPath, NULL, 0);
|
|
dllPathExpanded = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * dwExpandLen + 1);
|
|
|
|
if (dllPathExpanded)
|
|
{
|
|
ExpandEnvironmentStringsW(dllPath, dllPathExpanded, dwExpandLen + 1);
|
|
|
|
HeapFree(GetProcessHeap(), 0, dllPath);
|
|
dllPath = dllPathExpanded;
|
|
}
|
|
}
|
|
|
|
/* Load the DLL */
|
|
TRACE("loading %s\n", debugstr_w(dllPath));
|
|
*mapi_provider = LoadLibraryW(dllPath);
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, dllPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************
|
|
* load_mapi_providers
|
|
*
|
|
* Scans the registry for MAPI providers and attempts to load a Simple and
|
|
* Extended MAPI library.
|
|
*
|
|
* Returns TRUE if at least one library loaded, FALSE otherwise.
|
|
*/
|
|
void load_mapi_providers(void)
|
|
{
|
|
static const WCHAR regkey_mail[] = {
|
|
'S','o','f','t','w','a','r','e','\\','C','l','i','e','n','t','s','\\',
|
|
'M','a','i','l',0 };
|
|
|
|
static const WCHAR regkey_dllpath[] = {'D','L','L','P','a','t','h',0 };
|
|
static const WCHAR regkey_dllpath_ex[] = {'D','L','L','P','a','t','h','E','x',0 };
|
|
static const WCHAR regkey_backslash[] = { '\\', 0 };
|
|
|
|
HKEY hkeyMail;
|
|
DWORD dwType, dwLen = 0;
|
|
LPWSTR appName = NULL, appKey = NULL;
|
|
|
|
TRACE("()\n");
|
|
|
|
/* Open the Mail key */
|
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, regkey_mail, 0, KEY_READ, &hkeyMail) != ERROR_SUCCESS)
|
|
return;
|
|
|
|
/* Check if we have a default value set, and the length of it */
|
|
if ((RegQueryValueExW(hkeyMail, NULL, NULL, &dwType, NULL, &dwLen) != ERROR_SUCCESS) ||
|
|
!((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)) || (dwLen == 0))
|
|
goto cleanUp;
|
|
|
|
appName = HeapAlloc(GetProcessHeap(), 0, dwLen);
|
|
|
|
if (!appName)
|
|
goto cleanUp;
|
|
|
|
/* Get the value, and get the path to the app key */
|
|
RegQueryValueExW(hkeyMail, NULL, NULL, NULL, (LPBYTE)appName, &dwLen);
|
|
|
|
TRACE("appName: %s\n", debugstr_w(appName));
|
|
|
|
appKey = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (lstrlenW(regkey_mail) +
|
|
lstrlenW(regkey_backslash) + lstrlenW(appName) + 1));
|
|
|
|
if (!appKey)
|
|
goto cleanUp;
|
|
|
|
lstrcpyW(appKey, regkey_mail);
|
|
lstrcatW(appKey, regkey_backslash);
|
|
lstrcatW(appKey, appName);
|
|
|
|
RegCloseKey(hkeyMail);
|
|
|
|
TRACE("appKey: %s\n", debugstr_w(appKey));
|
|
|
|
/* Open the app's key */
|
|
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, appKey, 0, KEY_READ, &hkeyMail) != ERROR_SUCCESS)
|
|
goto cleanUp;
|
|
|
|
/* Try to load the providers */
|
|
load_mapi_provider(hkeyMail, regkey_dllpath, &mapi_provider);
|
|
load_mapi_provider(hkeyMail, regkey_dllpath_ex, &mapi_ex_provider);
|
|
|
|
/* Now try to load our function pointers */
|
|
ZeroMemory(&mapiFunctions, sizeof(mapiFunctions));
|
|
|
|
/* Simple MAPI functions */
|
|
if (mapi_provider)
|
|
{
|
|
mapiFunctions.MAPIAddress = (void*) GetProcAddress(mapi_provider, "MAPIAddress");
|
|
mapiFunctions.MAPIDeleteMail = (void*) GetProcAddress(mapi_provider, "MAPIDeleteMail");
|
|
mapiFunctions.MAPIDetails = (void*) GetProcAddress(mapi_provider, "MAPIDetails");
|
|
mapiFunctions.MAPIFindNext = (void*) GetProcAddress(mapi_provider, "MAPIFindNext");
|
|
mapiFunctions.MAPILogoff = (void*) GetProcAddress(mapi_provider, "MAPILogoff");
|
|
mapiFunctions.MAPILogon = (void*) GetProcAddress(mapi_provider, "MAPILogon");
|
|
mapiFunctions.MAPIReadMail = (void*) GetProcAddress(mapi_provider, "MAPIReadMail");
|
|
mapiFunctions.MAPIResolveName = (void*) GetProcAddress(mapi_provider, "MAPIResolveName");
|
|
mapiFunctions.MAPISaveMail = (void*) GetProcAddress(mapi_provider, "MAPISaveMail");
|
|
mapiFunctions.MAPISendDocuments = (void*) GetProcAddress(mapi_provider, "MAPISendDocuments");
|
|
mapiFunctions.MAPISendMail = (void*) GetProcAddress(mapi_provider, "MAPISendMail");
|
|
mapiFunctions.MAPISendMailW = (void*) GetProcAddress(mapi_provider, "MAPISendMailW");
|
|
}
|
|
|
|
/* Extended MAPI functions */
|
|
if (mapi_ex_provider)
|
|
{
|
|
mapiFunctions.MAPIInitialize = (void*) GetProcAddress(mapi_ex_provider, "MAPIInitialize");
|
|
mapiFunctions.MAPILogonEx = (void*) GetProcAddress(mapi_ex_provider, "MAPILogonEx");
|
|
mapiFunctions.MAPIUninitialize = (void*) GetProcAddress(mapi_ex_provider, "MAPIUninitialize");
|
|
|
|
mapiFunctions.DeinitMapiUtil = (void*) GetProcAddress(mapi_ex_provider, "DeinitMapiUtil@0");
|
|
mapiFunctions.DllCanUnloadNow = (void*) GetProcAddress(mapi_ex_provider, "DllCanUnloadNow");
|
|
mapiFunctions.DllGetClassObject = (void*) GetProcAddress(mapi_ex_provider, "DllGetClassObject");
|
|
mapiFunctions.FGetComponentPath = (void*) GetProcAddress(mapi_ex_provider, "FGetComponentPath");
|
|
mapiFunctions.HrThisThreadAdviseSink = (void*) GetProcAddress(mapi_ex_provider, "HrThisThreadAdviseSink@8");
|
|
mapiFunctions.HrQueryAllRows = (void*) GetProcAddress(mapi_ex_provider, "HrQueryAllRows@24");
|
|
mapiFunctions.MAPIAdminProfiles = (void*) GetProcAddress(mapi_ex_provider, "MAPIAdminProfiles");
|
|
mapiFunctions.MAPIAllocateBuffer = (void*) GetProcAddress(mapi_ex_provider, "MAPIAllocateBuffer");
|
|
mapiFunctions.MAPIAllocateMore = (void*) GetProcAddress(mapi_ex_provider, "MAPIAllocateMore");
|
|
mapiFunctions.MAPIFreeBuffer = (void*) GetProcAddress(mapi_ex_provider, "MAPIFreeBuffer");
|
|
mapiFunctions.MAPIGetDefaultMalloc = (void*) GetProcAddress(mapi_ex_provider, "MAPIGetDefaultMalloc@0");
|
|
mapiFunctions.MAPIOpenLocalFormContainer = (void *) GetProcAddress(mapi_ex_provider, "MAPIOpenLocalFormContainer");
|
|
mapiFunctions.OpenStreamOnFile = (void*) GetProcAddress(mapi_ex_provider, "OpenStreamOnFile@24");
|
|
mapiFunctions.ScInitMapiUtil = (void*) GetProcAddress(mapi_ex_provider, "ScInitMapiUtil@4");
|
|
}
|
|
|
|
cleanUp:
|
|
RegCloseKey(hkeyMail);
|
|
HeapFree(GetProcessHeap(), 0, appKey);
|
|
HeapFree(GetProcessHeap(), 0, appName);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* unload_mapi_providers
|
|
*
|
|
* Unloads any loaded MAPI libraries.
|
|
*/
|
|
void unload_mapi_providers(void)
|
|
{
|
|
TRACE("()\n");
|
|
|
|
FreeLibrary(mapi_provider);
|
|
FreeLibrary(mapi_ex_provider);
|
|
}
|