0

私の同僚は、次の方法でアンマネージ ライブラリから System::String オブジェクトに 2 バイト文字を入力しています。

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

使用されているピン留めAPIには、のレイアウトに関する特別な知識がありString、生の内部文字バッファーを見つけて返す方法を知っていることがわかりました。うん!

しかし、その記事を引用するには:

最後の重要なポイント:これらの例のいくつかでは、pin_castを使用して、管理対象の文字列と配列のプライベートデータバッファーに、場合によっては非定数でアクセスする方法を示しています。これらは封印されたタイプであり、実装全体が不明であるため、メモリが固定されている場合でも、これらのバッファの内容を安全に変更できると想定するのは適切ではありません。

興味深いことに、APIドキュメントには文字列の特別な動作については記載されていません。

于 2010-02-09T20:37:09.307 に答える
0

実際には、問題は出力バッファーとしての .NET 文字列ではなく、代わりに入力バッファーにありました。

sprintf("%s") クラス関数 (wsprintf などを含む) は、文字列の任意のパラメーターに対して strlen 型の操作を実行します - snwprintf の場合でも - "n" 部分は文字列への書き込み量を制限するだけです、入力バッファから読み取った文字ではありません。

入力バッファが null で終了することが保証されていないことが判明しました。多くの場合、返されたデータが小さい場合、不良メモリに到達する前に SOMETHING null にヒットするため、幸運に恵まれます。

ただし、そこにあるデータが十分に大きい場合は、メモリ ページの最後に移動します。ストレンが続くとページから外れ、アクセス違反都市!

幸いなことに、ネイティブ モード デバッガーを接続して、C ランタイム用の MS からのすべてのデバッグ シンボルを準備して、別のものをテストしていたときに、これを見つけました。

代わりに、snwprintf を wcsncpy() に切り替えました (ANSI->Unicode 変換を行う必要があったとき、snwprintf は従来の操作でした。代わりに MultiByteToWideChar() を行わなかった理由はわかりません。

とにかくアドバイスをありがとう。

于 2010-03-17T17:36:29.120 に答える