480 lines
16 KiB
C++
480 lines
16 KiB
C++
//--------------------------------------------------------------------
|
|
// PingLib - implementation
|
|
// Copyright (C) Microsoft Corporation, 1999
|
|
//
|
|
// Created by: Louis Thomas (louisth), 10-8-99
|
|
//
|
|
// Various ways of pinging a server
|
|
//
|
|
|
|
#include "pch.h" // precompiled headers
|
|
|
|
#include <ipexport.h>
|
|
#include <icmpapi.h>
|
|
#include <DcInfo.h>
|
|
#include "NtpBase.h"
|
|
#include "EndianSwap.inl"
|
|
|
|
|
|
//####################################################################
|
|
// OLD CODE
|
|
#if 0
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT LookupServer(IN WCHAR * wszServerName, OUT sockaddr * psaOut, IN int nAddrSize) {
|
|
HRESULT hr;
|
|
DWORD dwDataLen;
|
|
SOCKET_ADDRESS * psaFound;
|
|
|
|
// pointers that must be cleaned up
|
|
HANDLE hSearch=INVALID_HANDLE_VALUE;
|
|
WSAQUERYSETW * pqsResult=NULL;
|
|
|
|
DebugWPrintf1(L"Looking up server \"%s\":\n", wszServerName);
|
|
|
|
// initialize the search
|
|
// const static GUID guidHostAddressByName = SVCID_INET_HOSTADDRBYNAME;
|
|
AFPROTOCOLS apInetUdp={AF_INET, IPPROTO_UDP};
|
|
GUID guidNtp=SVCID_NTP_UDP;
|
|
WSAQUERYSETW qsSearch;
|
|
ZeroMemory(&qsSearch, sizeof(qsSearch));
|
|
qsSearch.dwSize=sizeof(qsSearch);
|
|
qsSearch.lpszServiceInstanceName=wszServerName;
|
|
qsSearch.lpServiceClassId=&guidNtp;
|
|
qsSearch.dwNameSpace=NS_ALL;
|
|
qsSearch.dwNumberOfProtocols=1;
|
|
qsSearch.lpafpProtocols=&apInetUdp;
|
|
|
|
if (SOCKET_ERROR==WSALookupServiceBegin(&qsSearch, LUP_RETURN_ADDR/*flags*/, &hSearch)) {
|
|
_JumpLastError(hr, error, "WSALookupServiceBegin");
|
|
}
|
|
|
|
// get the buffer size for the first value
|
|
dwDataLen=1;
|
|
_Verify(SOCKET_ERROR==WSALookupServiceNext(hSearch, LUP_RETURN_ADDR/*flags*/, &dwDataLen, &qsSearch), hr, error);
|
|
if (WSAEFAULT!=GetLastError()) {
|
|
_JumpLastError(hr, error, "WSALookupServiceNext");
|
|
}
|
|
|
|
// allocate the buffer
|
|
pqsResult=reinterpret_cast<WSAQUERYSETW *>(LocalAlloc(LMEM_FIXED, dwDataLen));
|
|
_JumpIfOutOfMemory(hr, error, pqsResult);
|
|
|
|
// retrieve the first value
|
|
if (SOCKET_ERROR==WSALookupServiceNext(hSearch, LUP_RETURN_ADDR/*flags*/, &dwDataLen, pqsResult)) {
|
|
_JumpLastError(hr, error, "WSALookupServiceNext");
|
|
}
|
|
_Verify(pqsResult->dwNumberOfCsAddrs>0, hr, error);
|
|
if (pqsResult->dwNumberOfCsAddrs>1) {
|
|
DebugWPrintf1(L"WSALookupServiceNextW returned %d addresses. Using first one.\n", pqsResult->dwNumberOfCsAddrs);
|
|
}
|
|
psaFound=&(pqsResult->lpcsaBuffer[0].RemoteAddr);
|
|
_Verify(nAddrSize==psaFound->iSockaddrLength, hr, error);
|
|
|
|
*psaOut=*(psaFound->lpSockaddr);
|
|
DumpSockaddr(psaOut, nAddrSize);
|
|
|
|
hr=S_OK;
|
|
|
|
error:
|
|
if (NULL!=pqsResult) {
|
|
LocalFree(pqsResult);
|
|
}
|
|
if (INVALID_HANDLE_VALUE!=hSearch) {
|
|
if (SOCKET_ERROR==WSALookupServiceEnd(hSearch)) {
|
|
_IgnoreLastError("WSALookupServiceEnd");
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
MODULEPRIVATE HRESULT GetSample(WCHAR * wszServerName, TpcGetSamplesArgs * ptgsa) {
|
|
HRESULT hr;
|
|
NtpPacket npPacket;
|
|
NtTimeEpoch teDestinationTimestamp;
|
|
unsigned int nIpAddrs;
|
|
|
|
// must be cleaned up
|
|
in_addr * rgiaLocalIpAddrs=NULL;
|
|
in_addr * rgiaRemoteIpAddrs=NULL;
|
|
|
|
hr=MyGetIpAddrs(wszServerName, &rgiaLocalIpAddrs, &rgiaRemoteIpAddrs, &nIpAddrs, NULL);
|
|
_JumpIfError(hr, error, "MyGetIpAddrs");
|
|
_Verify(0!=nIpAddrs, hr, error);
|
|
|
|
hr=MyNtpPing(&(rgiaRemoteIpAddrs[0]), 500, &npPacket, &teDestinationTimestamp);
|
|
_JumpIfError(hr, error, "MyNtpPing");
|
|
|
|
{
|
|
NtTimeEpoch teOriginateTimestamp=NtTimeEpochFromNtpTimeEpoch(npPacket.teOriginateTimestamp);
|
|
NtTimeEpoch teReceiveTimestamp=NtTimeEpochFromNtpTimeEpoch(npPacket.teReceiveTimestamp);
|
|
NtTimeEpoch teTransmitTimestamp=NtTimeEpochFromNtpTimeEpoch(npPacket.teTransmitTimestamp);
|
|
NtTimeOffset toRoundtripDelay=
|
|
(teDestinationTimestamp-teOriginateTimestamp)
|
|
- (teTransmitTimestamp-teReceiveTimestamp);
|
|
NtTimeOffset toLocalClockOffset=
|
|
(teReceiveTimestamp-teOriginateTimestamp)
|
|
+ (teTransmitTimestamp-teDestinationTimestamp);
|
|
toLocalClockOffset/=2;
|
|
NtTimePeriod tpClockTickSize;
|
|
g_npstate.tpsc.pfnGetTimeSysInfo(TSI_ClockTickSize, &tpClockTickSize.qw);
|
|
|
|
|
|
TimeSample * pts=(TimeSample *)ptgsa->pbSampleBuf;
|
|
pts->dwSize=sizeof(TimeSample);
|
|
pts->dwRefid=npPacket.refid.value;
|
|
pts->toOffset=toLocalClockOffset.qw;
|
|
pts->toDelay=(toRoundtripDelay
|
|
+NtTimeOffsetFromNtpTimeOffset(npPacket.toRootDelay)
|
|
).qw;
|
|
pts->tpDispersion=(tpClockTickSize
|
|
+NtpConst::timesMaxSkewRate(abs(teDestinationTimestamp-teOriginateTimestamp))
|
|
+NtTimePeriodFromNtpTimePeriod(npPacket.tpRootDispersion)
|
|
).qw;
|
|
g_npstate.tpsc.pfnGetTimeSysInfo(TSI_TickCount, &(pts->nSysTickCount));
|
|
g_npstate.tpsc.pfnGetTimeSysInfo(TSI_PhaseOffset, &(pts->nSysPhaseOffset));
|
|
pts->nStratum=npPacket.nStratum;
|
|
pts->nLeapFlags=npPacket.nLeapIndicator;
|
|
|
|
ptgsa->dwSamplesAvailable=1;
|
|
ptgsa->dwSamplesReturned=1;
|
|
}
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=rgiaLocalIpAddrs) {
|
|
LocalFree(rgiaLocalIpAddrs);
|
|
}
|
|
if (NULL!=rgiaRemoteIpAddrs) {
|
|
LocalFree(rgiaRemoteIpAddrs);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
//####################################################################
|
|
// module public
|
|
|
|
//--------------------------------------------------------------------
|
|
HRESULT MyIcmpPing(in_addr * piaTarget, DWORD dwTimeout, DWORD * pdwResponseTime) {
|
|
HRESULT hr;
|
|
IPAddr ipaddrDest=piaTarget->S_un.S_addr;
|
|
BYTE rgbData[8]={'a','b','c','d','e','f','g','h'};
|
|
BYTE rgbResponse[1024];
|
|
DWORD dwDataSize;
|
|
|
|
// must be cleaned up
|
|
HANDLE hIcmp=NULL;
|
|
|
|
// open a handle for icmp access
|
|
hIcmp=IcmpCreateFile();
|
|
if (NULL==hIcmp) {
|
|
_JumpLastError(hr, error, "IcmpCreateFile");
|
|
}
|
|
|
|
// ping
|
|
ZeroMemory(rgbResponse, sizeof(rgbResponse));
|
|
dwDataSize=IcmpSendEcho(hIcmp, ipaddrDest, rgbData, 8, NULL, rgbResponse, sizeof(rgbResponse), dwTimeout);
|
|
if (0==dwDataSize) {
|
|
_JumpLastError(hr, error, "IcmpSendEcho");
|
|
}
|
|
|
|
*pdwResponseTime=((icmp_echo_reply *)rgbResponse)->RoundTripTime;
|
|
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=hIcmp) {
|
|
IcmpCloseHandle(hIcmp);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
HRESULT MyNtpPing(in_addr * piaTarget, DWORD dwTimeout, NtpPacket * pnpPacket, NtTimeEpoch * pteDestinationTimestamp) {
|
|
HRESULT hr;
|
|
sockaddr saServer;
|
|
int nBytesRecvd;
|
|
DWORD dwWaitResult;
|
|
|
|
// must be cleaned up
|
|
SOCKET sTest=INVALID_SOCKET;
|
|
HANDLE hDataAvailEvent=NULL;
|
|
|
|
// create a socket
|
|
sTest=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (INVALID_SOCKET==sTest) {
|
|
_JumpLastError(hr, error, "socket");
|
|
}
|
|
|
|
// fix the destination address
|
|
{
|
|
sockaddr_in & saiServer=*(sockaddr_in *)(&saServer);
|
|
saiServer.sin_port=EndianSwap((unsigned __int16)NtpConst::nPort);
|
|
saiServer.sin_family=AF_INET;
|
|
saiServer.sin_addr.S_un.S_addr=piaTarget->S_un.S_addr;
|
|
}
|
|
|
|
// connect the socket to the peer
|
|
if (SOCKET_ERROR==connect(sTest, &saServer, sizeof(saServer))) {
|
|
_JumpLastError(hr, error, "connect");
|
|
}
|
|
|
|
// send an NTP packet
|
|
//DebugWPrintf1(L"Sending %d byte SNTP packet.\n", sizeof(NtpPacket));
|
|
ZeroMemory(pnpPacket, sizeof(NtpPacket));
|
|
pnpPacket->nMode=e_Client;
|
|
pnpPacket->nVersionNumber=1;
|
|
pnpPacket->teTransmitTimestamp=NtpTimeEpochFromNtTimeEpoch(GetCurrentSystemNtTimeEpoch());
|
|
if (SOCKET_ERROR==send(sTest, reinterpret_cast<char *>(pnpPacket), sizeof(NtpPacket), 0/*flags*/)) {
|
|
_JumpLastError(hr, error, "send");
|
|
}
|
|
|
|
// create the data available event
|
|
hDataAvailEvent=CreateEvent(NULL /*security*/, FALSE /*auto-reset*/, FALSE /*nonsignaled*/, NULL /*name*/);
|
|
if (NULL==hDataAvailEvent) {
|
|
_JumpLastError(hr, error, "CreateEvent");
|
|
}
|
|
|
|
// bind the event to this socket
|
|
if (SOCKET_ERROR==WSAEventSelect(sTest, hDataAvailEvent, FD_READ)) {
|
|
_JumpLastError(hr, error, "WSAEventSelect");
|
|
}
|
|
|
|
// listen on the socket
|
|
//DebugWPrintf1(L"Waiting for response for %ums...\n", dwTimeout);
|
|
dwWaitResult=WaitForSingleObject(hDataAvailEvent, dwTimeout);
|
|
if (WAIT_FAILED==dwWaitResult) {
|
|
_JumpLastError(hr, error, "WaitForSingleObject");
|
|
} else if (WAIT_TIMEOUT==dwWaitResult) {
|
|
//DebugWPrintf0(L"No response.\n");
|
|
hr=HRESULT_FROM_WIN32(ERROR_TIMEOUT);
|
|
_JumpError(hr, error, "WaitForSingleObject");
|
|
} else {
|
|
|
|
// retrieve the data
|
|
nBytesRecvd=recv(sTest, reinterpret_cast<char *>(pnpPacket), sizeof(NtpPacket), 0/*flags*/);
|
|
*pteDestinationTimestamp=GetCurrentSystemNtTimeEpoch();
|
|
if (SOCKET_ERROR==nBytesRecvd) {
|
|
_JumpLastError(hr, error, "recv");
|
|
}
|
|
//DebugWPrintf2(L"Recvd %d of %d bytes.\n", nBytesRecvd, sizeof(NtpPacket));
|
|
//DumpNtpPacket(&npPacket,teDestinationTimestamp);
|
|
}
|
|
|
|
// done
|
|
hr=S_OK;
|
|
|
|
error:
|
|
if (INVALID_SOCKET!=sTest) {
|
|
if (SOCKET_ERROR==closesocket(sTest)) {
|
|
_IgnoreLastError("closesocket");
|
|
}
|
|
}
|
|
if (NULL!=hDataAvailEvent) {
|
|
CloseHandle(hDataAvailEvent);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
HRESULT MyGetIpAddrs(const WCHAR * wszDnsName, in_addr ** prgiaLocalIpAddrs, in_addr ** prgiaRemoteIpAddrs, unsigned int *pnIpAddrs, bool * pbRetry) {
|
|
AFPROTOCOLS apInetUdp = { AF_INET, IPPROTO_UDP };
|
|
bool bRetry = FALSE;
|
|
DWORD dwDataLen;
|
|
GUID guidNtp = SVCID_NTP_UDP;
|
|
HRESULT hr;
|
|
HANDLE hSearch = INVALID_HANDLE_VALUE;
|
|
in_addr *rgiaLocalIpAddrs = NULL;
|
|
in_addr *rgiaRemoteIpAddrs = NULL;
|
|
WSAQUERYSETW qsSearch;
|
|
WSAQUERYSETW *pqsResult = NULL;
|
|
|
|
ZeroMemory(&qsSearch, sizeof(qsSearch));
|
|
|
|
// initialize the search
|
|
qsSearch.dwSize = sizeof(qsSearch);
|
|
qsSearch.lpszServiceInstanceName = const_cast<WCHAR *>(wszDnsName);
|
|
qsSearch.lpServiceClassId = &guidNtp;
|
|
qsSearch.dwNameSpace = NS_ALL;
|
|
qsSearch.dwNumberOfProtocols = 1;
|
|
qsSearch.lpafpProtocols = &apInetUdp;
|
|
|
|
// begin the search
|
|
if (SOCKET_ERROR == WSALookupServiceBegin(&qsSearch, LUP_RETURN_ADDR/*flags*/, &hSearch)) {
|
|
hr = HRESULT_FROM_WIN32(WSAGetLastError());
|
|
_JumpError(hr, error, "WSALookupServiceBegin");
|
|
}
|
|
|
|
// retrieve the result set
|
|
dwDataLen = 5*1024;
|
|
pqsResult = (WSAQUERYSETW *)LocalAlloc(LPTR, dwDataLen);
|
|
_JumpIfOutOfMemory(hr, error, pqsResult);
|
|
|
|
if (SOCKET_ERROR == WSALookupServiceNext(hSearch, LUP_RETURN_ADDR/*flags*/, &dwDataLen, pqsResult)) {
|
|
hr = HRESULT_FROM_WIN32(WSAGetLastError());
|
|
_JumpError(hr, error, "WSALookupServiceNext");
|
|
}
|
|
_Verify(0 != pqsResult->dwNumberOfCsAddrs, hr, error);
|
|
|
|
// allocate room for the IP addresses
|
|
rgiaLocalIpAddrs = (in_addr *)LocalAlloc(LPTR, sizeof(in_addr) * pqsResult->dwNumberOfCsAddrs);
|
|
_JumpIfOutOfMemory(hr, error, rgiaLocalIpAddrs);
|
|
rgiaRemoteIpAddrs = (in_addr *)LocalAlloc(LPTR, sizeof(in_addr) * pqsResult->dwNumberOfCsAddrs);
|
|
_JumpIfOutOfMemory(hr, error, rgiaRemoteIpAddrs);
|
|
|
|
// copy the IP addresses
|
|
for (unsigned int nIndex = 0; nIndex < pqsResult->dwNumberOfCsAddrs; nIndex++) {
|
|
// copy local
|
|
_Verify(sizeof(sockaddr) == pqsResult->lpcsaBuffer[nIndex].LocalAddr.iSockaddrLength, hr, error);
|
|
_Verify(AF_INET == pqsResult->lpcsaBuffer[nIndex].LocalAddr.lpSockaddr->sa_family, hr, error);
|
|
rgiaLocalIpAddrs[nIndex].S_un.S_addr = ((sockaddr_in *)(pqsResult->lpcsaBuffer[nIndex].LocalAddr.lpSockaddr))->sin_addr.S_un.S_addr;
|
|
// copy remote
|
|
_Verify(sizeof(sockaddr) == pqsResult->lpcsaBuffer[nIndex].RemoteAddr.iSockaddrLength, hr, error);
|
|
_Verify(AF_INET == pqsResult->lpcsaBuffer[nIndex].RemoteAddr.lpSockaddr->sa_family, hr, error);
|
|
rgiaRemoteIpAddrs[nIndex].S_un.S_addr = ((sockaddr_in *)(pqsResult->lpcsaBuffer[nIndex].RemoteAddr.lpSockaddr))->sin_addr.S_un.S_addr;
|
|
}
|
|
|
|
// Assign out params:
|
|
if (NULL != prgiaLocalIpAddrs) { *prgiaLocalIpAddrs = rgiaLocalIpAddrs; }
|
|
if (NULL != prgiaRemoteIpAddrs) { *prgiaRemoteIpAddrs = rgiaRemoteIpAddrs; }
|
|
if (NULL != pbRetry) { *pbRetry = bRetry; }
|
|
if (NULL != pnIpAddrs) { *pnIpAddrs = pqsResult->dwNumberOfCsAddrs; }
|
|
rgiaLocalIpAddrs = NULL;
|
|
rgiaRemoteIpAddrs = NULL;
|
|
|
|
hr = S_OK;
|
|
error:
|
|
if (NULL != pbRetry) {
|
|
// Probably shouldn't be removing manual peers. Always retry.
|
|
*pbRetry = true;
|
|
}
|
|
if (NULL != rgiaLocalIpAddrs) {
|
|
LocalFree(rgiaLocalIpAddrs);
|
|
}
|
|
if (NULL != rgiaRemoteIpAddrs) {
|
|
LocalFree(rgiaRemoteIpAddrs);
|
|
}
|
|
if (NULL != pqsResult) {
|
|
LocalFree(pqsResult);
|
|
}
|
|
if (INVALID_HANDLE_VALUE != hSearch) {
|
|
if (SOCKET_ERROR == WSALookupServiceEnd(hSearch)) {
|
|
HRESULT hr2 = HRESULT_FROM_WIN32(WSAGetLastError());
|
|
_IgnoreError(hr2, "WSALookupServiceEnd");
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// initialize the socket layer
|
|
HRESULT OpenSocketLayer(void) {
|
|
HRESULT hr;
|
|
int nRetVal;
|
|
|
|
WSADATA wdWinsockInfo;
|
|
nRetVal=WSAStartup(0x0002/*version*/, &wdWinsockInfo);
|
|
if (0!=nRetVal) {
|
|
hr=HRESULT_FROM_WIN32(nRetVal);
|
|
_JumpError(hr, error, "WSAStartup");
|
|
}
|
|
//DebugWPrintf4(L"Socket layer initialized. v:0x%04X hv:0x%04X desc:\"%S\" status:\"%S\"\n",
|
|
// wdWinsockInfo.wVersion, wdWinsockInfo.wHighVersion, wdWinsockInfo.szDescription,
|
|
// wdWinsockInfo.szSystemStatus);
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
// close down the socket layer
|
|
HRESULT CloseSocketLayer(void) {
|
|
HRESULT hr;
|
|
int nRetVal;
|
|
|
|
nRetVal=WSACleanup();
|
|
if (SOCKET_ERROR==nRetVal) {
|
|
_JumpLastError(hr, error, "WSACleanup");
|
|
}
|
|
//DebugWPrintf0(L"Socket layer cleanup successful\n");
|
|
|
|
hr=S_OK;
|
|
error:
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
HRESULT GetSystemErrorString(HRESULT hrIn, WCHAR ** pwszError) {
|
|
HRESULT hr=S_OK;
|
|
DWORD dwResult;
|
|
WCHAR * rgParams[2]={
|
|
NULL,
|
|
(WCHAR *)(ULONG_PTR)hrIn
|
|
};
|
|
|
|
// must be cleaned up
|
|
WCHAR * wszErrorMessage=NULL;
|
|
WCHAR * wszFullErrorMessage=NULL;
|
|
|
|
// initialize input params
|
|
*pwszError=NULL;
|
|
|
|
// get the message from the system
|
|
dwResult=FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL/*ignored*/, hrIn, 0/*language*/, (WCHAR *)&wszErrorMessage, 0/*min-size*/, NULL/*valist*/);
|
|
if (0==dwResult) {
|
|
if (ERROR_MR_MID_NOT_FOUND==GetLastError()) {
|
|
rgParams[0]=L"";
|
|
} else {
|
|
_JumpLastError(hr, error, "FormatMessage");
|
|
}
|
|
} else {
|
|
rgParams[0]=wszErrorMessage;
|
|
|
|
// trim off \r\n if it exists
|
|
if (L'\r'==wszErrorMessage[wcslen(wszErrorMessage)-2]) {
|
|
wszErrorMessage[wcslen(wszErrorMessage)-2]=L'\0';
|
|
}
|
|
}
|
|
|
|
// add the error number
|
|
dwResult=FormatMessage(
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
L"%1 (0x%2!08X!)", 0, 0/*language*/, (WCHAR *)&wszFullErrorMessage, 0/*min-size*/, (va_list *)rgParams);
|
|
if (0==dwResult) {
|
|
_JumpLastError(hr, error, "FormatMessage");
|
|
}
|
|
|
|
// success
|
|
*pwszError=wszFullErrorMessage;
|
|
wszFullErrorMessage=NULL;
|
|
hr=S_OK;
|
|
error:
|
|
if (NULL!=wszErrorMessage) {
|
|
LocalFree(wszErrorMessage);
|
|
}
|
|
if (NULL!=wszFullErrorMessage) {
|
|
LocalFree(wszFullErrorMessage);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
extern "C" void MIDL_user_free(void * pvValue) {
|
|
LocalFree(pvValue);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
extern "C" void * MIDL_user_allocate(size_t n) {
|
|
return (LocalAlloc(LPTR, n));
|
|
}
|
|
|