"Parameter Not Valid"调用 GetHenhmetafile(),尝试将内存中的图形对象保存为 EMF



在过去的两周里,我徒劳地寻找这个问题的答案,但我被难住了。

我正在使用一些代码,从一个由元文件构建的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

最新更新