从 C++ 传递位图时行为不一致; DLL 返回 C#
我有一个 C# 应用程序,需要获取从 C++ DLL 传回的可变长度数据。通常,我按如下方式解决此问题:对于每种实体类型(例如字符串或位图),我都有一个适当类型的持久 C++ 变量。然后,C# 应用程序调用类似“QueryBitmapByName(string bitmapName)”的内容,“契约”是 C# 必须在调用 C++ DLL 中的另一个函数之前处理此变量。这在过去一直有效,但我最近在返回多个位图时遇到了一个我不明白的绊脚石。在发布模式下,行为正常,但在调试模式下,如果快速连续查询位图,则位图传递无法正确复制像素数据。
C# 代码片段:
[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++ 代码片段:
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;
}
在调试模式下运行此代码并使用 QueryBitmapByNameBad 时,无论先查询哪个位图都将具有正确的尺寸,但像素数据将全部为浅绿色(第二个位图将呈现良好); QueryBitmapByNameGood 工作正常,因为它为每个可能的查询使用不同的容器。我不明白为什么 QueryBitmapByNameBad 在单线程应用程序中行为不正确:不应该
new Bitmap(bitmapInfo.width, ..., bitmapInfo.colorData);
在返回之前强制将 bitmapInfo.colorData 复制出来?是否有必要为每个变长查询创建一个新的“本地存储容器”?显然,对于可以从多个线程调用的 DLL,这很复杂,但对于单线程,上述方法似乎应该足够了。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
该文档非常清楚地表明,Bitmap 将继续使用该内存,直到它(Bitmap 对象)被释放:
来自位图构造函数(Int32、Int32、Int32、PixelFormat、IntPtr)
The documentation makes it quite clear that the
Bitmap
will continue using that memory until it (theBitmap
object) is disposed:from Bitmap Constructor (Int32, Int32, Int32, PixelFormat, IntPtr)