0

I am writing a c++ mixed mode DLL and converting objects read from a database to unmanaged code variables. I am trying to convert the object into a byte array so I can copy its value byte by byte to unmanaged memory.

When I do the conversion I get more than the object's data in the byte array.

Here is an example of the code I have:

// Convert an object to a byte array
static array<System::Byte>^ ObjectToByteArray(Object^ obj)
{
    if (obj->Equals(nullptr))
        return nullptr;
    BinaryFormatter^ bf = gcnew BinaryFormatter();
    MemoryStream^ ms = gcnew MemoryStream();
    bf->Serialize(ms, obj);
    return ms->ToArray();
}

void dtmControlLimits() {
    OdbcDataReader^ reader;
    // ...

    Object oVal = reader->GetValue(ordinalPositionColumn);
    array<System::Byte, 1>^ ab = ObjectToByteArray(oVal);
    pin_ptr<System::Byte> pp = &ab[0];
    char *p = (char *)pz->zMap.pValue;
    for (size_t i = 0; i < knSizeOf; i++, p++){
      *p = ab[i];
    }
    // ...

}

The array ab that is returned looks like a struct for an Int32 object, not just it's value.

I want to get a byte[] with just the value of the object so that I can copy it byte by byte to the unmanaged data.

IF oVal is Int32 and *oVal = 0x12345678 then here's a memory dump of ab:

0x03A45954  00 01 00 00 00 ff ff ff ff 01 00 00 00 00 00 00 00 04  .....ÿÿÿÿ.........
0x03A45966  01 00 00 00 0c 53 79 73 74 65 6d 2e 49 6e 74 33 32 01  .....System.Int32.
0x03A45978  00 00 00 07 6d 5f 76 61 6c 75 65 00 08 78 56 34 12 0b  ....m_value..xV4..

The value for oVal is on the last line following the fieldname m_value. Since oVal is read from a database I can't be sure of its datatype at compile time.

How can I get the object's value, not the entire object data structure, into the byte[] ab?

=========================================================

UPDATE June 12, 2014

This is an update of old MFC code that uses DAO and I want to update the old 32 bit app to be a 64 bit app. To do that I have to rewrite the DAO code to use ADO.Net since DAO is only available in 32 bit.

I really wanted to avoid putting in a switch for memory copy but looks like I'm forced to live within the restrictions of .Net. The old MFC code let me use memcpy() to transfer the DAO recordset data to the appropriate variable. This is a straight forward way to update the data.

Using a switch statement seems unnecessary and complex. I was hoping to avoid it.

Does anyone have a better idea?

static ViStatus ReaderToTag(
    pzDataTag_t pz
    , String^ sColumnName
    , IDataReader^ reader)
{
    ViStatus errStatus = VI_SUCCESS;
    array<System::Byte, 1>^ ab;
    if (pz) {
        const MM_eVppType_t keVppType = pz->zMap.eVppType;
        const size_t knSizeOf = MM_VPPTYPE_SIZEOF(keVppType);
        Object^ oVal = reader[sColumnName];
        Type^ typ = oVal->GetType();
        switch (Type::GetTypeCode(typ)) {
        case System::TypeCode::Byte:
            ab = BitConverter::GetBytes((Byte)oVal);
            break;
        case System::TypeCode::Char:
            ab = BitConverter::GetBytes((Char)oVal);
            break;
        case System::TypeCode::DateTime:
        {
            DateTime^ date = (DateTime)oVal;
            TimeSpan^ diff = date->ToUniversalTime() - DateTime(1970, 1, 1);
            *(time_t *)pz->zMap.pValue = static_cast<std::time_t>(diff->TotalMilliseconds);
            break;
        }
        case System::TypeCode::Decimal:
        {
            array<int>^ bits = Decimal::GetBits((Decimal)oVal);
            System::Array::Resize(ab, sizeof(Decimal));
            System::Buffer::BlockCopy(bits, 0, ab, 0, ab->Length);
            break;
        }
        case System::TypeCode::Double:
            ab = BitConverter::GetBytes((Double)oVal);
            break;
        case System::TypeCode::Int16:
            ab = BitConverter::GetBytes((Int16)oVal);
            break;
        case System::TypeCode::Int32:
            ab = BitConverter::GetBytes((Int32)oVal);
            break;
        case System::TypeCode::Int64:
            ab = BitConverter::GetBytes((Int64)oVal);
            break;
        case System::TypeCode::Object:
            break;
        case System::TypeCode::SByte:
            ab = BitConverter::GetBytes((SByte)oVal);
            break;
        case System::TypeCode::Single:
            ab = BitConverter::GetBytes((Single)oVal);
            break;
        case System::TypeCode::String:
        {
            String ^ str = Convert::ToString(oVal);
            char *pcSrc = (char*)(void*)Marshal::StringToHGlobalAnsi(str);
            char *pcDest = (char *)pz->zMap.pValue;
            for (int i = 0; i < min(str->Length, pz->zMap.nArySize - 1); i++, pcSrc++, pcDest++) {
                *pcDest = *pcSrc;
            }
            *pcDest = 0;
            break;
        }
        case System::TypeCode::UInt16:
            ab = BitConverter::GetBytes((UInt16)oVal);
            break;
        case System::TypeCode::UInt32:
            ab = BitConverter::GetBytes((UInt32)oVal);
            break;
        case System::TypeCode::UInt64:
            ab = BitConverter::GetBytes((UInt64)oVal);
            break;

        case System::TypeCode::Empty:
        case System::TypeCode::DBNull:
        default:
            goto AbortOnError;
        }
        if (ab && ab->Length) {
            pin_ptr<System::Byte> pp = &ab[0];
            char *p = (char *)pz->zMap.pValue;
            for (size_t i = 0; i < min((size_t)ab->Length, knSizeOf); i++, p++){
                *p = ab[i];
            }
            //memcpy(pz->zMap.pValue, (char*)pp, knSizeOf);
        }
        Debug::WriteLine(sColumnName->ToString()
            + "=" + oVal->ToString()
            + ", typ=" + typ->ToString());

    }
    return errStatus;

AbortOnError:
    if (VI_SUCCESS == errStatus) errStatus = mmGrasp_eErr_Unspecified;
    if (MM_pfnRunStatus) MM_pfnRunStatus("... mmDotNet_dtmControlLimitsToTags: Error");
    return errStatus;
} // ReaderToTag()
Michael Fitzpatrick
  • 682
  • 3
  • 8
  • 18

1 Answers1

1

Using BinaryFormatter is a fail-whale here. It doesn't just write the object you pass it, it also adds metadata that describes the object. So it can be reliably de-serialized again, reconstructing the exact same object. You can clearly see this in your dump. It starts with a header that says "I'm binary serialized data", followed by a description of the object type. An int in this case. "m_Value" is the field that stores the value in the boxed Int32 object.

You do know the object type, it is set by the column in the database. Which is fixed for all intents and purposes, changing it forces you to change your code as well. So byte the bullet and convert the object:

  int limit = safe_cast<int>(oVal);

But I'd guess you don't want to do that because the type conversion actually exists in your native code. You can get ahead with reflection, write a switch() for oVal->GetType(). You can now write the proper casts in each case statement and use BitConverter.GetBytes() to get an array<Byte>^. Watch out for strings, encoding matters.

The conversion from the raw dbase row to safe managed objects, back to raw data again and converted to native values clearly is unproductive. You are a bit doomed to lift the code and make the data conversion earlier. Hopefully you have a native struct that matches the dbase row, you can fill it with C++/CLI as well.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536