Обработка неуправляемой строки в оболочке C / CLI — BLOCK_TYPE_IS_VALID, _CrtIsValidHeapPointer

#c -cli #marshalling #unmanaged #wrapper #managed

#c -cli #сортировка #неуправляемый #оболочка #управляемый

Вопрос:

Я новичок в C / CLI, но уже много лет пишу управляемый код… очевидно, слишком много лет. 🙂

Пытаюсь написать оболочку для неуправляемого класса, предоставленного третьей стороной, и вижу некоторые странные вещи. Я надеюсь, что вы все сможете помочь мне разобраться в том, в чем заключается мой нубизм и что на самом деле странно.

Оболочка CLI:

 public ref class Wrapper
{
public:
    Wrapper(const float* flts, unsigned int fltlen, int offset)
    {
        _unmanagedClass = new UnmanagedClass(flts, fltlen, offset);
    }

    ~Wrapper()
    {
        delete _unmanagedClass;
    }

    String^ getSomeString()
    {
        string x = _unmanagedClass->getSomeString(); //1
        String^ ret = gcnew String(x.c_str()); //2
        return ret; //3
    }

private:
    UnmanagedClass* _unmanagedClass;
};
  

Я должен также отметить, что у меня есть эти директивы в заголовке;

 #pragma managed(push, off)
#include "Unmanaged.h"
#pragma comment(lib, "lib\Unmanaged_dll.lib")
#pragma managed(pop)
  

Вот Unmanaged.h;

 class UNMANGED_API UnmanagedClass
{
public:
    UnmanagedClass(const float* flts, uint fltlen, int offset);
    string getSomeString() { return _someString; }

private:
    string _someString;
};
  

Все это компилируется. Затем возникает странность / недостаток опыта.

При отладке этого в конфигурации ОТЛАДКИ UnmanagedClass::getSomeString() , похоже, возвращается допустимое / ожидаемое строковое значение. Я могу увидеть это, установив точку останова на //2 и просмотрев значение x . Если я перейду к //3 , я увижу, что это ret имеет значение x . Однако, если я пытаюсь выйти / перезапустить //3 , я получаю пару неудачных утверждений ( BLOCK_TYPE_IS_VALID и _CrtIsValidHeapPointer ), и отладчик останавливается, никогда не возвращаясь к управляемой реализации.

При отладке этого в конфигурации ВЫПУСКА я не получаю неудачных утверждений и возвращаюсь к своей управляемой реализации, но строковое значение, возвращаемое getSomeString() , является мусором, где бы я его ни просматривал; //2 , //3 а также в управляемой реализации.

Я переделал код несколькими различными способами, но безрезультатно.

Я думаю, что есть некоторый мэшаллинг, который нужно обойти //2 , но я не смог найти ничего, что действительно попадало бы в цель в том, как преобразовать это basic_string в System::String^ , или если это вообще требуется. Если это так, то мы были бы весьма признательны за некоторую помощь с явным синтаксисом.

Я также сузил вызов, который выдает неудачные утверждения, до //1 возврата return ""; //3 . Эти утверждения указывают на попытку изменить / удалить память, которая не существует в куче, к которой имеет доступ текущая среда выполнения. Связано ли это с необходимостью маршалирования возвращаемого значения UnmangedClass::getSomeString() ?

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

Заранее спасибо за любую информацию или «указатели»;

РЕДАКТИРОВАТЬ: Добавление клиентской реализации, управляемой C #;

 public unsafe string GetString(List<float> flts )
{
    float[] fltArr = flts.ToArray();

    Wrapper wrap;

    fixed (float* ptrFlts = fltArr)
    {
        wrap = new Wrapper(ptrFlts , fltArr.Length, 0);
    }

    var x = wrap.getSomeString();
    return x.ToString();
}
  

РЕДАКТИРОВАТЬ: Добавление Dumpbin.exe подпись Unmanged.dll !Не измененный класс::getSomeString()

(public: class std::basic_string <char,struct std::char_traits<char>,class std::allocator<char> > __thiscall Codegen::getSomeString(void))

Ответ №1:

Эта проблема не имеет ничего общего с .NET или C / CLI, проблема заключается исключительно в машинном коде.

Вы нарушили правило единого определения для std::string , если ваше определение не совсем соответствует тому, что Unmanaged_dll.dll используется, начнется настоящий ад. И это звучит так, как будто эта DLL используется для определения отладки / компоновки класса.

Комментарии:

1. Спасибо за ваш ответ. Правильно ли я вас понимаю, что определение std:string меняется между режимами отладки и выпуска? Я отредактировал основной пост, чтобы предоставить сигнатуру dumpbin для Unmanaged::getSomeString . Есть ли какой-нибудь способ, которым я могу более точно соответствовать их строковому типу? Нужно ли мне всегда запускать мою оболочку в конфигурации отладки, если я хочу использовать машинный код как есть?

2. @Josh: У тебя есть код для Unmanaged_dll.dll ? Если это так, «правильное» исправление заключается в использовании разных имен для конфигураций отладки и выпуска, точно так же, как это делает Microsoft CRT (например MSVCR90.dll против MSVCR90D.dll ), а затем используйте #if NDEBUG вместе с #pragma comment(lib) для выбора правильной. Если вы не можете создать релизную версию DLL, то вы также не можете использовать release CRT для своего кода. Одна из причин, по которой я не рекомендую совместно использовать классы через границы DLL.

3. @Josh: Также возможно, что меняется макет UnmanagedClass самого _someString файла, так что местоположение, в котором хранится DLL, находится не там, где ваш компилятор ищет его.

4. @Ben, еще раз спасибо за ваши быстрые ответы! У меня нет доступа к источнику, но у меня есть некоторый доступ к автору. Просто чтобы мои утки были в ряд, это так же просто, как если бы автор предоставил биты отладки и выпуска? Я также упомяну, что я связываюсь с использованием библиотеки lib, а не DllImport … к сожалению, я не знаю, имеет ли это значение…

5. @Josh: Это можно заставить работать, если оба модуля используют одинаковый макет для стандартной библиотеки … это означает версию компилятора (включая пакеты обновления), debug vs release, _SCL_ITERATOR_DEBUGGING и т.д. Куча должна быть общей, поэтому вам также необходимо использовать DLL-версию CRT. Это неизбежно вызовет проблемы, когда вы захотите перейти к следующей отличной новой версии компилятора. Менее хрупкое исправление заключается в том, что вы предоставляете буфер как char* и заставляете его копировать в него свою строку (может потребоваться вторая функция, чтобы сначала получить размер, чтобы вы могли выделить свой буфер достаточно большим).

Ответ №2:

Вы просто отлично преобразовали свою собственную строку в управляемую строку. В этой статье о MSDN приведены примеры преобразования между всеми различными типами строк, которые поставляются на платформах Microsoft:

Сказав это, я взял ваш код и скомпилировал его, и я не смог добиться сбоя. Конечно, мне пришлось придумать свой собственный способ инициализации UnmanagedClass ::_someString , что я и сделал, просто выполнив это:

 UnmanagedClass::UnmanagedClass(const float* /*flts*/, unsigned int /*fltlen*/, int /*offset*/)
{
    _someString = "A few of my favorite things";
}
  

Когда я сделал это и прошел через этот код:

 #include "stdafx.h"
#include "Wrapper.h"

int _tmain(int argc, _TCHAR* argv[])
{
    Wrapper^ w = gcnew Wrapper(NULL, 0, 0);
    System::String^ s = w->getSomeString();
    return 0;
}
  

Это сработало просто отлично.
Вот остальная часть того, что я сделал:

 // UnmanagedClass.h
#pragma once
#pragma unmanaged
#include <vector>

class UnmanagedClass
{
public:
    UnmanagedClass(const float* flts, unsigned int fltlen, int offset);
    std::string getSomeString() { return _someString; }
private:
    std::string _someString;
};
  

И это реализация:

 // UnmanagedClass.cpp
#include "UnmanagedClass.h"
#include <tchar.h>

UnmanagedClass::UnmanagedClass(const float* /*flts*/, unsigned int /*fltlen*/, int /*offset*/)
{
    _someString = "A few of my favorite things";
}
  

И управляемый класс

 // wrapper.h
#pragma once

#pragma unmanaged
#include "UnmanagedClass.h"

#pragma managed

public ref class Wrapper
{
public:
    Wrapper(const float* flts, unsigned int fltlen, int offset)
    {
        _unmanagedClass = new UnmanagedClass(flts, fltlen, offset);
    }

    ~Wrapper()
    {
        delete _unmanagedClass;
    }

    System::String^ getSomeString()
    {
        std::string x = _unmanagedClass->getSomeString(); //1
        System::String^ ret = gcnew System::String(x.c_str()); //2
        return ret; //3
    }
private:
    UnmanagedClass* _unmanagedClass;
};
  

Я надеюсь, что это немного поможет.

Комментарии:

1. @C Johnson: Спасибо, что нашли время повторить мой senario и подтвердить мой собственный / управляемый переход, все помогает. (К сожалению, я все еще слишком новичок, чтобы голосовать.)