C++ Unhandled Exception - A heap has been corrupted - excel

I am struggling with an intermittent C++ application crash.
I am not a C++ programmer but am tasked with solving this problem, so very much hope you can help me.
Often the app runs fine, then on occasion crashes with an exception.
When entering debug from running the exe, line of code as seen highlighted seems to be at fault - please see first screen shot.
I have expanded some of the Locals in the second screen shot.
This line of code calls a function 'ClearVariant' the code being as follows for this function:
/*
* ClearVariant
*
* Zeros a variant structure without regard to current contents
*/
void CXLAutomation::ClearVariant(VARIANTARG *pvarg)
{
pvarg->vt = VT_EMPTY;
pvarg->wReserved1 = 0;
pvarg->wReserved2 = 0;
pvarg->wReserved3 = 0;
pvarg->lVal = 0;
}
The entire cpp file is at the end of the post.
The OpenExcelFile is function that leads to this problem - as you can from the call stack in the screen shots.
// XLAutomation.cpp: implementation of the CXLAutomation class.
//This is C++ modification of the AutoXL C-sample from
//Microsoft Excel97 Developer Kit, Microsoft Press 1997
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
//#include "XLAutomationTester.h"
#include "XLAutomation.h"
#include <ole2ver.h>
#include <string.h>
#include <winuser.h>
#include <stdio.h>
#include <string>
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
/*
* Arrays of argument information, which are used to build up the arg list
* for an IDispatch call. These arrays are statically allocated to reduce
* complexity, but this code could be easily modified to perform dynamic
* memory allocation.
*
* When arguments are added they are placed into these arrays. The
* Vargs array contains the argument values, and the lpszArgNames array
* contains the name of the arguments, or a NULL if the argument is unnamed.
* Flags for the argument such as NOFREEVARIANT are kept in the wFlags array.
*
* When Invoke is called, the names in the lpszArgNames array are converted
* into the DISPIDs expected by the IDispatch::Invoke function. The
* IDispatch::GetIDsOfNames function is used to perform the conversion, and
* the resulting IDs are placed in the DispIds array. There is an additional
* slot in the DispIds and lpszArgNames arrays to allow for the name and DISPID
* of the method or property being invoked.
*
* Because these arrays are static, it is important to call the ClearArgs()
* function before setting up arguments. ClearArgs() releases any memory
* in use by the argument array and resets the argument counters for a fresh
* Invoke.
*/
//int m_iArgCount;
//int m_iNamedArgCount;
//VARIANTARG m_aVargs[MAX_DISP_ARGS];
//DISPID m_aDispIds[MAX_DISP_ARGS + 1]; // one extra for the member name
//LPOLESTR m_alpszArgNames[MAX_DISP_ARGS + 1]; // used to hold the argnames for GetIDs
//WORD m_awFlags[MAX_DISP_ARGS];
//////////////////////////////////////////////////////////////////////
CXLAutomation::CXLAutomation()
{
m_pdispExcelApp = NULL;
m_pdispWorkbook = NULL;
m_pdispWorksheet = NULL;
m_pdispActiveChart = NULL;
InitOLE();
StartExcel();
//SetExcelVisible(TRUE);
//CreateWorkSheet();
//CreateXYChart();
}
CXLAutomation::CXLAutomation(BOOL bVisible)
{
m_pdispExcelApp = NULL;
m_pdispWorkbook = NULL;
m_pdispWorksheet = NULL;
m_pdispActiveChart = NULL;
InitOLE();
StartExcel();
SetExcelVisible(bVisible);
CreateWorkSheet();
//CreateXYChart();
}
CXLAutomation::~CXLAutomation()
{
//ReleaseExcel();
ReleaseDispatch();
OleUninitialize();
}
BOOL CXLAutomation::InitOLE()
{
DWORD dwOleVer;
dwOleVer = CoBuildVersion();
// check the OLE library version
if (rmm != HIWORD(dwOleVer))
{
MessageBox(NULL, _T("Incorrect version of OLE libraries."), "Failed", MB_OK | MB_ICONSTOP);
return FALSE;
}
// could also check for minor version, but this application is
// not sensitive to the minor version of OLE
// initialize OLE, fail application if we can't get OLE to init.
if (FAILED(OleInitialize(NULL)))
{
MessageBox(NULL, _T("Cannot initialize OLE."), "Failed", MB_OK | MB_ICONSTOP);
return FALSE;
}
return TRUE;
}
BOOL CXLAutomation::StartExcel()
{
CLSID clsExcelApp;
// if Excel is already running, return with current instance
if (m_pdispExcelApp != NULL)
return TRUE;
/* Obtain the CLSID that identifies EXCEL.APPLICATION
* This value is universally unique to Excel versions 5 and up, and
* is used by OLE to identify which server to start. We are obtaining
* the CLSID from the ProgID.
*/
if (FAILED(CLSIDFromProgID(L"Excel.Application", &clsExcelApp)))
{
MessageBox(NULL, _T("Cannot obtain CLSID from ProgID"), "Failed", MB_OK | MB_ICONSTOP);
return FALSE;
}
// start a new copy of Excel, grab the IDispatch interface
if (FAILED(CoCreateInstance(clsExcelApp, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void**)&m_pdispExcelApp)))
{
MessageBox(NULL, _T("Cannot start an instance of Excel for Automation."), "Failed", MB_OK | MB_ICONSTOP);
return FALSE;
}
return TRUE;
}
/*******************************************************************
*
* INVOKE
*
*******************************************************************/
/*
* INVOKE
*
* Invokes a method or property. Takes the IDispatch object on which to invoke,
* and the name of the method or property as a String. Arguments, if any,
* must have been previously setup using the AddArgumentXxx() functions.
*
* Returns TRUE if the call succeeded. Returns FALSE if an error occurred.
* A messagebox will be displayed explaining the error unless the DISP_NOSHOWEXCEPTIONS
* flag is specified. Errors can be a result of unrecognized method or property
* names, bad argument names, invalid types, or runtime-exceptions defined
* by the recipient of the Invoke.
*
* The argument list is reset via ClearAllArgs() if the DISP_FREEARGS flag is
* specified. If not specified, it is up to the caller to call ClearAllArgs().
*
* The return value is placed in pvargReturn, which is allocated by the caller.
* If no return value is required, pass NULL. It is up to the caller to free
* the return value (ReleaseVariant()).
*
* This function calls IDispatch::GetIDsOfNames for every invoke. This is not
* very efficient if the same method or property is invoked multiple times, since
* the DISPIDs for a particular method or property will remain the same during
* the lifetime of an IDispatch object. Modifications could be made to this code
* to cache DISPIDs. If the target application is always the same, a similar
* modification is to statically browse and store the DISPIDs at compile-time, since
* a given application will return the same DISPIDs in different sessions.
* Eliminating the extra cross-process GetIDsOfNames call can result in a
* signficant time savings.
*/
BOOL CXLAutomation::ExlInvoke(IDispatch *pdisp, LPOLESTR szMember, VARIANTARG * pvargReturn,
WORD wInvokeAction, WORD wFlags)
{
HRESULT hr;
DISPPARAMS dispparams;
unsigned int uiArgErr;
EXCEPINFO excep;
// Get the IDs for the member and its arguments. GetIDsOfNames expects the
// member name as the first name, followed by argument names (if any).
m_alpszArgNames[0] = szMember;
hr = pdisp->GetIDsOfNames( IID_NULL, m_alpszArgNames,
1 + m_iNamedArgCount, LOCALE_SYSTEM_DEFAULT, m_aDispIds);
if (FAILED(hr))
{
if (!(wFlags & DISP_NOSHOWEXCEPTIONS))
ShowException(szMember, hr, NULL, 0);
return FALSE;
}
if (pvargReturn != NULL)
ClearVariant(pvargReturn);
// if doing a property put(ref), we need to adjust the first argument to have a
// named arg of DISPID_PROPERTYPUT.
if (wInvokeAction & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
{
m_iNamedArgCount = 1;
m_aDispIds[1] = DISPID_PROPERTYPUT;
pvargReturn = NULL;
}
dispparams.rgdispidNamedArgs = m_aDispIds + 1;
dispparams.rgvarg = m_aVargs;
dispparams.cArgs = m_iArgCount;
dispparams.cNamedArgs = m_iNamedArgCount;
excep.pfnDeferredFillIn = NULL;
hr = pdisp->Invoke(m_aDispIds[0], IID_NULL, LOCALE_SYSTEM_DEFAULT,
wInvokeAction, &dispparams, pvargReturn, &excep, &uiArgErr);
if (wFlags & DISP_FREEARGS)
ClearAllArgs();
if (FAILED(hr))
{
// display the exception information if appropriate:
if (!(wFlags & DISP_NOSHOWEXCEPTIONS))
ShowException(szMember, hr, &excep, uiArgErr);
// free exception structure information
SysFreeString(excep.bstrSource);
SysFreeString(excep.bstrDescription);
SysFreeString(excep.bstrHelpFile);
return FALSE;
}
return TRUE;
}
/*
* ClearVariant
*
* Zeros a variant structure without regard to current contents
*/
void CXLAutomation::ClearVariant(VARIANTARG *pvarg)
{
pvarg->vt = VT_EMPTY;
pvarg->wReserved1 = 0;
pvarg->wReserved2 = 0;
pvarg->wReserved3 = 0;
pvarg->lVal = 0;
}
/*
* ClearAllArgs
*
* Clears the existing contents of the arg array in preparation for
* a new invocation. Frees argument memory if so marked.
*/
void CXLAutomation::ClearAllArgs()
{
int i;
for (i = 0; i < m_iArgCount; i++)
{
if (m_awFlags[i] & DISPARG_NOFREEVARIANT)
// free the variant's contents based on type
ClearVariant(&m_aVargs[i]);
else
//ClearVariant(&m_aVargs[i]);
ReleaseVariant(&m_aVargs[i]);
}
m_iArgCount = 0;
m_iNamedArgCount = 0;
}
void CXLAutomation::ReleaseVariant(VARIANTARG *pvarg)
{
VARTYPE vt;
VARIANTARG *pvargArray;
long lLBound, lUBound, l;
vt = pvarg->vt & 0xfff; // mask off flags
// check if an array. If so, free its contents, then the array itself.
if (V_ISARRAY(pvarg))
{
// variant arrays are all this routine currently knows about. Since a
// variant can contain anything (even other arrays), call ourselves
// recursively.
if (vt == VT_VARIANT)
{
SafeArrayGetLBound(pvarg->parray, 1, &lLBound);
SafeArrayGetUBound(pvarg->parray, 1, &lUBound);
if (lUBound > lLBound)
{
lUBound -= lLBound;
SafeArrayAccessData(pvarg->parray, (void**)&pvargArray);
for (l = 0; l < lUBound; l++)
{
ReleaseVariant(pvargArray);
pvargArray++;
}
SafeArrayUnaccessData(pvarg->parray);
}
}
else
{
return ;//1; // non-variant type
// MessageBox(NULL, _T("ReleaseVariant: Array contains non-variant type"), "Failed", MB_OK | MB_ICONSTOP);
}
// Free the array itself.
SafeArrayDestroy(pvarg->parray);
}
else
{
switch (vt)
{
case VT_DISPATCH:
//(*(pvarg->pdispVal->lpVtbl->Release))(pvarg->pdispVal);
pvarg->pdispVal->Release();
break;
case VT_BSTR:
SysFreeString(pvarg->bstrVal);
break;
case VT_I2:
case VT_BOOL:
case VT_R8:
case VT_ERROR: // to avoid erroring on an error return from Excel
// no work for these types
break;
default:
return;// 2; //unknonw type
// MessageBox(NULL, _T("ReleaseVariant: Unknown type"), "Failed", MB_OK | MB_ICONSTOP);
break;
}
}
ClearVariant(pvarg);
return ;//0;
}
BOOL CXLAutomation::SetExcelVisible(BOOL bVisible)
{
if (m_pdispExcelApp == NULL)
return FALSE;
ClearAllArgs();
AddArgumentBool(NULL, 0, bVisible);
return ExlInvoke(m_pdispExcelApp, L"Visible", NULL, DISPATCH_PROPERTYPUT, DISP_FREEARGS);
}
BOOL CXLAutomation::SetExcelFileValidation(BOOL bFileValidation)
{
if (m_pdispExcelApp == NULL)
return FALSE;
ClearAllArgs();
AddArgumentBool(NULL, 0, bFileValidation);
return ExlInvoke(m_pdispExcelApp, L"FileValidation", NULL, DISPATCH_PROPERTYPUT, DISP_FREEARGS);
}
/*******************************************************************
*
* ARGUMENT CONSTRUCTOR FUNCTIONS
*
* Each function adds a single argument of a specific type to the list
* of arguments for the current invoke. If appropriate, memory may be
* allocated to represent the argument. This memory will be
* automatically freed the next time ClearAllArgs() is called unless
* the NOFREEVARIANT flag is specified for a particular argument. If
* NOFREEVARIANT is specified it is the responsibility of the caller
* to free the memory allocated for or contained within the argument.
*
* Arguments may be named. The name string must be a C-style string
* and it is owned by the caller. If dynamically allocated, the caller
* must free the name string.
*
*******************************************************************/
/*
* Common code used by all variant types for setting up an argument.
*/
void CXLAutomation::AddArgumentCommon(LPOLESTR lpszArgName, WORD wFlags, VARTYPE vt)
{
ClearVariant(&m_aVargs[m_iArgCount]);
m_aVargs[m_iArgCount].vt = vt;
m_awFlags[m_iArgCount] = wFlags;
if (lpszArgName != NULL)
{
m_alpszArgNames[m_iNamedArgCount + 1] = lpszArgName;
m_iNamedArgCount++;
}
}
BOOL CXLAutomation::AddArgumentDispatch(LPOLESTR lpszArgName, WORD wFlags, IDispatch * pdisp)
{
AddArgumentCommon(lpszArgName, wFlags, VT_DISPATCH);
m_aVargs[m_iArgCount++].pdispVal = pdisp;
return TRUE;
}
BOOL CXLAutomation::AddArgumentInt2(LPOLESTR lpszArgName, WORD wFlags, int i)
{
AddArgumentCommon(lpszArgName, wFlags, VT_I2);
m_aVargs[m_iArgCount++].iVal = i;
return TRUE;
}
BOOL CXLAutomation::AddArgumentBool(LPOLESTR lpszArgName, WORD wFlags, BOOL b)
{
AddArgumentCommon(lpszArgName, wFlags, VT_BOOL);
// Note the variant representation of True as -1
m_aVargs[m_iArgCount++].boolVal = b ? -1 : 0;
return TRUE;
}
BOOL CXLAutomation::AddArgumentDouble(LPOLESTR lpszArgName, WORD wFlags, double d)
{
AddArgumentCommon(lpszArgName, wFlags, VT_R8);
m_aVargs[m_iArgCount++].dblVal = d;
return TRUE;
}
BOOL CXLAutomation::ReleaseExcel()
{
if (m_pdispExcelApp == NULL)
return TRUE;
// Tell Excel to quit, since for automation simply releasing the IDispatch
// object isn't enough to get the server to shut down.
// Note that this code will hang if Excel tries to display any message boxes.
// This can occur if a document is in need of saving. The CreateChart() code
// always clears the dirty bit on the documents it creates, avoiding this problem.
ClearAllArgs();
ExlInvoke(m_pdispExcelApp, L"Quit", NULL, DISPATCH_METHOD, 0);
// Even though Excel has been told to Quit, we still need to release the
// OLE object to account for all memory.
ReleaseDispatch();
return TRUE;
}
//Create an empty workshet
BOOL CXLAutomation::CreateWorkSheet()
{
if(NULL == m_pdispExcelApp)
return FALSE;
BOOL fResult;
VARIANTARG varg1, varg2;
IDispatch *pdispRange = NULL;
IDispatch *pdispActiveSheet = NULL;
IDispatch *pdispActiveCell = NULL;
IDispatch *pdispCrt = NULL;
// Set wb = [application].Workbooks.Add(template := xlWorksheet)
ClearAllArgs();
if (!ExlInvoke(m_pdispExcelApp, L"Workbooks", &varg1, DISPATCH_PROPERTYGET, 0))
return FALSE;
ClearAllArgs();
AddArgumentInt2(L"Template", 0, xlWorksheet);
fResult = ExlInvoke(varg1.pdispVal, L"Add", &varg2, DISPATCH_METHOD, 0);
ReleaseVariant(&varg1);
if (!fResult)
return FALSE;
m_pdispWorkbook = varg2.pdispVal;
// Set ws = wb.Worksheets(1)
ClearAllArgs();
AddArgumentInt2(NULL, 0, 1);
if (!ExlInvoke(m_pdispWorkbook, L"Worksheets", &varg2, DISPATCH_PROPERTYGET, 0))
goto CreateWsBail;
m_pdispWorksheet = varg2.pdispVal;
fResult = TRUE;
CreateWsExit:
if (pdispRange != NULL)
pdispRange->Release();
if (pdispCrt != NULL)
pdispCrt->Release();
return fResult;
CreateWsBail:
fResult = FALSE;
goto CreateWsExit;
}
/*
* OLE and IDispatch use a BSTR as the representation of strings.
* This constructor automatically copies the passed-in C-style string
* into a BSTR. It is important to not set the NOFREEVARIANT flag
* for this function, otherwise the allocated BSTR copy will probably
* get lost and cause a memory leak.
*/
BOOL CXLAutomation::AddArgumentOLEString(LPOLESTR lpszArgName, WORD wFlags, LPOLESTR lpsz)
{
BSTR b;
b = SysAllocString(lpsz);
if (!b)
return FALSE;
AddArgumentCommon(lpszArgName, wFlags, VT_BSTR);
m_aVargs[m_iArgCount++].bstrVal = b;
return TRUE;
}
BOOL CXLAutomation::AddArgumentCString(LPOLESTR lpszArgName, WORD wFlags, CString szStr)
{
BSTR b;
b = szStr.AllocSysString();
if (!b)
return FALSE;
AddArgumentCommon(lpszArgName, wFlags, VT_BSTR);
m_aVargs[m_iArgCount++].bstrVal = b;
return TRUE;
}
/*
* Constructs an 1-dimensional array containing variant strings. The strings
* are copied from an incoming array of C-Strings.
*/
BOOL CXLAutomation::AddArgumentCStringArray(LPOLESTR lpszArgName, WORD wFlags, LPOLESTR *paszStrings, int iCount)
{
SAFEARRAY *psa;
SAFEARRAYBOUND saBound;
VARIANTARG *pvargBase;
VARIANTARG *pvarg;
int i, j;
saBound.lLbound = 0;
saBound.cElements = iCount;
psa = SafeArrayCreate(VT_VARIANT, 1, &saBound);
if (psa == NULL)
return FALSE;
SafeArrayAccessData(psa, (void**) &pvargBase);
pvarg = pvargBase;
for (i = 0; i < iCount; i++)
{
// copy each string in the list of strings
ClearVariant(pvarg);
pvarg->vt = VT_BSTR;
if ((pvarg->bstrVal = SysAllocString(*paszStrings++)) == NULL)
{
// memory failure: back out and free strings alloc'ed up to
// now, and then the array itself.
pvarg = pvargBase;
for (j = 0; j < i; j++)
{
SysFreeString(pvarg->bstrVal);
pvarg++;
}
SafeArrayDestroy(psa);
return FALSE;
}
pvarg++;
}
SafeArrayUnaccessData(psa);
// With all memory allocated, setup this argument
AddArgumentCommon(lpszArgName, wFlags, VT_VARIANT | VT_ARRAY);
m_aVargs[m_iArgCount++].parray = psa;
return TRUE;
}
//Clean up: release dipatches
void CXLAutomation::ReleaseDispatch()
{
if(NULL != m_pdispExcelApp)
{
m_pdispExcelApp->Release();
m_pdispExcelApp = NULL;
}
if(NULL != m_pdispWorksheet)
{
m_pdispWorksheet->Release();
m_pdispWorksheet = NULL;
}
if(NULL != m_pdispWorkbook)
{
m_pdispWorkbook->Release();
m_pdispWorkbook = NULL;
}
if(NULL != m_pdispActiveChart)
{
m_pdispActiveChart->Release();
m_pdispActiveChart = NULL;
}
}
void CXLAutomation::ShowException(LPOLESTR szMember, HRESULT hr, EXCEPINFO *pexcep, unsigned int uiArgErr)
{
TCHAR szBuf[512];
switch (GetScode(hr))
{
case DISP_E_UNKNOWNNAME:
wsprintf(szBuf, "%s: Unknown name or named argument.", szMember);
break;
case DISP_E_BADPARAMCOUNT:
wsprintf(szBuf, "%s: Incorrect number of arguments.", szMember);
break;
case DISP_E_EXCEPTION:
wsprintf(szBuf, "%s: Error %d: ", szMember, pexcep->wCode);
if (pexcep->bstrDescription != NULL)
lstrcat(szBuf, (char*)pexcep->bstrDescription);
else
lstrcat(szBuf, "<<No Description>>");
break;
case DISP_E_MEMBERNOTFOUND:
wsprintf(szBuf, "%s: method or property not found.", szMember);
break;
case DISP_E_OVERFLOW:
wsprintf(szBuf, "%s: Overflow while coercing argument values.", szMember);
break;
case DISP_E_NONAMEDARGS:
wsprintf(szBuf, "%s: Object implementation does not support named arguments.",
szMember);
break;
case DISP_E_UNKNOWNLCID:
wsprintf(szBuf, "%s: The locale ID is unknown.", szMember);
break;
case DISP_E_PARAMNOTOPTIONAL:
wsprintf(szBuf, "%s: Missing a required parameter.", szMember);
break;
case DISP_E_PARAMNOTFOUND:
wsprintf(szBuf, "%s: Argument not found, argument %d.", szMember, uiArgErr);
break;
case DISP_E_TYPEMISMATCH:
wsprintf(szBuf, "%s: Type mismatch, argument %d.", szMember, uiArgErr);
break;
default:
wsprintf(szBuf, "%s: Unknown error occured.", szMember);
break;
}
MessageBox(NULL, szBuf, "OLE Error", MB_OK | MB_ICONSTOP);
}
//Open Microsoft Excel file and switch to the firs available worksheet.
BOOL CXLAutomation::OpenExcelFile(CString szFileName)
{
//Leave if the file cannot be open
if(NULL == m_pdispExcelApp)
return FALSE;
if(szFileName.IsEmpty())
return FALSE;
VARIANTARG varg1, vargWorkbook, vargWorksheet;
SetExcelFileValidation(FALSE);
ClearAllArgs();
if (!ExlInvoke(m_pdispExcelApp, L"Workbooks", &varg1, DISPATCH_PROPERTYGET, 0))
return FALSE;
ClearAllArgs();
AddArgumentCString(L"Filename", 0, szFileName);
if (!ExlInvoke(varg1.pdispVal, L"Open", &vargWorkbook, DISPATCH_PROPERTYGET, DISP_FREEARGS))
return FALSE;
//Now let's get the first worksheet of this workbook
ClearAllArgs();
AddArgumentInt2(NULL, 0, 1);
if (!ExlInvoke(vargWorkbook.pdispVal, L"Worksheets", &vargWorksheet, DISPATCH_PROPERTYGET, DISP_FREEARGS))
return FALSE;
//Close the empty worksheet
ClearAllArgs();
//if (!ExlInvoke(m_pdispWorkbook, L"Close", NULL, DISPATCH_PROPERTYGET, DISP_FREEARGS))
// return FALSE;
SetExcelVisible(TRUE);
//Remember the newly open worksheet
m_pdispWorkbook = vargWorkbook.pdispVal;
m_pdispWorksheet = vargWorksheet.pdispVal;
ReleaseDispatch();
return TRUE;
}

At first glance it seems your pvarg may not be fully consistent / not providing what's expected. The clear function does no checks, so it will just write, regardless. Or try to.
But that may be a gross oversimplification without a full and thorough look at all involved code.
Adding this here as your project is closed on Freelancer and who knows, it may help you or another think in the right direction at a future date anyway.

I fixed the problem by not using that MS class library at all - it is way too complicated and prone to problems.
I found a simple code sample here and adapted it for my needs.
I have added code to open the Excel file as I need to, here is the source which I hope will help anyone with a similar problem.
Whoever down voted the question - please reconsider and vote it back up.
// Start server and get IDispatch...
IDispatch *pXlApp;
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pXlApp);
if(FAILED(hr)) {
::MessageBox(NULL, "Excel not registered properly", "Error", 0x10010);
return -2;
}
// Make it visible (i.e. app.visible = 1)
{
VARIANT x;
x.vt = VT_I4;
x.lVal = 1;
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlApp, L"Visible", 1, x);
}
// Get Workbooks collection
IDispatch *pXlBooks;
{
VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"Workbooks", 0);
pXlBooks = result.pdispVal;
}
// Open Excel file
{
VARIANT result;
VariantInit(&result);
VARIANT fname;
fname.vt = VT_BSTR;
std::string str = GetAppPath() + "\\test.xlsm";
fname.bstrVal=::SysAllocString(CA2W (str.c_str ()));
AutoWrap(DISPATCH_METHOD, &result, pXlBooks, L"Open", 1, fname);
}
// Release references...
pXlBooks->Release();
pXlApp->Release();

Related

Trying to replace delete pointer call with std::unique_ptr call

I am getting myself confused again with unique pointers and deletion.
I am trying to avoid the need for the delete pWidth line below:
void CCreateReportDlg::DeleteColumnWidthMap(CMapWordToPtr& rMapColumnWidth)
{
WORD wColumn{}, * pWidth = nullptr;
auto sPos = rMapColumnWidth.GetStartPosition();
while (sPos != nullptr)
{
rMapColumnWidth.GetNextAssoc(sPos, wColumn, reinterpret_cast<void*&>(pWidth));
if (pWidth != nullptr)
{
//delete pWidth;
std::unique_ptr<WORD*> cleanup(pWidth);
}
}
rMapColumnWidth.RemoveAll();
}
Why is it saying this?
Ah, I think I now see that the unique_ptr is supposed to be an array of WORD pointers. But I only have just the one value.
Update
Example of how I populate the map (parameter to function):
void CCreateReportDlg::HideColumns(CGridCtrl* pGrid, const CDWordArray* pAryDWColumns, CMapWordToPtr& rMapColumnWidth)
{
ASSERT(pGrid != nullptr);
ASSERT(pAryDWColumns != nullptr);
if (pGrid == nullptr || pAryDWColumns == nullptr)
return;
DeleteColumnWidthMap(rMapColumnWidth);
const auto iSize = pAryDWColumns->GetSize();
for (INT_PTR i = 0; i < iSize; i++)
{
const auto dwColumnData = pAryDWColumns->GetAt(i);
const auto iCol = LOWORD(dwColumnData);
const auto eImage = static_cast<CheckImageIndex>(HIWORD(dwColumnData));
if (eImage == CheckImageIndex::Unchecked)
{
auto pWidth = std::make_unique<WORD>().release();
ASSERT(pWidth != nullptr);
if (pWidth != nullptr)
{
*pWidth = pGrid->GetColumnWidth(iCol);
rMapColumnWidth.SetAt(iCol, pWidth);
}
pGrid->SetColumnWidth(iCol, 0);
}
}
}
Use {std::unique_ptr<WORD> cleanup(pWidth);} for deleting pWidth = std::make_unique<WORD>().release();
But std::map<int,int> (or unordered_map) is a much better than CMapWordToPtr, you don't need to store a pointer here.
You should be able to simplify your function like this:
void HideColumns(CGridCtrl* pGrid,
std::vector<DWORD> &pAryDWColumns, std::map<int,int> &rMapColumnWidth)
{
//clear the map, it doesn't need separate function
rMapColumnWidth.clear();
for (auto dword : pAryDWColumns)
{
const auto iCol = LOWORD(dword);
const auto eImage = static_cast<CheckImageIndex>(HIWORD(dword));
if (eImage == CheckImageIndex::Unchecked)
{
rMapColumnWidth[iCol] = pGrid->GetColumnWidth(iCol);
pGrid->SetColumnWidth(iCol, 0);
}
pGrid->SetColumnWidth(iCol, 0);
}
}
...
for (auto e : map)
TRACE("%d\n", e);
By the way, in another question I think I recommended using std::unique_ptr to turn off some Code Analysis messages. You should ignore that advice. Either stick with new/delete or use STL classes which have automatic memory management.
You can still use std::unique_ptr in some special cases, for example when passing data to APIs or some MFC functions.

How to get the output with console app and display it to MFC Dialog StaticText

I am working on a MFC app that need to get the same output with the console app. The console app can display the input data from serial port. I want to get the input with the MFC dialog app and display it into edit control box. Since I am still new to C++, it seems quite challenging to me. Any suggestions for the workaround? Thanks in advance!
The console app code:
enum { EOF_Char = 27 };
int ShowError (LONG lError, LPCTSTR lptszMessage)
{
// Generate a message text
TCHAR tszMessage[256];
wsprintf(tszMessage,_T("%s\n(error code %d)"), lptszMessage, lError);
return 1;
}
int __cdecl _tmain (int /*argc*/, char** /*argv*/)
{
CSerial serial;
LONG lLastError = ERROR_SUCCESS;
lLastError = serial.Open(_T("COM1"), 0, 0, false);
lLastError = serial.Setup(CSerial::EBaud9600, CSerial::EData8,CSerial::EParNone, CSerial::EStop1);
lLastError = serial.SetupHandshaking(CSerial::EHandshakeHardware);
// ** The query command to get input data **
lLastError = serial.Write(":MEAS:FREQuency?\n");
// Register only for the receive event
lLastError = serial.SetMask(CSerial::EEventBreak |
CSerial::EEventCTS |
CSerial::EEventDSR |
CSerial::EEventError |
CSerial::EEventRLSD |
CSerial::EEventRecv);
if (lLastError != ERROR_SUCCESS)
return ::ShowError(serial.GetLastError(), _T("Unable to set COM-port event mask"));
// Use 'non-blocking' reads,cuz we don't know how many bytes will be received.
lLastError = serial.SetupReadTimeouts(CSerial::EReadTimeoutNonblocking);
if (lLastError != ERROR_SUCCESS)
return ::ShowError(serial.GetLastError(), _T("Unable to set COM-port read timeout."));
// Keep reading data, until an EOF (CTRL-Z) has been received
bool fContinue = true;
do
{
// Wait for an event
lLastError = serial.WaitEvent();
if (lLastError != ERROR_SUCCESS)
return ::ShowError(serial.GetLastError(), _T("Unable to wait for a COM-port event."));
// Save event
const CSerial::EEvent eEvent = serial.GetEventType();
// Handle break event
if (eEvent & CSerial::EEventBreak)
{ printf("\n### BREAK received ###\n"); }
// Handle CTS event
if (eEvent & CSerial::EEventCTS)
{ printf("\n### Clear to send %s ###\n", serial.GetCTS()?"on":"off"); }
// Handle error event
if (eEvent & CSerial::EEventError)
{
printf("\n### ERROR: ");
switch (serial.GetError())
{
case CSerial::EErrorBreak: printf("Break condition"); break;
case CSerial::EErrorMode: printf("Unsupported mode"); break;
case CSerial::EErrorOverrun: printf("Buffer overrun"); break;
case CSerial::EErrorTxFull: printf("Output buffer full"); break;
default: printf("Unknown"); break;
}
printf(" ###\n");
}
// Handle RLSD/CD event
if (eEvent & CSerial::EEventRLSD)
{ printf("\n### RLSD/CD %s ###\n", serial.GetRLSD()?"on":"off"); }
// Handle data receive event
if (eEvent & CSerial::EEventRecv)
{
// Read data, until there is nothing left
DWORD dwBytesRead = 0;
char szBuffer[101];
do
{
// Read data from the COM-port
lLastError = serial.Read(szBuffer,sizeof(szBuffer)-1,&dwBytesRead);
if (lLastError != ERROR_SUCCESS)
return ::ShowError(serial.GetLastError(), _T("Unable to read from COM-port."));
if (dwBytesRead > 0)
{
// Finalize the data, so it is a valid string
szBuffer[dwBytesRead] = '\0';
// **Display the data/Need the same output for EditBox**
printf("%s", szBuffer);
// Check if EOF (CTRL+'[') has been specified
if (strchr(szBuffer,EOF_Char))
fContinue = false;
}
}
while (dwBytesRead == sizeof(szBuffer)-1);
}
}
while (fContinue);
// Close the port again
serial.Close();
return 0;
}
Since my knowledge in C++ was not good enough, so I was not aware of my problems were how to break the loop in the above code & the conversion between char array and CString. This is my answer :
void Singlemode::OnBnClickedButton3()
{
CString str3;
// TODO: Add your control notification handler code here
CSerial serial;
LONG lLastError = ERROR_SUCCESS;
lLastError = serial.Open(_T("COM1"), 0, 0, false);
lLastError = serial.Setup(CSerial::EBaud9600, CSerial::EData8, CSerial::EParNone, CSerial::EStop1);
lLastError = serial.SetupHandshaking(CSerial::EHandshakeHardware);
lLastError = serial.Write(":MEAS:FREQuency?\n");
// Register only for the receive event
// Use 'non-blocking' reads, because we don't know how many bytes
// will be received. This is normally the most convenient mode
// (and also the default mode for reading data).
bool fContinue = true;
do
{
// Wait for an event
lLastError = serial.WaitEvent();
// Save event
const CSerial::EEvent eEvent = serial.GetEventType();
// Handle data receive event
if (eEvent & CSerial::EEventRecv)
{
// Read data, until there is nothing left
DWORD dwBytesRead = 0;
char szBuffer[101];
do
{
// Read data from the COM-port
lLastError = serial.Read(szBuffer, sizeof(szBuffer) - 1, &dwBytesRead);
if (dwBytesRead > 0)
{
// Finalize the data, so it is a valid string
szBuffer[dwBytesRead] = '\0';
// Display the data
str3 = CString(szBuffer); // conversion to display
show_result.SetWindowText(str3);
fContinue = false; // break the loop
}
} while (dwBytesRead == sizeof(szBuffer) - 1);
}
} while (fContinue);
}

Calculating width of unicode character in DirectWrite using GetDesignGlyphMetrics

I am working on a GDI to DirectWrite migration project.
I want to calculate width of each unicode character , on GDI this is acomplished using GetCharWidth.
On msdn blog i found that replacement for GetCharWidth of GDI is GetDesignGlyphMetrics .
Can anyone tell me how to use this function GetDesignGlyphMetrics in order to get DWRITE_GLYPH_METRICS?
How to instantiate its first parameter UINT16 const* glyphIndices?
You can get the glyphindices by IDWriteFontFace::GetGlyphIndices
I pick some code from my project, this is just an example show you how to use this function, if you want to use it in your project, you should do some refactoring, move the XXXCreate function to the initialize part of your code. for example, you don't need to create the DWriteFacotry every time when you call this function(GetCharWidth). and release the dynamic array to avoid memory leaks.
IDWriteFactory* g_pDWriteFactory = NULL;
IDWriteFontFace* g_pFontFace = NULL;
IDWriteFontFile* g_pFontFile = NULL;
IDWriteTextFormat* g_pTextFormat = NULL;
VOID GetCharWidth(wchar_t c)
{
// Create Direct2D Factory
HRESULT hr = D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&g_pD2DFactory
);
if(FAILED(hr))
{
MessageBox(NULL, L"Create Direct2D factory failed!", L"Error", 0);
return;
}
// Create font file reference
const WCHAR* filePath = L"C:/Windows/Fonts/timesbd.ttf";
hr = g_pDWriteFactory->CreateFontFileReference(
filePath,
NULL,
&g_pFontFile
);
if(FAILED(hr))
{
MessageBox(NULL, L"Create font file reference failed!", L"Error", 0);
return;
}
// Create font face
IDWriteFontFile* fontFileArray[] = { g_pFontFile };
g_pDWriteFactory->CreateFontFace(
DWRITE_FONT_FACE_TYPE_TRUETYPE,
1,
fontFileArray,
0,
DWRITE_FONT_SIMULATIONS_NONE,
&g_pFontFace
);
if(FAILED(hr))
{
MessageBox(NULL, L"Create font file face failed!", L"Error", 0);
return;
}
wchar_t textString[] = {c, '\0'};
// Get text length
UINT32 textLength = (UINT32)wcslen(textString);
UINT32* pCodePoints = new UINT32[textLength];
ZeroMemory(pCodePoints, sizeof(UINT32) * textLength);
UINT16* pGlyphIndices = new UINT16[textLength];
ZeroMemory(pGlyphIndices, sizeof(UINT16) * textLength);
for(unsigned int i = 0; i < textLength; ++i)
{
pCodePoints[i] = textString[i];
}
// Get glyph indices
hr = g_pFontFace->GetGlyphIndices(
pCodePoints,
textLength,
pGlyphIndices
);
if(FAILED(hr))
{
MessageBox(NULL, L"Get glyph indices failed!", L"Error", 0);
return;
}
DWRITE_GLYPH_METRICS* glyphmetrics = new DWRITE_GLYPH_METRICS[textLength];
g_pFontFace->GetDesignGlyphMetrics(pGlyphIndices, textLength, glyphmetrics);
// do your calculation here
delete []glyphmetrics;
glyphmetrics = NULL;
}

Is there a way to use normal ASCII characters (like a comma) as wxWidgets menu accelerators?

I want a few menu entries that show accelerators that are normal keys, like the space-bar or comma key, but I don't want wxWidgets to make those accelerators itself (because then they can't be used anywhere in the program, including in things like edit boxes).
Unfortunately, wxWidgets insists on always making anything it recognizes in that column into an accelerator under its control, and simply erases anything it doesn't recognize.
I'm looking for some way to either put arbitrary text into the accelerator column (which I don't think exists, I've looked at the source code), or get 'hold of the accelerator table used for the menus so I can modify it myself (haven't found it yet). Can anyone point me in the right direction?
You can try wxKeyBinder. It allows you to bind hotkeys to commands (usually menu entries), save/load/add/remove/modify ... them easily
I couldn't find a way to access the menu's accelerator keys directly, but modifying the accelerator menu text works just as well. Here's the code I came up with:
In a header file:
class accel_t {
public:
// If idcount == -1, idlist must be null or terminated with a -1 entry.
accel_t(): mMenu(0) { }
accel_t(wxMenuBar *m, int *idlist = 0, int idcount = -1);
void reset(wxMenuBar *m, int *idlist = 0, int idcount = -1);
void restore() const;
void remove() const;
private: //
struct accelitem_t {
accelitem_t(int _id, wxAcceleratorEntry _key): id(_id), hotkey(_key) { }
int id;
wxAcceleratorEntry hotkey;
};
typedef std::vector<accelitem_t> data_t;
void noteProblemMenuItems(wxMenu *m);
static bool isProblemAccelerator(wxAcceleratorEntry *a);
wxMenuBar *mMenu;
data_t mData;
};
In a cpp file:
accel_t::accel_t(wxMenuBar *m, int *idlist, int idcount) {
reset(m, idlist, idcount);
}
void accel_t::reset(wxMenuBar *m, int *idlist, int idcount) {
mMenu = m;
mData.clear();
if (idlist == 0) {
for (int i = 0, ie = m->GetMenuCount(); i != ie; ++i)
noteProblemMenuItems(m->GetMenu(i));
} else {
if (idcount < 0) {
int *i = idlist;
while (*i != -1) ++i;
idcount = (i - idlist);
}
for (int *i = idlist, *ie = i + idcount; i != ie; ++i) {
wxMenuItem *item = mMenu->FindItem(*i);
if (item) {
wxAcceleratorEntry *a = item->GetAccel();
if (a != 0) mData.push_back(accelitem_t(*i, *a));
}
}
}
}
bool accel_t::isProblemAccelerator(wxAcceleratorEntry *a) {
if (a == 0) return false;
int flags = a->GetFlags(), keycode = a->GetKeyCode();
// Normal ASCII characters, when used with no modifier or Shift-only, would
// interfere with editing.
if ((flags == wxACCEL_NORMAL || flags == wxACCEL_SHIFT) &&
(keycode >= 32 && keycode < 127)) return true;
// Certain other values, when used as normal accelerators, could cause
// problems too.
if (flags == wxACCEL_NORMAL) {
if (keycode == WXK_RETURN ||
keycode == WXK_DELETE ||
keycode == WXK_BACK) return true;
}
return false;
}
void accel_t::noteProblemMenuItems(wxMenu *m) {
// Problem menu items have hotkeys that are ASCII characters with normal or
// shift-only modifiers.
for (size_t i = 0, ie = m->GetMenuItemCount(); i != ie; ++i) {
wxMenuItem *item = m->FindItemByPosition(i);
if (item->IsSubMenu())
noteProblemMenuItems(item->GetSubMenu());
else {
wxAcceleratorEntry *a = item->GetAccel();
if (isProblemAccelerator(a))
mData.push_back(accelitem_t(item->GetId(), *a));
}
}
}
void accel_t::restore() const {
if (mMenu == 0) return;
for (data_t::const_iterator i = mData.begin(), ie = mData.end(); i != ie;
++i)
{
wxMenuItem *item = mMenu->FindItem(i->id);
if (item) {
wxString text = item->GetItemLabel().BeforeFirst(wxT('\t'));
wxString hotkey = i->hotkey.ToString();
if (hotkey.empty()) {
// The wxWidgets authors apparently don't expect ASCII
// characters to be used for accelerators, because
// wxAcceleratorEntry::ToString just returns an empty string for
// them. This code deals with that.
int flags = i->hotkey.GetFlags(), key = i->hotkey.GetKeyCode();
if (flags == wxACCEL_SHIFT) hotkey = wx("Shift-") + wxChar(key);
else hotkey = wxChar(key);
}
item->SetItemLabel(text + '\t' + hotkey);
}
}
}
void accel_t::remove() const {
if (mMenu == 0) return;
for (data_t::const_iterator i = mData.begin(), ie = mData.end(); i != ie;
++i)
{
wxMenuItem *item = mMenu->FindItem(i->id);
if (item) {
wxString text = item->GetItemLabel().BeforeFirst(wxT('\t'));
item->SetItemLabel(text);
}
}
}
The easiest way to use it is to create your menu-bar as normal, with all accelerator keys (including the problematic ones) in place, then create an accel_t item from it something like this:
// mProblemAccelerators is an accel_t item in the private part of my frame class.
// This code is in the frame class's constructor.
wxMenuBar *menubar = _createMenuBar();
SetMenuBar(menubar);
mProblemAccelerators.reset(menubar);
It will identify and record the accelerator keys that pose problems. Finally, call the remove and restore functions as needed, to remove or restore the problematic accelerator keys. I'm calling them via messages passed to the frame whenever I open a window that needs to do standard editing.

Problem with SafeArray access using CComVariant

I have following block of code
/////////////////////////////////////
CComVariant newVal;
//pass the CComVariant and get the strings array!!!
GetStrList(newVal);
USES_CONVERSION;
if (((newVal.vt & VT_ARRAY) == VT_ARRAY) && ((newVal.vt & VT_BSTR) == VT_BSTR))
{
SAFEARRAY* paArray = newVal.parray;
BSTR * str = NULL;
SafeArrayAccessData(paArray, (void**)&str);
SafeArrayUnaccessData(paArray);
long lLBound = 0;
long lUBound = 0;
long nCount = 0;
if (FAILED(SafeArrayGetLBound(paArray, 1, &lLBound)) ||
FAILED(SafeArrayGetUBound(paArray, 1, &lUBound)))
{
ASSERT(false);
return FALSE;
}
nCount = ( lUBound - lLBound + 1 );
for (int i = 0 ; i < nCount ; i++)
{
m_cstrList.AddString(W2T(str[i]));
}
//SafeArrayDestroy(paArray); ---> is it required here???
}
/////////////////////////////////////
method returing the safe arrays
HRESULT GetStrList(VARIANT *pVal)
{
USES_CONVERSION;
if (!pVal)
return E_FAIL;
SAFEARRAYBOUND bound[1]; //single dimension array
bound[0].lLbound = 0;
bound[0].cElements = 10;
SAFEARRAY * A = SafeArrayCreate(VT_BSTR, 1, bound);
BSTR * str = NULL;
SafeArrayAccessData(A, (void**)&str);
//user wants the NT view OPC drivers list.
for (int i=0;i<10;i++)
{
str[i] = SysAllocString(T2W(mystrings[i]));
}
VariantInit(pVal);
pVal->vt = VT_ARRAY | VT_BSTR;
pVal->parray = A;
SafeArrayUnaccessData(A);
A = NULL;
return S_OK;
}
My doubt is, above first block of code has any memory leaks?
Does the CComVariant itself handle every thing about the cleaning?
or do i also manually do SafeArrayDestroy(paArray);
Thanks in Advance!
CComVariant destructor calls VariantClear() which frees whatever the variant was incapsulating, arrays included.
One caveat: the array should not be locked at the time when VariantClear() is called. This means that if an exception is thrown after SafeArrayAccessData() but before SafeArrayUnaccessData() the latter will not be called and VariantClear() will not free resources.
Therefore you better write a bracket class for pairing SafeArrayAccessData() and SafeArrayUnaccessData() calls.

Resources