Matt Fisher
Matt Fisher

Reputation: 213

Inconsistent behavior when passing bitmaps from C++ DLL back to C#

I have a C# application that needs to get variable-length data passed back from a C++ DLL. Typically I solve this problem as follows: for each entity type (such as a string or a bitmap) I have a persistent C++ variable of the appropriate type. The C# app then calls something like "QueryBitmapByName(string bitmapName)", and the "contract" is that C# must process this variable before it calls another function in the C++ DLL. This has always worked in the past, but I have recently hit a stumbling block I don't understand when returning multiple bitmaps. In Release mode the behavior is fine, but in Debug mode the bitmap passing fails to copy the pixel data correctly if the bitmaps are queried in rapid succession.

C# code fragment:

[StructLayout(LayoutKind.Sequential), Serializable]
public struct BCBitmapInfo
{
    [MarshalAs(UnmanagedType.U4)] public int width;
    [MarshalAs(UnmanagedType.U4)] public int height;
    [MarshalAs(UnmanagedType.SysInt)] public IntPtr colorData;
}

const string BaseCodeDLL = "BaseCode.dll";
[DllImport(BaseCodeDLL)] private static extern IntPtr BCQueryBitmapByName(IntPtr context, [In, MarshalAs(UnmanagedType.LPStr)] String bitmapName);

private Bitmap GetBitmap(String bitmapName)
{
    IntPtr bitmapInfoUnmanaged = BCQueryBitmapByName(baseCodeDLLContext, bitmapName);
    if (bitmapInfoUnmanaged == (IntPtr)0) return null;

    BCBitmapInfo bitmapInfo = (BCBitmapInfo)Marshal.PtrToStructure(bitmapInfoUnmanaged, typeof(BCBitmapInfo));

    return new Bitmap(bitmapInfo.width, bitmapInfo.height, bitmapInfo.width * 4, System.Drawing.Imaging.PixelFormat.Format32bppRgb, bitmapInfo.colorData);
}

private void UpdateReplayImages()
{
    pictureBoxSprites.Image = (Image)GetBitmap("replaySprites");
    pictureBoxTiles.Image = (Image)GetBitmap("replayTiles");
}

C++ Code fragment:

struct BCBitmapInfo
{
    UINT width;
    UINT height;
    BYTE *colorData;
};
class App
{
    ...
    BCBitmapInfo _queryBitmapInfo;
    Bitmap _queryBitmapDataA;
    Bitmap _queryBitmapDataB;
};

BCBitmapInfo* App::QueryBitmapByNameBad(const String &s)
{
    const Bitmap *resultPtr = &_queryBitmapDataA;

    if(s == "replayTiles") _queryBitmapDataA = MakeTilesBitmap();
    else if(s == "replaySprites") _queryBitmapDataA = MakeSpritesBitmap();

    _queryBitmapInfo.width = resultPtr->Width();
    _queryBitmapInfo.height = resultPtr->Height();
    _queryBitmapInfo.colorData = (BYTE*)resultPtr->Pixels();
    return &_queryBitmapInfo;
}

BCBitmapInfo* App::QueryBitmapByNameGood(const String &s)
{
    const Bitmap *resultPtr = NULL;

    if(s == "replayTiles")
    {
        resultPtr = &_queryBitmapDataA;
        _queryBitmapDataA = MakeTilesBitmap();
    }
    else if(s == "replaySprites")
    {
        resultPtr = &_queryBitmapDataB;
        _queryBitmapDataB = MakeSpritesBitmap();
    }

    _queryBitmapInfo.width = resultPtr->Width();
    _queryBitmapInfo.height = resultPtr->Height();
    _queryBitmapInfo.colorData = (BYTE*)resultPtr->Pixels();
    return &_queryBitmapInfo;
}

When running this code in Debug mode and using QueryBitmapByNameBad, whichever bitmap is queried first will have the correct dimensions but the pixel data will be all light green (the second bitmap will render fine); QueryBitmapByNameGood works fine because it uses a different container for each possible query. I don't understand why QueryBitmapByNameBad doesn't behave correctly in a single-threaded application: shouldn't

new Bitmap(bitmapInfo.width, ..., bitmapInfo.colorData);

Be forced to copy the bitmapInfo.colorData out before it returns? Is it necessary to create a new "local storage container" for each variable-length query? Clearly this is complicated in the case of DLLs that can be called from multiple threads but for single-threaded the above approach seems like it should be sufficient.

Upvotes: 0

Views: 510

Answers (1)

Ben Voigt
Ben Voigt

Reputation: 283634

The documentation makes it quite clear that the Bitmap will continue using that memory until it (the Bitmap object) is disposed:

The caller is responsible for allocating and freeing the block of memory specified by the scan0 parameter. However, the memory should not be released until the related Bitmap is released.

from Bitmap Constructor (Int32, Int32, Int32, PixelFormat, IntPtr)

Upvotes: 1

Related Questions