Создание объекта System :: String из BSTR в Managed C ++ - это хороший способ? - PullRequest
0 голосов
/ 09 февраля 2010

Мой коллега заполняет объект System :: String двухбайтовыми символами из неуправляемой библиотеки следующим способом:

RFC_PARAMETER aux;
Object* target;
RFC_UNICODE_TYPE_ELEMENT* elm;
elm = &(m_coreObject->m_pStructMeta->m_typeElements[index]);
aux.name = NULL;
aux.nlen = 0;
aux.type = elm->type;
aux.leng = elm->c2_length;
aux.addr = m_coreObject->m_rfcWa + elm->c2_offset;

GlobalFunctions::CreateObjectForRFCField(target,aux,elm->decimals);
GlobalFunctions::ReadRFCField(target,aux,elm->decimals);

Где GlobalFunctions :: CreateObjectForRFCField создает объект System :: String, заполненный пробелами (для заполнения), в котором неуправляемая библиотека заявляет, что максимальная длина должна быть:

static void CreateObjectForRFCField(Object*& object, RFC_PARAMETER& par, unsigned dec)
{
    switch (par.type)
    {
        case TYPC:
            object = new String(' ',par.leng / sizeof(_TCHAR));
            break;
        // unimportant afterwards.
    }
}

И GlobalFunctions :: ReadRFCField () копирует данные из библиотеки в созданный объект String и сохраняет заполнение пробелами:

static void ReadRFCField(String* target, RFC_PARAMETER& par)
{
    int lngt;
    _TCHAR* srce;
    switch (par.type)
    {
        case TYPC:
        case TYPDATE:
        case TYPTIME:
        case TYPNUM:
            lngt = par.leng / sizeof(_TCHAR);
            srce = (_TCHAR*)par.addr;
            break;

        case RFCTYPE_STRING:
            lngt = (*(_TCHAR**)par.addr != NULL) ? (int)_tcslen(*(_TCHAR**)par.addr) : 0;
            srce = *(_TCHAR**)par.addr;
            break;

        default:
            throw new DotNet_Incomp_RFCType2;
    }

    if (lngt > target->Length) lngt = target->Length;

    GCHandle gh = GCHandle::Alloc(target,GCHandleType::Pinned);
    wchar_t* buff = reinterpret_cast<wchar_t*>(gh.AddrOfPinnedObject().ToPointer());
    _wcsnset(buff,' ',target->Length);
    _snwprintf(buff,lngt,_T2WFSP,srce);
    gh.Free();
}

Теперь, иногда, мы видим нарушения прав доступа в вызове _snwprintf. Мой вопрос на самом деле: уместно ли создать строку, дополненную по длине (в идеале, для предварительного выделения внутреннего буфера), а затем изменить строку, используя GCHandle :: Alloc и беспорядок выше.

И да, Я знаю, что объекты System :: String должны быть неизменными - я ищу однозначное "Это НЕПРАВИЛЬНО и вот почему".

Спасибо, Эли.

Ответы [ 2 ]

1 голос
/ 09 февраля 2010

Я поражен, что это, кажется, работает. Если я понимаю, вы прикрепляете объект String, получаете его адрес, а затем приводите его в буфер символов. Это не буфер символов. Объекты CLR начинаются с 8-байтового заголовка (во всяком случае, 32-битного). Вероятно, вы удаляете внутренние данные, используемые CLR при сборке мусора.

Почему бы не выделить собственный буфер (std::vector<wchar_t> было бы замечательно) для передачи на собственный API, а затем безопасно создать строку CLR из этого буфера?

Обновление:

Хорошо, вот ссылка: http://www.drdobbs.com/cpp/184403869

Оказывается, что используемый API закрепления обладает специальными знаниями о макете String и знает, как найти и вернуть необработанный внутренний символьный буфер. Иш!

Но процитирую эту статью:

Последний важный момент: в некоторых эти примеры, я показываю, как pin_cast может использоваться для доступа к личный буфер данных управляемых строк и массивы, возможно, в неконстантном манера. Учитывая, что это запечатанные типы, и что вся реализация неизвестно, было бы плохо предполагать вас может безопасно изменить содержимое эти буферы, даже если память возлагали.

Интересно, что в документации API не упоминается специальное поведение для строк.

0 голосов
/ 17 марта 2010

На самом деле проблема заключалась не в строке .NET в качестве выходного буфера, а в буфере input .

Функции класса sprintf ("% s") (включая wsprintf и т. Д.) Будут выполнять операцию strlen-типа над любыми параметрами в строке - ДАЖЕ, если это snwprintf - часть "n" ограничивает только значение WRITTEN в строку, а НЕ символы, прочитанные из буфера ввода.

Оказывается, входной буфер никогда не гарантированно завершается нулем. Часто нам везет, потому что, если возвращаемые данные были маленькими, они НЕ ДАЛИ Нулевого значения до того, как достигли плохой памяти.

Однако, если данные там достаточно велики, они перейдут в конец страницы памяти. Когда strlen продолжает работать, он уходит со страницы, и Access Violation city!

К счастью, я обнаружил это при тестировании чего-то другого с отладчиком в основном режиме, подключенным и готовым ко всем символам отладки от MS для среды выполнения C!

Вместо этого мы переключили snwprintf на wcsncpy () (snwprintf был унаследованной операцией, когда нам приходилось выполнять преобразование ANSI-> Unicode - почему он вместо этого не выполнял MultiByteToWideChar (), я никогда не узнаю.

спасибо за совет, во всяком случае.

...