Related
I've got an issue with getting the rows in the Recordset as it is really slow.
We've got an virtual ListCtrl where the data is retrieved and set in the "OnGetdispinfo" method.
This is pretty fast (~2 Seconds for 300k rows on localhost) however if the connection is slow the GUI becomes unrepsonsive and completly unusable until the job is finished.
So I've tried to do the Sql stuff in a different thread and updating the list once all data is fetched.
The issue with the unresponsive GUI is solved with that, but the time it takes to get all the data jumped from 2 seconds to several minutes.
Even if I dont do anything but loop through the rows (just calling MoveNext() in the loop until EOF is reached) it will still take over a minute to complete.
How do I resolve the issue with the freezing GUI without completly destroying the performance here?
I've included the relevant code below
m_pRecordset is a normal Recordset
Old:
void KundenListControlSQLCommand::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_pRecordset->IsBOF())
{
*pResult = 0;
return;
}
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem = &(pDispInfo)->item;
if (pItem->mask & LVIF_TEXT)
{
CString strData;
m_pRecordset->SetAbsolutePosition(pItem->iItem + 1);
if (getStatusRow() != pItem->iSubItem)
{
m_pRecordset->GetFieldValue(short(pItem->iSubItem), strData);
}
::lstrcpy(pItem->pszText, strData);
}
if (pItem->mask & LVIF_IMAGE)
{
int const nIndex = this->GetParent()->SendMessage(OT_VLC_ONGETIMAGEINDEX, pItem->iItem, 0);
if (0 != nIndex)
{
pItem->iImage = nIndex - 1;
}
}
*pResult = 0;
}
void KundenListControlSQLCommand::loadAndDisplayData()
{
ASSERT(!m_strSQLCommand.IsEmpty());
CWaitCursor wc;
try
{
if (!m_pDatabase->IsOpen())
{
CString strSQL = m_pDatabase->getDatabaseInfo().getConnectString();
m_pDatabase->OpenEx(strSQL);
}
// RecordCount ermitteln
m_nRecordCount = m_pRecordset->selectCount(_T("*"), m_strSQLCommand);
if (m_pRecordset->IsOpen())
m_pRecordset->Close();
m_pRecordset->Open(Recordset::snapshot, m_strSQLCommand + m_strSortOrder,
Recordset::executeDirect | Recordset::noDirtyFieldCheck |
Recordset::readOnly | Recordset::useBookmarks);
SetItemCountEx(m_nRecordCount);
}
catch (CDBException* e)
{
e->ReportError();
e->Delete();
}
}
New:
void KundenListControlSQLCommand::loadAndDisplayData()
{
ASSERT(!m_strSQLCommand.IsEmpty());
CWaitCursor wc;
try
{
if (!m_pDatabase->IsOpen())
{
CString strSQL = m_pDatabase->getDatabaseInfo().getConnectString();
m_pDatabase->OpenEx(strSQL);
}
// RecordCount ermitteln
m_nRecordCount = m_pRecordset->selectCount(_T("*"), m_strSQLCommand);
if (m_pRecordset->IsOpen())
m_pRecordset->Close();
m_pRecordset->Open(Recordset::dynaset, m_strSQLCommand + m_strSortOrder,
Recordset::executeDirect | Recordset::noDirtyFieldCheck |
Recordset::readOnly | Recordset::useBookmarks);
m_vResult.clear();
m_vResult.reserve(m_nRecordCount);
int nFieldCount = m_pRecordset->GetODBCFieldCount();
CString strData;
while (!m_pRecordset->IsEOF())
{
for (auto i = 0; i < nFieldCount; i++)
{
m_pRecordset->GetFieldValue(short(i), strData);
m_vResult.push_back(std::move(strData));
}
if (m_bAbort)
{
m_bAbort = false;
return;
}
m_pRecordset->MoveNext();
}
GetParent()->SendMessage(OT_VLC_ON_LIST_DONE, NULL, NULL);
}
catch (CDBException* e)
{
e->ReportError();
e->Delete();
}
}
void KundenListControlSQLCommand::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_pRecordset->IsBOF())
{
*pResult = 0;
return;
}
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem = &(pDispInfo)->item;
UINT nItem = (pItem->iItem * m_pRecordset->GetODBCFieldCount()) + pItem->iSubItem;
if (pItem->mask & LVIF_TEXT && m_vResult.size() >= nItem)
{
::lstrcpy(pItem->pszText, std::move(m_vResult.at(nItem)));
}
if (pItem->mask & LVIF_IMAGE)
{
int const nIndex = this->GetParent()->SendMessage(OT_VLC_ONGETIMAGEINDEX, pItem->iItem, 0);
if (0 != nIndex)
{
pItem->iImage = nIndex - 1;
}
}
*pResult = 0;
}``
As I can see in your code, you read the data and place them into the vector. In such a setting, I think you don't really need a dynaset recordset, which according to the documentation is "A recordset with bi-directional scrolling". It fetches data row-by-row, which may be what makes the process slow. Also, "changes made by other users to the data values are visible following a fetch operation", but I think this is not of critical importance in this case. It would be mostly useful for displaying more "live" data, that are updated often.
Instead, a snapshot, or even forwardOnly recordset would suffice and would be faster. You can also experiment with the CRecordset::useMultiRowFetch option. The documentation says it's faster. It requires some changes to your code (moving next etc). Take a look here: Recordset: Fetching Records in Bulk (ODBC).
An alternative, radically different implementation would be to use bookmarks instead. Loading would be a lot faster, but scrolling somewhat sluggish, as you will have to fetch data in the OnGetdispinfo() function.
Finally a tip, if you are using the MS-SQL server, check the native driver, if you haven't already, many on the i-net claim that it's considerably faster.
I don't know much about ODBC, but suspect that there are better way to get bulk data.
Regardless, you do a lot of unnecessary copying of your vectors. Two easy fixes:
Right after m_vResult.clear();, resize your m_vResult to the number of records.
Instead of m_vResult.push_back(vResult); do m_vResult.push_back(std::move(vResult));, as you don't need your vResult after that.
Another solution is to do a cache list, handling LVN_ODCACHEHINT notification (this example is for CListView but you can adapt it on your CListCtrl:
// header.h
class CYourListView : public CListView
{
// ...
afx_msg void OnLvnOdcachehint(NMHDR* pNMHDR, LRESULT *pResult);
};
and implementation:
// YourListView.cpp
// ...
ON_NOTIFY_REFLECT(LVN_ODCACHEHINT, &CYourListView::OnLvnOdcachehint)
END_MESSAGE_MAP()
void CYourListView::OnLvnOdcachehint(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLVCACHEHINT pCacheHint = reinterpret_cast<LPNMLVCACHEHINT>(pNMHDR);
const DWORD dwTo = pCacheHint->iTo;
const DWORD dwFetched = m_vResult.size();
if (dwTo >= dwFetched) // new rows must be fetched
{
const DWORD dwColCount = m_pRecordset->GetColumnCount();
m_vResult.resize(dwTo + 1);
for (DWORD dwRow = dwFetched; dwRow <= dwTo; ++dwRow)
{
CDBRecord* pRecord = new CDBRecord;
pRecord->SetSize(dwColCount);
for (DWORD dwCol = 1; dwCol <= dwColCount; dwCol++)
{
CDBValue* pDBValue = new CDBValue(m_pRecordset, dwCol);
pRecord->SetAt(dwCol - 1, pDBValue);
}
m_vResult.emplace(m_vResult.begin() + dwRow, pRecord);
m_pRecordset->MoveNext();
}
}
*pResult = 0;
}
might be need to adjust some variables / values with your certain situation.
I've got a huge PDF file with more than 100 pages and I want to separate them to single PDF files (containing only one page each). Problem is, that PoDoFo does not copy just the page, but the whole document because of the references (and so each of the 100 PDF files have same size as the 100-page PDF). A relevant mailing list post can be found, unfortunately there is no solution provided.
In source code of function InsertPages there is explanation:
This function works a bit different than one might expect.
Rather than copying one page at a time - we copy the ENTIRE document
and then delete the pages we aren't interested in.
We do this because
1) SIGNIFICANTLY simplifies the process
2) Guarantees that shared objects aren't copied multiple times
3) offers MUCH faster performance for the common cases
HOWEVER: because PoDoFo doesn't currently do any sort of "object
garbage collection" during a Write() - we will end up with larger
documents, since the data from unused pages will also be in there.
I have tried few methods to copy only relevant objects, but each of them failed.
Copy all pages and remove irrelevant ones
Use XObject wrapping: FillXObjectFromDocumentPage and FillXObjectFromExistingPage
Copy object by object
Use RenumberObjects with bDoGarbageCollection = true
but none of them worked out. Does anybody have an idea or working solution for this problem?
The only solution is to use another PDF library. Or wait for garbage collection to be implemented.
The problem is stated in the quote you mentioned:
> during a Write() - we will end up with larger documents, since the
> data from unused pages will also be in there.
This means podofo always puts the entire PDF content in your file, no matter what. The whole PDF is there, you just don't see parts of it.
Dennis from the podofo support sent me an working example of optimized version of InsertPages function which is actually fixing page references and decreases document size significantly!
void PdfMemDocument::InsertPages2(const PdfMemDocument & rDoc, std::vector<int> pageNumbers)
{
std::unordered_set<PdfObject*> totalSet;
std::vector<pdf_objnum> oldObjNumPages;
std::unordered_map<pdf_objnum, pdf_objnum> oldObjNumToNewObjNum;
std::vector<PdfObject*> newPageObjects;
// Collect all dependencies from all pages that are to be copied
for (int i = 0; i < pageNumbers.size(); ++i) {
PdfPage* page = rDoc.GetPage(pageNumbers[i]);
if (page) {
oldObjNumPages.push_back(page->GetObject()->Reference().ObjectNumber());
std::unordered_set<PdfObject*> *set = page->GetPageDependencies();
totalSet.insert(set->begin(), set->end());
delete set;
}
}
// Create a new page object for every copied page from the old document
// Copy all objects the pages depend on to the new document
for (auto it = totalSet.begin(); it != totalSet.end(); ++it) {
unsigned int length = static_cast<unsigned int>(GetObjects().GetSize() + GetObjects().GetFreeObjects().size());
PdfReference ref(static_cast<unsigned int>(length+1), 0);
PdfObject* pObj = new PdfObject(ref, *(*it));
pObj->SetOwner(&(GetObjects()));
if ((*it)->HasStream()) {
PdfStream *stream = (*it)->GetStream();
pdf_long length;
char* buf;
stream->GetCopy(&buf, &length);
PdfMemoryInputStream inputStream(buf, length);
pObj->GetStream()->SetRawData(&inputStream, length);
free(buf);
}
oldObjNumToNewObjNum.insert(std::pair<pdf_objnum, pdf_objnum>((*it)->Reference().ObjectNumber(), length+1));
GetObjects().push_back(pObj);
newPageObjects.push_back(pObj);
}
// In all copied objects, fix the object numbers so they are valid in the new document
for (auto it = newPageObjects.begin(); it != newPageObjects.end(); ++it) {
FixPageReferences(GetObjects(), *it, oldObjNumToNewObjNum);
}
// Insert the copied pages into the pages tree
for (auto it = oldObjNumPages.begin(); it != oldObjNumPages.end(); ++it) {
PdfObject* pageObject = GetObjects().GetObject(PdfReference(oldObjNumToNewObjNum[(*it)], 0));
PdfPage *page = new PdfPage(pageObject, std::deque<PdfObject*>());
GetPagesTree()->InsertPage(GetPageCount() - 1, page);
}
}
std::unordered_set<PdfObject *>* PdfPage::GetPageDependencies() const
{
std::unordered_set<PdfObject *> *set = new std::unordered_set<PdfObject *>();
const PdfObject* pageObj = GetObject();
if (pageObj) {
PdfVecObjects* objects = pageObj->GetOwner();
if (objects) {
set->insert((PdfObject*)pageObj);
objects->GetObjectDependencies2(pageObj, *set);
}
}
return set;
}
// Optimized version of PdfVecObjects::GetObjectDependencies
void PdfVecObjects::GetObjectDependencies2(const PdfObject* pObj, std::unordered_set<PdfObject*> &refMap) const
{
// Check objects referenced from this object
if (pObj->IsReference())
{
PdfObject* referencedObject = GetObject(pObj->GetReference());
if (referencedObject != NULL && refMap.count(referencedObject) < 1) {
(refMap).insert((PdfObject *)referencedObject); // Insert referenced object
GetObjectDependencies2((const PdfObject*)referencedObject, refMap);
}
}
else {
// Recursion
if (pObj->IsArray())
{
PdfArray::const_iterator itArray = pObj->GetArray().begin();
while (itArray != pObj->GetArray().end())
{
GetObjectDependencies2(&(*itArray), refMap);
++itArray;
}
}
else if (pObj->IsDictionary())
{
TCIKeyMap itKeys = pObj->GetDictionary().GetKeys().begin();
while (itKeys != pObj->GetDictionary().GetKeys().end())
{
if ((*itKeys).first != PdfName("Parent")) {
GetObjectDependencies2((*itKeys).second, refMap);
}
++itKeys;
}
}
}
}
void FixPageReferences(PdfVecObjects& objects, PdfObject* pObject, std::unordered_map<pdf_objnum, pdf_objnum>& oldNumToNewNum) {
if( !pObject)
{
PODOFO_RAISE_ERROR( ePdfError_InvalidHandle );
}
if( pObject->IsDictionary() )
{
TKeyMap::iterator it = pObject->GetDictionary().GetKeys().begin();
while( it != pObject->GetDictionary().GetKeys().end() )
{
if ((*it).first != PdfName("Parent")) {
FixPageReferences(objects, (*it).second, oldNumToNewNum);
}
++it;
}
}
else if( pObject->IsArray() )
{
PdfArray::iterator it = pObject->GetArray().begin();
while( it != pObject->GetArray().end() )
{
FixPageReferences(objects, &(*it), oldNumToNewNum),
++it;
}
}
else if( pObject->IsReference() )
{
//PdfObject* referencedObj = objects.GetObject(pObject->GetReference());
pdf_objnum oldnum = pObject->GetReference().ObjectNumber();
pdf_objnum newnum = oldNumToNewNum[oldnum];
if (!newnum) throw new std::exception("No new object number for old object number");
*pObject = PdfReference(newnum, 0);
}
}
How to correct return created std::list through function argument? Now, I try so:
bool DatabaseHandler::tags(std::list<Tag> *tags)
{
QString sql = "SELECT * FROM " + Tag::TABLE_NAME + ";";
QSqlQueryModel model;
model.setQuery(sql);
if(model.lastError().type() != QSqlError::NoError) {
log(sql);
tags = NULL;
return false;
}
const int count = model.rowCount();
if(count > 0)
tags = new std::list<Tag>(count);
else
tags = new std::list<Tag>();
//some code
return true;
}
After I can use it:
std::list<Tag> tags;
mDB->tags(&tags);
Now, I fix my function:
bool DatabaseHandler::tags(std::list<Tag> **tags)
{
QString sql = "SELECT * FROM " + Tag::TABLE_NAME + ";";
QSqlQueryModel model;
model.setQuery(sql);
if(model.lastError().type() != QSqlError::NoError) {
log(sql);
*tags = NULL;
return false;
}
const int count = model.rowCount();
if(count > 0)
*tags = new std::list<Tag>(count);
else
*tags = new std::list<Tag>();
for(int i = 0; i < count; ++i) {
auto record = model.record(i);
Tag tag(record.value(Table::KEY_ID).toInt());
(*tags)->push_back(tag);
}
return true;
}
It works but list return size 4 although loop executes only 2 iterations and empty child objects (if I just called their default constructor). The Tag class hasn't copy constructor.
Since you passed an already instantiated list as a pointer to the function, there is no need to create another list.
In that sense, you question is pretty unclear. I'd suggest you read up a bit on pointers, references and function calls in general.
http://www.cplusplus.com/doc/tutorial/pointers/
http://www.cplusplus.com/doc/tutorial/functions/
UPDATE: I still strongly suggest you read up on the mentioned topics, since you don't know these fundamental points.
Anyway, this is what you probably want to do (event though I would suggest using references, here is the solution with pointers):
bool someFunc(std::list<Tag> **tags) {
// by default null the output argument
*tags = nullptr;
if (error) {
return false;
}
// dereference tags and assign it the address to a new instance of list<Tag>
*tags = new std::list<Tag>();
return true
}
std::list<Tag> *yourList;
if (someFunc(&yourList)) {
// then yourList is valid
} else {
// then you had an error and yourList == nullptr
}
However, this is not idiomatic C++. Please read a modern book or tutorial.
Use a reference.
bool DatabaseHandler::tags(std::list<Tag>& tags);
std::list<Tag> tags;
mDB->tags(tags);
You'll have to change all the -> to ., of course. Every operation done on the reference in the function will be done to the original tags list it was called with.
EDIT: If you want to create the list inside the function and return it, you have a couple options. The closest, I think, is to just return a list pointer, and return nullptr if the function fails.
//beware, pseudocode ahead
std::list<Tag>* DatabaseHandler::tags() //return new list
{
if (success)
return new std::list<Tag>(...); //construct with whatever
else
return nullptr; //null pointer return, didn't work
}
std::list<Tag> tags* = mDB->tags();
You could alternatively have it return an empty list instead, depending on how you want it to work. Taking a reference to a pointer would work the same way, too.
bool DatabaseHandler::tags(std::list<Tag>*&); //return true/false
std::list<Tag>* tags;
mDB->tags(tags); //tags will be set to point to a list if it worked
This meight be a long question but i hope someone can help me figuring out whats going wrong.
I am inserting a JSON Object into already allocated Memory with my own Datatype which basically holds a Union with Data and a ptrdiff_t to the next Datatype in 8bit steps.
template <typename T>
class BaseType
{
public:
BaseType();
explicit BaseType(T& t);
explicit BaseType(const T& t);
~BaseType();
inline void setNext(const ptrdiff_t& next);
inline std::ptrdiff_t getNext();
inline void setData(T& t);
inline void setData(const T& t);
inline T getData() const;
protected:
union DataUnion
{
T data;
::std::ptrdiff_t size;
DataUnion()
{
memset(this, 0, sizeof(DataUnion));
} //init with 0
explicit DataUnion(T& t);
explicit DataUnion(const T& t);
} m_data;
long long m_next;
};
The implementation is streight so nothing special happes there just setting/getting the values of the definition. (i'll skip the impl. here)
So here starts the code where something goes wrong:
std::pair<void*, void*> Page::insertObject(const rapidjson::GenericValue<rapidjson::UTF8<>>& value,
BaseType<size_t>* last)
{
//return ptr to the first element
void* l_ret = nullptr;
//prev element ptr
BaseType<size_t>* l_prev = last;
//position pointer
void* l_pos = nullptr;
//get the members
for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it)
{
switch (it->value.GetType())
{
case rapidjson::kNullType:
LOG_WARN << "null type: " << it->name.GetString();
continue;
case rapidjson::kFalseType:
case rapidjson::kTrueType:
{
l_pos = find(sizeof(BaseType<bool>));
void* l_new = new (l_pos) BaseType<bool>(it->value.GetBool());
if (l_prev != nullptr)
l_prev->setNext(dist(l_prev, l_new));
}
break;
case rapidjson::kObjectType:
{
//pos for the obj id
//and insert the ID of the obj
l_pos = find(sizeof(BaseType<size_t>));
std::string name = it->name.GetString();
void* l_new = new (l_pos) BaseType<size_t>(common::FNVHash()(name));
if (l_prev != nullptr)
l_prev->setNext(dist(l_prev, l_new));
//TODO something strange happens here!
// pass the objid Object to the insertobj!
// now recursive insert the obj
// the second contains the last element inserted
// l_pos current contains the last inserted element and get set to the
// last element of the obj we insert
l_pos = (insertObject(it->value, reinterpret_cast<BaseType<size_t>*>(l_new)).second);
}
break;
case rapidjson::kArrayType:
{//skip this at the moment till the bug is fixed
}
break;
case rapidjson::kStringType:
{
// find pos where the string fits
// somehow we get here sometimes and it does not fit!
// which cant be since we lock the whole page
l_pos = find(sizeof(StringType) + strlen(it->value.GetString()));
//add the String Type at the pos of the FreeType
auto* l_new = new (l_pos) StringType(it->value.GetString());
if (l_prev != nullptr)
l_prev->setNext(dist(l_prev, l_new));
}
break;
case rapidjson::kNumberType:
{
//doesnt matter since long long and double are equal on x64
//find pos where the string fits
l_pos = find(sizeof(BaseType<long long>));
void* l_new;
if (it->value.IsInt())
{
//insert INT
l_new = new (l_pos) BaseType<long long>(it->value.GetInt64());
}
else
{
//INSERT DOUBLE
l_new = new (l_pos) BaseType<double>(it->value.GetDouble());
}
if (l_prev != nullptr)
l_prev->setNext(dist(l_prev, l_new));
}
break;
default:
LOG_WARN << "Unknown member Type: " << it->name.GetString() << ":" << it->value.GetType();
continue;
}
//so first element is set now, store it to return it.
if(l_ret == nullptr)
{
l_ret = l_pos;
}
//prev is the l_pos now so cast it to this;
l_prev = reinterpret_cast<BaseType<size_t>*>(l_pos);
}
//if we get here its in!
return{ l_ret, l_pos };
}
I am starting to insert like this:
auto firstElementPos = insertObject(value.MemberBegin()->value, nullptr).first;
While value.MemberBegin()->value is Object to be inserted and ->name holds the Name of the object. In the case below its Person and everything between {}.
The problem is, if i insert a JSON Object which has one Object inside like so:
"Person":
{
"age":25,
"double": 23.23,
"boolean": true,
"double2": 23.23,
"firstInnerObj":{
"innerDoub": 12.12
}
}
It works properly and i can reproduce the Object. But if i have more inner objects like so:
"Person":
{
"age":25,
"double": 23.23,
"boolean": true,
"double2": 23.23,
"firstInnerObj":{
"innerDoub": 12.12
},
"secondInnerObj":{
"secInnerDoub": 12.12
}
}
It fails and i lose data so i think that my recursion goes wrong but i dont see why. If you need any more informations let me know. Meight take a look here and the client here.
The test.json need to contain a json object like above. And the find only need to contain {"oid__":2} to get the second object that was inserted.
I could track the issue down to the Point where i recreate the Object recursively in the code. Some of the Nextpointers seem to be incorrect:
void* Page::buildObject(const size_t& hash, void* start, rapidjson::Value& l_obj,
rapidjson::MemoryPoolAllocator<>& aloc)
{
//get the meta information of the object type
//to build it
auto& l_metaIdx = meta::MetaIndex::getInstance();
//get the meta dataset
auto& l_meta = l_metaIdx[hash];
//now we are already in an object here with l_obj!
auto l_ptr = start;
for (auto it = l_meta->begin(); it != l_meta->end(); ++it)
{
//create the name value
rapidjson::Value l_name(it->name.c_str(), it->name.length(), aloc);
//create the value we are going to add
rapidjson::Value l_value;
//now start building it up again
switch (it->type)
{
case meta::OBJECT:
{
auto l_data = static_cast<BaseType<size_t>*>(l_ptr);
//get the hash to optain the metadata
auto l_hash = l_data->getData();
//set to object and create the inner object
l_value.SetObject();
//get the start pointer which is the "next" element
//and call recursive
l_ptr = static_cast<BaseType<size_t>*>(buildObject(l_hash,
(reinterpret_cast<char*>(l_data) + l_data->getNext()), l_value, aloc));
}
break;
case meta::ARRAY:
{
l_value.SetArray();
auto l_data = static_cast<ArrayType*>(l_ptr);
//get the hash to optain the metadata
auto l_size = l_data->size();
l_ptr = buildArray(l_size, static_cast<char*>(l_ptr) + l_data->getNext(), l_value, aloc);
}
break;
case meta::INT:
{
//create the data
auto l_data = static_cast<BaseType<long long>*>(l_ptr);
//with length attribute it's faster ;)
l_value = l_data->getData();
}
break;
case meta::DOUBLE:
{
//create the data
auto l_data = static_cast<BaseType<double>*>(l_ptr);
//with length attribute it's faster ;)
l_value = l_data->getData();
}
break;
case meta::STRING:
{
//create the data
auto l_data = static_cast<StringType*>(l_ptr);
//with length attribute it's faster
l_value.SetString(l_data->getString()->c_str(), l_data->getString()->length(), aloc);
}
break;
case meta::BOOL:
{
//create the data
auto l_data = static_cast<BaseType<bool>*>(l_ptr);
l_value = l_data->getData();
}
break;
default:
break;
}
l_obj.AddMember(l_name, l_value, aloc);
//update the lptr
l_ptr = static_cast<char*>(l_ptr) + static_cast<BaseType<size_t>*>(l_ptr)->getNext();
}
//return the l_ptr which current shows to the next lement. //see line above
return l_ptr;
}
After houers and houres of debugging i found the small issue which causes this. The method which builds up the Object after it was inserted returns a pointer to the actuall last element->next which was inserted and after the switch case i did call the ->next again which causes a loss of data because it scipped one element in the single chained list.
The Fix to this is to put the line
l_ptr = static_cast<char*>(l_ptr) + static_cast<BaseType<size_t>*>(l_ptr)->getNext();
Only into the switch cases where it is not an Object or Array. Fix Commit This actually also gave me the fix for an Issue with inserting Array.
Of cause the real issue could not know someone here who did not took a deep look into the code but i still want to show the fix here. Thanks to #sehe who helped alot with figuring out whats going wrong here.
The following code works in a the Marmalade simulator (I'm on OSX using x-code)
bool PictureDictionary::OnTableSelect(CTable* table, int tab){
//if something is selected, look up the item, and display it
//also change the search to the selected item
if(-1 < tab){
// if a term is selected, set the search text field to the term
CString term = m_SearchResults.GetString(tab);
if(m_currentWord != (char*)term.Get()){
m_currentWord = (char *)term.Get();
m_searchTextField->SetAttribute("text", term);
char* normalizedTerm = (char *)term.Get();
char* imagePath;
sprintf(imagePath,"images/%s.jpg", normalizedTerm);
if(m_ImageAttached){
m_Image->SetAttribute("image", (const char*)imagePath);
} else {
m_Image = CreateImage(CAttributes()
.Set("name", "picture")
.Set("x1", "0")
.Set("x2", "0")
.Set("y1", "50%")
.Set("image", (const char*)imagePath)
);
m_SearchView->AddChild(m_Image);
m_ImageAttached = true;
}
}
}
return true;
}
When I run the simulator, and select an item from the table, the image appears, and changes when I select a different item. When I go to refactor, I get a EXC_BAD_ACCESS (code=1…..) Error
bool PictureDictionary::OnTableSelect(CTable* table, int tab){
//if something is selected, look up the item, and display it
//also change the search to the selected item
if(-1 < tab){
// if a term is selected, set the search text field to the term
CString term = m_SearchResults.GetString(tab);
if(m_currentWord != (char*)term.Get()){
m_currentWord = (char *)term.Get();
m_searchTextField->SetAttribute("text", term);
char* normalizedTerm = (char *)term.Get();
char* imagePath;
sprintf(imagePath,"images/%s.jpg", normalizedTerm);
UpdatePictureView(imagePath);
}
}
return true;
}
void PictureDictionary::UpdatePictureView(char* imagePath){
if(m_ImageAttached){
m_Image->SetAttribute("image", (const char*)imagePath);
} else {
m_Image = CreateImage(CAttributes()
.Set("name", "picture")
.Set("x1", "0")
.Set("x2", "0")
.Set("y1", "50%")
.Set("image", (const char*)imagePath)
);
m_SearchView->AddChild(m_Image);
m_ImageAttached = true;
}
}
Any suggestions on how to clean up the code without getting these issues?
Edit RE Comments about uninitialized variables:
m_ImageAttached was initialized to false in the constructor, unless I'm doing something wrong. Also, changing the condition to check if m_Image!=NULL also throws the same error.
main.cpp:
PictureDictionary pictDict(myApp, &dictionary);
Constructor for PictureDictionary:
PictureDictionary::PictureDictionary(CAppPtr app,Dictionary::Dictionary* dictionary){
m_App = app;
m_Dictionary = dictionary;
m_currentWord = "";
m_ImageAttached = false;
}
imagePath is an unitialized pointer, in both snippets. Any attempt to dereference is undefined behaviour. It just appeared to work in the first snippet. Use an array or populate a std::string instead:
std::string imagePath(std::string("images/") + normalizedTerm + ".jpg");
And use std::string::c_str() if access to the underlying const char* is required.