0

我的同事正在通过以下方法使用非托管库中的双字节字符填充 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 对象应该是不可变的——我正在寻找一个明确的“这是错误的,这就是为什么”。

谢谢,伊莱。

4

2 回答 2

1

我很惊讶这似乎有效。如果我理解它,您可以固定一个String对象,获取它的地址,然后将其转换为字符缓冲区。它不是字符缓冲区。CLR 对象以 8 字节标头开始(无论如何都是 32 位)。您可能正在丢弃 CLR 在垃圾收集中使用的内部数据。

为什么不分配一个本机缓冲区(std::vector<wchar_t>会很棒)传递给本机 API,然后从该缓冲区安全地构造一个 CLR 字符串?

更新:

好的,这是一个参考:http ://www.drdobbs.com/cpp/184403869

事实证明,正在使用的 pinning API 具有特殊的布局知识String,并且知道如何查找和返回原始内部字符缓冲区。耶!

但是引用那篇文章:

最后一点很重要:在其中一些示例中,我展示了如何使用 pin_cast 访问托管字符串和数组的私有数据缓冲区,可能以非常量方式。鉴于这些是密封类型,并且整个实现是未知的,因此假设您可以安全地修改这些缓冲区的内容是很糟糕的,即使内存是固定的。

有趣的是,API 文档没有提到字符串的特殊行为。

于 2010-02-09T20:37:09.307 回答
0

实际上,问题不在于 .NET 字符串作为输出缓冲区,而在于输入缓冲区。

sprintf("%s") 类函数(包括 wsprintf 等)将对字符串上的任何参数执行 strlen 类型的操作 - 即使它是 snwprintf - "n" 部分仅限制 WRITTEN 到字符串的数量,而不是从输入缓冲区读取的字符。

原来输入缓冲区永远不能保证为空终止。通常我们很幸运,因为如果返回的数据很小,它会在到达坏内存之前达到 SOMETHING null 。

但是,如果那里的数据足够大,它就会走到内存页的末尾。当 strlen 继续运行时,它会离开页面,并且访问违规城市!

幸运的是,我在使用附加的本机模式调试器测试其他东西时发现了这一点,并准备好所有来自 MS 的 C 运行时调试符号!

我们将 snwprintf 改为 wcsncpy() (当我们必须进行 ANSI->Unicode 转换时,snwprintf 是一个遗留操作 - 为什么他没有改为使用 MultiByteToWideChar(),我永远不会知道。

无论如何感谢您的建议。

于 2010-03-17T17:36:29.120 回答