Тема: Не могу понять в чем ошибка при получении данных из AutoCAD

Вот такой вот кусок программного кода:

void ReadingDWGBloks(void)
{
    //Подключение к таблице блоков
    AcDbBlockTable *pBlockTable;
    if (
            (acdbHostApplicationServices()->workingDatabase()->getBlockTable(pBlockTable,AcDb::kForRead)!=Acad::eOk)
        ||    (!pBlockTable)    
    ){
        acutPrintf("Не могу получить доступ к таблице блоков\n");
        return;
    }
    //Создание итератора
    AcDbBlockTableIterator *pBlockTableIterator=NULL;
    if (
            (pBlockTable->newIterator(pBlockTableIterator, true)!=Acad::eOk)
        ||    (!pBlockTableIterator)
    ){
        acutPrintf("Ошибка создания итератора блоков: не хватает памяти\n");
        return;
    }
    //Получение первой записи в таблице
    AcDbBlockTableRecord *pBlockTableRecord=NULL;
    if (
            (pBlockTableIterator->getRecord(pBlockTableRecord,AcDb::kForRead)!=Acad::eOk)
        ||    (!pBlockTableRecord)
    ){
        acutPrintf("Не могу получить запись таблицы через итератор блоков\n");
        return;
    }
    //Печать списка блоков
    acutPrintf("Блоки текущего документа:\n");
    char *BlockName;
    for(;!pBlockTableIterator->done();pBlockTableIterator->step())
    {
        pBlockTableIterator->getRecord(pBlockTableRecord,AcDb::kForRead);
        pBlockTableRecord->getName(BlockName);
        acutPrintf("Блок %s:\n", BlockName);
        ReadingBlockReference(pBlockTableRecord);
        ReadingBlockRecordEntity(pBlockTableRecord);
        pBlockTableRecord->close();
    }
    //Завершение работы закрываем открытые таблицы и удаляем итератор
    delete pBlockTableIterator;
    pBlockTable->close();
}
void ReadingBlockReference(AcDbBlockTableRecord *pBlockTableRecord)
{
    acutPrintf("Получаем Reference блока\n");
    //Получаем содержимое блока
    AcDbObjectIdArray pObjectIdArray;
    if (
            pBlockTableRecord->getBlockReferenceIds(pObjectIdArray)!=Acad::eOk
    ){
            acutPrintf("Не могу получить доступ к содержимому блока\n");
            return;
    }
    //Просматриваем все содержимое объекта
    
    AcDbBlockReference *pBlockReference;
    AcGePoint3d BlockPosition;
    for ( int i=0; i<pObjectIdArray.length(); ++i)
    {
        //Для каждого конкретного содержимого просматриваем из чего оно состоит
        if (
                (acdbOpenObject(pBlockReference, pObjectIdArray.at(i), AcDb::kForWrite) == Acad::eOk)
            || (!pBlockReference)
        ){
            if (pBlockReference->treatAsAcDbBlockRefForExplode()==Adesk::kFalse  )
            {
                BlockPosition=pBlockReference->position();
                acutPrintf("Позиция: (%f,%f,%f)\n", BlockPosition[0], BlockPosition[1], BlockPosition[2]);
            }
            pBlockReference->close();
        }
    }
    //delete pObjectIdArray;
}
void ReadingBlockRecordEntity(AcDbBlockTableRecord *pBlockTableRecord)
{
    acutPrintf("Получаем Entity блока\n");
    //Создание итератора
    AcDbBlockTableRecordIterator *pBlockTableRecordIterator=NULL;
    if (
            (pBlockTableRecord->newIterator(pBlockTableRecordIterator, true)!=Acad::eOk)
        ||    (!pBlockTableRecordIterator)
    ){
        acutPrintf("Ошибка создания итератора блоков: не хватает памяти\n");
        return;
    }
    //Получение первой записи в таблице
    AcDbEntity *pEntity=NULL;
    if (
            (pBlockTableRecordIterator->getEntity(pEntity,AcDb::kForRead)!=Acad::eOk)
        ||    (!pEntity)
    ){
        acutPrintf("Не могу получить запись таблицы через итератор блоков\n");
        return;
    }
    //Печать списка блоков
    acutPrintf("Содержимое блока текущего документа:\n");
    AcDbText *pText=NULL;
    AcGePoint3d TextPosition;
    for(;!pBlockTableRecordIterator->done();pBlockTableRecordIterator->step())
    {
        if (
                (pBlockTableRecordIterator->getEntity(pEntity,AcDb::kForRead)==Acad::eOk)
            || !(!pEntity)
        ){
            //AcDbHandle objHandle;
            //pEntity->getAcDbHandle(objHandle);
            //char handleStr[20];
            //objHandle.getIntoAsciiBuffer(handleStr);
        
            if(
                    (!strcmp(pEntity->isA()->name(),"AcDbText"))
            //    ||    (!strcmp(pEntity->isA()->name(),"AcDbAttribute"))
            ){
                if ((pText = AcDbText::cast(pEntity))!= NULL) {
                // Можно считывать текстовую информацию из pText:
                    TextPosition=pText->position();
                    acutPrintf("Text: %s positon(%f,%f,%f)\n", pText->textString(), TextPosition[0], TextPosition[1], TextPosition[2]);
            }
                pText->close();
            }
            pEntity->close();
        }
    }
    //Завершение работы закрываем открытые таблицы и удаляем итератор
    delete pBlockTableRecordIterator;
}

Если я вызываю обе функции (ReadingBlockReference и ReadingBlockRecordEntity), то автокад вылетает. Если же только одну, то все нормально. Может я где-то теряю память или, наоборот, закрываю, что-то лишнее?
Где-то читал, что автокад может глючить при работе с char*. Может из-за этого очень много памяти теряется?

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

Ищи ошибки в своей программе - до глюков AutoCAD ты еще не добрался smile
Первое, что я увидел - это двойное закрытие объекта - pText->close(); и pEntity->close(); Я уже писал, что они ссылаются на один объект. Второе - если тебе нужно получить информацию - никогда не открывай примитив с AcDb::kForWrite - только AcDb::kForRead и как только он становится не нужен, немедленно закрывай его. Большинство ошибок связано именно с этим. Когда ты создал итератор для pBlockTable сразу же вызывай pBlockTable->close(); - она тебе больше не нужна(!!!)
И т.д. и т.п. Удачи!

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

Спасибо.
Я сам не заментил, что открывал один из объектов на запись. Помоему моя самая главная ошибка была в этом.
А по поводу работы с памятью, так это ещё разбираться и разбираться, т.к. расширение для меня новое.

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

А зачем вот это надо я вообще не понял - первую запись зачем-то(зачем???) получаем и где-же мы ее закрываем???
//Получение первой записи в таблице
    AcDbEntity *pEntity=NULL;
  if (
      (pBlockTableRecordIterator->getEntity(pEntity,AcDb::kForRead)!=Acad::eOk)
    ||  (!pEntity)
  ){
    acutPrintf("Не могу получить запись таблицы через итератор блоков\n");
    return;
  }

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

И еще как препод по С++ скажу
За конструкции подобного типа:
if ( (pBlockTableRecordIterator->getEntity(pEntity,AcDb::kForRead)==Acad::eOk)
      || !(!pEntity)
    )
if(
          (!strcmp(pEntity->isA()->name(),"AcDbText"))
      //  ||  (!strcmp(pEntity->isA()->name(),"AcDbAttribute"))
      ){
рву на части... (не принимать близко к сердцу)
Вместо:
if ( (point.x > rect.x )&&(point.y > rect.y)&&(point.x < rect.x + rect.width)&&(point.y < rect.y + rect.height) )
можно написать проще:
if ( isInsideRect(rect, point) ) {...}
Во-первых глазам приятней, во-вторых мозг лучше понимает что за х... написана, в-третьих ошибки легче искать, в-четвертых можно вторично использовать isInsideRect, а не писать констукцию заново...
Повторю нехитрую истину, что пусть будет 100 функций с хорошими именами по 10 строк в каждой, чем 10 функций по 100 строк...

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

> KonstantinM
Это уже давно устаревший код. Писалось так, чтобы быстрее разобраться с возможностями ObjectARX и методом работы с ним.
А что не понравилось в такой конструкции:

if ( (pBlockTableRecordIterator->getEntity(pEntity,AcDb::kForRead)==Acad::eOk)
|| !(!pEntity)
)

Тут правда ещё не хватает нескольких Tab-ов, но в общем-то всё вроде нормально. Для меня даже более читабельно, чем в одну строчку.
Может это на самом деле можно написать более красиво, тогда подскажите как...

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

> Pharaon

!(!pEntity)

Это у меня в свое время вызвало ехидную улыбку... biggrin

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

> Александр Ривилис
Во, вот с этим точно стормозил...

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

Во-первых дело в сложности конструкций стоящих в if - е.
if ( open...() == Acad::eOk )
еще нормально, а если ставить много конструкций, то от || и && в глазах рябит - к тому же очень часто ошибаются что поставить.
Во-вторых. Есть такая специфика у if-ов, что условия могут не всегда выполниться.
if ( (x > 0)&&(doFunction()) )
т.е. если x <= 0, то doFunction - не БУДЕТ вызвана. Поэтому я настоятельно рекомендую не использовать || и && в if если в прям в условие входит вызов хотябы одной функции.
В-третьих. Если есть возвращаемый параметр и код ошибки раздельно, при этом есть зависимость между возвр. параметром и кодом ошибки (типа если eOk, то pEntity != NULL) в этом случае надо использовать assert-ы. (Только не assert стандартный, а свой который работает одинаково и в дебаге и в релизе - почему рассказывать не буду). ASSERT - это логический тупик для конечного автомата. Если делать return в критической ситуации - то это нарушение принципа конечного автомата - а что делать тому кто сверху если функция снизу не прошла хотя должна была пройти? Все эти критический ситуации не обработать, поэтому лучше ставить ASSERT на те ветки которые Вы не можете сейчас обработать и не знаете что делать если это событие случилось - но не в коем случае не return.
pEntity = NULL;
if ( open...(pEntity) == Acad::eOK )
{
MYASSERT( pEntity != NULL );
}
И с массивом strcmp
if ( isTextEntity(pEntity) )
{
...
}
не надо думать что такое isA name() strcmp - все это барахло можно вынести в отдельный метод и не сорить в if. Хотя для тестового кода можно и посорить...
И еще лучше сразу писать так
AcDbBlockTableRecordIterator *pBtrItr = NULL;
pBtr->newIterator(pBtrItr);
MYASSERT( pBtrItr != NULL  );
AcDbEntity *pEnt;
for (pBtrItr->start(); !pBtrItr->done(); pBtrItr->step())
{
   pEnt = NULL;
   pBtrItr->getEntity(pEnt, AcDb::kForRead);
   MYASSERT( pEnt != NULL )
   try
   {
     doOpsWithEntity(pEnt);
   }
   catch(...)
   {
     ERROR_REPORT("...");
   }
   pEnt->close();
}
даже в тестовом коде Вы будете сразу получать эксепшены и ассерты в лоб, закрывать объекты в исключительных ситуациях. Причем макрос MYASSERT должен показать место где сломалось и выражение и кинуть exception, который если надо с верху перехватят.
P.S. Обидно что уже некому учить меня :( А это всегда так полезно.

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

> KonstantinM
>Обидно что уже некому учить меня :(
Ну что ж, попробую smile
Все эти ASSERT-ы малость устарели. Гораздо более эффективно и эстетично использовать комбинацию интеллектуальных указателей и исключений. У меня последний код будет выглядеть примерно так:

arxkit::ErroStatusCheck es;
arxkit::utility::AcDbBlockTableRecordIteratorPtr pBtrItr;
es = pBtr->newIterator(GetImplRef(pBtrItr));
for (pBtrItr->start(); !pBtrItr->done(); pBtrItr->step())
{
    try  {
        arxkit::utility::AcDbEntityPtr pEnt;
        es = pBtrItr->getEntity(GetImplRef(pEnt), AcDb::kForRead);
        doOpsWithEntity(pEnt);
    }
    catch(arxkit::AcadError& err) {
        std::cerr << err << std::endl;
    }
}

Здесь, arxkit::utility::AcDbBlockTableRecordIteratorPtr - класс интеллектуального указателя (вообще говоря, шаблон), в деструкторе происходит удаление объекта, а при попытке обратиться к нулевому указателю возбуждается исключение, которое может быть перехвачено на более высоком уровне.
arxkit::utility::AcDbEntityPtr - другой класс интеллектуального указателя (тоже шаблон), использующий подсчет ссылок, в деструкторе происходит вызов close().
arxkit::AcadError - класс исключения, оболочка вокруг Acad::ErrorStatus.
arxkit::ErroStatusCheck - вспомогательный класс, в котором (после больших сомнений и раздумий) был перегружен operator=(Acad::ErrorStatus), который возбуждает исключение arxkit::AcadError если не sOk.
По умолчанию, при инициализации приложения происходит привязка потоков std::cout и std::cerr к командному окну Автокада, что значительно упрощает вывод различных сообщений.
P.S. Надеюсь, что это было полезно smile

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

Не удивил... :)
Но все таки мне больше импонирует использование макроса MYASSERT. Может консерватизм.
Дело в том, что если юзать умный указатель, то при выбрасывании исключения - это исключение происходит в коде умного указателя а еще хуже коде шаблона. Плюс к этому в макросе ASSERT отображается информация об условии а в эксепшене умного указателя нет.
Т.е. допустим такой код:
---
getEntity( pWiseRefEnt );
pWiseRefEnt->callMethod();
---
и
---
getEntity( pEnt );
MYASSERT( pEnt != NULL );
pEnt->callMethod();
---
В первом случае мы получим что-то типа:
Exception in WiseRefTemplate.h line 66 pRefObj is NULL
Во втором случае мы получим
pEnt != NULL GraphIterator.cpp line 101
почему ТО мне кажется что получить второе сообщение об ошибке ГОРАЗДО информативней и лучше чем первое...
т.к. GraphIterator.cpp line 101 - это высокоуровневый код обхода графа и зовется из одного, двух мест и понять ситуацию из-за которой произошла ошибка легче, чем получить ошибку которая может быть инициирована откуда угодно. Особенно для релиза, коды программа установлена на ПК пользователя/тестера. И очень часто получив баг от MYASSERT - становится ясно не только ГДЕ и ПОЧЕМУ ошибка, но и ИЗ-ЗА ЧЕГО.
Второе, относительно close при достижении счетчика 0 в подсчете ссылок... Лучше уж я сам закрою объект где надо т.к. может возникнуть ситуация что закрыть НАДО, а ссылки еще живы и не ушли из области видимости.

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

> KonstantinM
Ошибки.
Я тоже очень хотел знать в каком именно месте (с точностью до строки) происходит ошибка, поэтому добавил к arxkit::ErroStatusCheck необходимый функционал. Он не показан в коде потому, что я им практически не пользуюсь - он почти бесполезен! В самом деле, при программировании под Автокад, ошибка обычно допущена не в месте её обнаружения, а где-нибудь совсем в другом месте. Обычно достаточно просто знать функцию, в которой произошла ошибка.
Интеллектуальные указатели
Может быть и возможны ситуации, когда интеллектуальные указатели мешают, но они очень редки и никто не запрещает в этом случае использовать простые указатели. Но что-то я в своих 10 000 строк кода не припомню такого. Опасения, что объект нужно закрыть, а указатель ещё не вышел из области видимости, напрасны ? областью видимости можно эффективно управлять, например:

arxkit::ErroStatusCheck es;
arxkit::utility::AcDbBlockTableRecordPtr blockTableRecord;
{    
    arxkit::utility::AcDbBlockTablePtr blockTable;
    es = document_->database()->getSymbolTable(GetImplRef(blockTable), AcDb::kForRead);
    es = blockTable->getAt(ACDB_MODEL_SPACE, GetImplRef(blockTableRecord), AcDb::kForWrite);
}

Я стал повсеместно использовать подобную технику, когда понял, что после каждого вызова функции ObjectArx необходимо проверять код ошибки, иначе даже отлаженное приложение работает неустойчиво. А это приводит к весьма значительному увеличению объёма кода, что само по себе провоцирует ошибки. Когда пишешь быстро и много это особенно актуально.
Позволю себе ещё одну вольность и приведу свой пример приложения ?Hello, ObjectArx!?. Вот его полный код:

#include "aced.h"
#include "arxkit\utility.h"
#include "arxkit\application.h"
extern const char arxHelloStr[] = "ArxHello";
extern const char arxHelloGroupStr[] = "ArxHelloGroup";
void ArxHello(void) {
    std::cout << "Hello, ObjectArx!" << std::endl;
}
typedef arxkit::ArxCommand<ArxHello, arxHelloStr> ArxHelloCommand;
typedef arxkit::ArxCommandGroup<TYPELIST_1(ArxHelloCommand), arxHelloGroupStr, ACRX_CMD_MODAL> ArxHelloGroup;
typedef arxkit::AcadUtilityApplication<ArxHelloGroup> HelloApplication;
typedef arxkit::SingletonAcadApplication<HelloApplication> Application;
extern "C" template
AcRx::AppRetCode acrxEntryPoint<Application>(AcRx::AppMsgCode, void*);

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

Вобщем я использую умные указатели в своем коде, но только не в коде напрямую свзанным с ObjectARX.
А про шаблоны мы уже где-то обсуждали.
Посмотри на свой код выше и подумай что подумает человек, который будет смотреть твой код. Увы, совсем не всех учат шаблонам... поэтому моя позиция по этому вопросу, и я ее уже ранее высказывал, что черезмерно использовать шаблоны не надо. У меня есть опыт общения с программистами, которым даешь задачу a + b - потом через неделю обнаруживаешь вместо функции f(a,b,result), набор шаблонных классов которые могут складывать что угодно и как угодно, но только не так как надо :) (Это отдельная история - рассказывать долго).
Если ты подумаешь что другой человек подумает - ой как круто, то ошибаешься :) В большинстве случаев подумают - че за х тут написано :) - такая вот неблагодарность. И опять же скажу, что большинство задач можно решить удачной комбинацией паттернов проектирования и шаблоны там ну совсем не нужны.
Может я еще просто не дорос до шаблонного программирования, но на данный момент я приверженец интерфейсного программирования. Хотя соглашусь что смесь шаблонов и интерфейсного программирования иногда оказывается очень ядреной(всмысле убийственно хороших решений).

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

> KonstantinM
Да, шаблоны...
Я считаю, что при изучении С++ есть две основных трудности: указатели и шаблоны smile Если с этим проблемы, то "оставь надежды всяк сюда входящий" - С++ не для вас.
Избегая шаблоны, ты просто игнорируешь современные тенденции развития С++. Что бы понять это, достаточно просто посмотреть, например, на исходный код STL, boost (который написан, между прочим, в основном членами комитета по стандартизации С++), Loki, ATL, WTL и т.д.
Показательный пример идивительной мощи шаблонов - паттерны. После публикации паттернов GoF считалось, что их нельзя запрограммировать в виде повторно используемых компонентов. Однако, Александреску, используя шаблоны, прекрасно справился с этой задачей (и показал остальным как это надо делать).
Чёрт, даже ACE всё больше и больше использует эти возможности.
ИМХО, такая позиция неприемлема для человека, который преподаёт С++. У учащихся может создатся неверное представление о современном состоянии языка. Это будет препятствовать их развитию и может создать трудности при поиске работы (ввиду недостаточно квалификации).

Re: Не могу понять в чем ошибка при получении данных из AutoCAD

> archimag
А я согласен с KonstantinM.
Любое средство языка хорошо в меру.
Ведь после инстанциации шаблон превратится в обычную функцию (класс), которая и будет компилироваться. Т.е все равно возвращаемся к тем же функциям(класам) как бы этого не хотели. Шаблон - это своего рода гарантия одинаковости и средство обобщения реализаций но не панацея от всех бед.
Кроме того, ИМХО намного лучше пользоваться собственной реализацией тех же паттернов, "подточенной" под свои задачи, чем супер-навороченной, универсальной но уже готовой. Своя реализация не имеет ничего лишнего и решает поставленную заачу smile  (это безусловно не касается stl)
А вот умные указатели использовать стоит!.
Особенно в конструкциях типа:

AcDbEntity *pEnt;
Open(pEnt);
Если открыли{
//действия
[i]if(что-то) return;[/i]
}
pEnt->close();

Если return появится после написания ф-и, или еще как то, то закрытие объекта не произойдет. А если он еще открывался только для чтения то ошибка может проявится неизвестно когда. А отыскать ее станет ну очень не просто.
В то время как использование умного указателя дает полную гарантию коректного завершения такой фукнции.