【问题标题】:Handling an Unmanaged String in a C++/CLI Wrapper - BLOCK_TYPE_IS_VALID, _CrtIsValidHeapPointer在 C++/CLI 包装器中处理非托管字符串 - BLOCK_TYPE_IS_VALID、_CrtIsValidHeapPointer
【发布时间】:2011-04-11 11:05:03
【问题描述】:

我是 C++/CLI 的新手,但多年来一直在编写托管代码......显然太多年了。 :)

尝试为第 3 方提供的非托管类编写包装器,但我看到了一些奇怪的东西。我希望你们都可以帮助我清除我的笨拙和真正奇怪的地方。

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;
};

这一切都会编译。然后就会出现陌生感/缺乏经验。

在 DEBUG 配置中对此进行调试时,UnmanagedClass::getSomeString() 似乎返回了一个合理/预期的字符串值。我可以通过在//2 上设置断点并查看x 的值来看到这一点。如果我步进到//3,我可以看到ret 的值是x。但是,如果我尝试跳出/越过 //3,我会收到一些失败的断言(BLOCK_TYPE_IS_VALID_CrtIsValidHeapPointer),并且调试器会停止,永远不会返回到托管实现。

在 RELEASE 配置中调试它时,我没有得到失败的断言并返回到我的托管实现,但 getSomeString() 返回的字符串值在我偷看的地方都是垃圾; //2//3 以及托管实现中。

我已经用几种不同的方式修改了代码,但都无济于事。

我认为需要围绕 //2 进行一些编组,但我还没有找到任何真正重要的事情,比如如何将 basic_string 编组为 System::String^,或者如果它甚至是必需的。如果是这样,那么我们将非常感谢您对显式语法的帮助。

我还通过返回return ""; //3 将产生失败断言的调用范围缩小到//1。这些断言指向尝试修改/删除当前运行时有权访问的堆上不存在的内存。这是否与需要编组 UnmangedClass::getSomeString() 的返回值有关?

希望我只是在这里遗漏了一些简单的概念,并且第 3 方代码没有问题。如果我能提供更多细节,请告诉我,并为我几乎完全不了解所有语言的祖父而道歉。

提前感谢您提供任何信息或“指针”;

编辑:添加 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();
}

编辑:添加 Unmanged.dll!UnmangedClass::getSomeString() 的 Dumpbin.exe 签名

(public: class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt; __thiscall Codegen::getSomeString(void))

【问题讨论】:

    标签: c++-cli marshalling unmanaged wrapper managed


    【解决方案1】:

    此问题与.NET 或 C++/CLI 无关,问题纯粹在本机代码中。

    您违反了std::string 的单一定义规则,如果您的定义与Unmanaged_dll.dll 使用的定义不完全匹配,那么一切都会崩溃。听起来好像该 DLL 用于调试定义/类布局。

    【讨论】:

    • 感谢您的回复。我是否正确理解 std:string 的定义在调试和发布模式之间发生变化?我编辑了主帖以提供 Unmanaged::getSomeString 的转储文件签名。有什么方法可以更接近地匹配他们的字符串类型吗?如果要按原样使用本机代码,是否需要始终在调试配置中运行包装器?
    • @Josh:你有Unmanaged_dll.dll 的代码吗?如果是这样,“正确”的修复是使用不同的名称进行调试和发布配置,就像 Microsoft CRT 一样(例如 MSVCR90.dll 与 MSVCR90D.dll),然后使用 #if NDEBUG#pragma comment(lib) 来选择正确的那一个。如果您无法构建 DLL 的发布版本,那么您也无法将发布 CRT 用于您的代码。我建议不要跨 DLL 边界共享类的原因之一。
    • @Josh:UnmanagedClass 本身的布局也可能发生变化,因此 DLL 存储_someString 的位置不是编译器寻找它的位置。
    • @Ben,再次感谢您的快速回复!我无权访问源,但我可以访问作者。就这样我的鸭子排成一排,这就像让作者提供调试和发布位一样简单吗?我还要提到我正在使用 lib 而不是 DLLImport 进行链接...不幸的是,我不知道这是否重要...
    • @Josh:如果两个模块对标准库使用相同的精确布局,它就可以工作......这意味着编译器版本(包括服务包)、调试与发布、_SCL_ITERATOR_DEBUGGING 等。堆必须是共享的,因此您还需要使用 CRT 的 DLL 版本。当您想要迁移到下一个出色的新编译器版本时,这势必会造成麻烦。较不脆弱的解决方法是为您提供一个缓冲区作为char* 并让他将他的字符串复制到其中(可能需要第二个函数来首先获取大小,因此您可以分配足够大的缓冲区)。
    【解决方案2】:

    您将本机字符串转换为托管字符串就好了。这个article on 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;
    };
    

    希望对你有所帮助。

    【讨论】:

    • @C Johnson:感谢您花时间复制我的情景并验证我的原生/托管转换,一切都会有所帮助。 (遗憾的是,我还太新,无法投票。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-30
    • 2011-10-06
    • 1970-01-01
    • 2018-09-01
    相关资源
    最近更新 更多