在过去的两周里,我徒劳地寻找这个问题的答案,但我被难住了。
我正在使用一些代码,从一个由元文件构建的Graphics对象创建一个示例图像,所有这些都驻留在内存流中,以避免对Windows.Forms(它是一个控制台应用程序)的需要,并且我正在使用函数CopyEnhMetaFile
(从gdi32.dll
导入),将元文件作为一个真正的EMF保存到磁盘中。你可以看看这里,这里,这里和这里,关于我如何把这些放在一起的一些基本笔记。
当我把它自上而下写成简单的main()脚本时,它工作得很好(如代码项目示例中所示)。但是,当我试图用方法将元文件/图形对象绑定到一个类中时,我无法获得MetafileHandle
,因为GetHenhmetafile()
会抛出parameter is not valid
异常。
根据该消息来源,该异常清楚地表明该方法以前至少被调用过一次。但请看一下我的代码。我肯定看不出我在哪里调用过两次。也许你可以?
无论如何,我强烈怀疑我要么没有完全理解这些对象的使用方式(MemoryStream、Metafile或p/Invoked函数)的一些基本内容,要么我缺少了C#类工作方式的一些基本信息,我希望有人能给我一个正确的方向。
[编辑后添加回成功的代码中,并根据建议只保留损坏代码的上下文位]
以下是有效的代码:
class EmfGenerator
{
static void Main()
{
const int width = 450;
const int height = 325;
Metafile m;
using (var stream = new MemoryStream())
{
Graphics offScreenBufferGraphics; //this is a throw-away object needed for the deviceContext
using (offScreenBufferGraphics = Graphics.FromHwndInternal(IntPtr.Zero))
{
IntPtr deviceContextHandle = offScreenBufferGraphics.GetHdc();
m = new Metafile(
stream,
deviceContextHandle,
new RectangleF(0, 0, width, height),
MetafileFrameUnit.Pixel, //scaling only works properly with integers due to decimal truncation, so use milimeters or pixels here
EmfType.EmfPlusOnly); //force GDI+ mode
offScreenBufferGraphics.ReleaseHdc(); //once we have our metafile, we no longer need the context handle
}
}
using (Graphics g = Graphics.FromImage(m))
{
//draw a picture
g.Clear(Color.White);
//etc...
}
// Save it as a metafile
IntPtr iptrMetafileHandle = m.GetHenhmetafile();
CopyEnhMetaFile(iptrMetafileHandle, @"emf_binary_sample.emf"); //this gives us just the metafile
DeleteEnhMetaFile(iptrMetafileHandle);
}
}
这是不起作用的代码。有一点需要注意:我最初是用上面的"using"结构写的,但也有同样的错误。所以,我重建了它,没有,因为使用包装纸可能过早地破坏了一些东西。不管怎样,我都犯了同样的错误。
class MetafileExperiment
{
protected Graphics G; //the working graphics object
protected Metafile M; //the working metafile
protected IntPtr MetafileHandle;
public MetafileExperiment(int startingWidth, int startingHeight)
{
var stream = new MemoryStream();
var bfr = Graphics.FromHwndInternal(IntPtr.Zero);
IntPtr dev = bfr.GetHdc();
M = new Metafile(
stream,
dev,
new RectangleF(0, 0, startingWidth, startingHeight),
MetafileFrameUnit.Pixel, //scaling only works properly with integers due to decimal truncation, so use milimeters or pixels here
EmfType.EmfPlusOnly); //force GDI+ mode
//the handle is needed in order to use the P/Invoke to save out and delete the metafile in memory.
MetafileHandle = M.GetHenhmetafile(); // Parameter is not valid
bfr.ReleaseHdc();
G = Graphics.FromImage(M);
}
}
正如您所看到的,在创建元文件本身之后,我直接将GetHenhmetafile()
放入构造函数中。我在发现的一些注释中这样做了,这些注释表示每个实例只能调用此方法一次(例如,请参见此处)。对于喜欢冒险的人来说,整个回购都可以在这里找到。
偶尔它会有所帮助,以下是损坏代码中的异常详细信息(内部异常为空):
System.ArgumentException was unhandled
_HResult=-2147024809
_message=Parameter is not valid.
HResult=-2147024809
IsTransient=false
Message=Parameter is not valid.
Source=System.Drawing
StackTrace:
at System.Drawing.Imaging.Metafile.GetHenhmetafile()
at SimpleEmfGenerator.MetafileExperiment..ctor(Int32 startingWidth, Int32 startingHeight) in c:UsersggauthierRepositoriesArticulateSimpleEmfGeneratorSimpleEmfGeneratorMetafileExperiment.cs:line 40
at SimpleEmfGenerator.EmfGenerator.Main() in c:UsersggauthierRepositoriesArticulateSimpleEmfGeneratorSimpleEmfGeneratorEmfGenerator.cs:line 108
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
这是一个初始化问题。核心问题是,你从一个空流中创建元文件,而GDI+似乎会延迟本机元文件的实际创建,直到它有充分的理由这样做。有了这个怪癖(又名bug),GetHenhmetafile()并不是一个足够好的理由。
解决方法是强制它这样做,将这行代码放在调用之前:
using (Graphics.FromImage(M)) {}
请注意,在获得本机句柄后,从元文件创建Graphics对象是不可能的,您会看到代码失败并出现相同的异常。目前还不清楚你为什么要这么做。
我不确定这是否只是因为它在.NET 4.0中得到了修复,但我可以在不使用丑陋的DllImport的情况下保存元文件。
很抱歉这是VB.NET而不是C#,但这就是我使用的。。。
Public Shared Sub Test()
Dim ms As New IO.MemoryStream()
Dim img As New Bitmap(1000, 1000)
Dim imgGraphics = Graphics.FromImage(img)
Dim hdc = imgGraphics.GetHdc()
Dim mf = New Metafile(ms, hdc, EmfType.EmfPlusOnly)
imgGraphics.ReleaseHdc()
' Important - The above is just a test. In production code, you should eventually dispose of stuff
Using g = Graphics.FromImage(mf)
g.DrawRectangle(Pens.Black, 20, 30, 15, 16)
g.DrawRectangle(Pens.Black, 140, 130, 15, 16)
End Using
' Note: it's important that the Graphics used to draw into the MetaFile is disposed
' or GDI+ won't flush.
' Easy Way
Dim buffer1 = ms.GetBuffer() ' This produces a 444 byte buffer given the example above
' The Hard way
Dim enhMetafileHandle = mf.GetHenhmetafile
Dim bufferSize = GetEnhMetaFileBits(enhMetafileHandle, 0, Nothing)
Dim buffer(bufferSize - 1) As Byte
Dim ret = GetEnhMetaFileBits(enhMetafileHandle, bufferSize, buffer)
End Sub