调用GetHenhmetafile()时“参数无效”,试图将内存中的Graphics对象保存为EMF

时间:2013-12-05 02:30:56

标签: c# pinvoke gdi+ metafile .emf

过去两周我一直在搜索这个答案,但我很难过。

我正在使用一些代码,这些代码从一个元文件构造的Graphics对象创建一个示例图像,所有代码都驻留在内存流中,以避免需要Windows.Forms(它是一个控制台应用程序),而且我m使用函数CopyEnhMetaFile(从gdi32.dll导入),将元文件作为真正的EMF保存到磁盘。您可以查看herehereherehere,了解我如何将其整合在一起的基本说明。

当我将其自上而下编写为简单的main()脚本(如codeproject示例中所示)时,它工作正常。但是当我尝试将图元文件/图形对象捆绑到一个带有方法的类时,我无法获得MetafileHandle,因为GetHenhmetafile()会吐出parameter is not valid异常。

根据this source,该异常是一个明确的指示,表明该方法至少曾被调用过一次。但看看我的代码。我肯定看不到我两次调用它的位置。也许你可以吗?

无论如何,我强烈怀疑我要么不完全理解这些对象的使用方式(MemoryStreams,Metafiles或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);

    }
}

这是不起作用的代码。一个注意事项:我最初使用上面的“使用”结构编写它,并且具有相同的错误。所以,我没有重建它,因为使用包装器过早地破坏了某些东西。无论如何,我都得到了同样的错误。

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()放在构造函数中。我在一些笔记上做了这个,我发现每个实例只能调用一次这个方法(例如See here)。对于冒险者,可以找到整个回购here

关闭它的机会很有帮助,这里是破解代码中的异常细节(内部异常为空):

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:\Users\ggauthier\Repositories\Articulate\SimpleEmfGenerator\SimpleEmfGenerator\MetafileExperiment.cs:line 40
       at SimpleEmfGenerator.EmfGenerator.Main() in c:\Users\ggauthier\Repositories\Articulate\SimpleEmfGenerator\SimpleEmfGenerator\EmfGenerator.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: 

2 个答案:

答案 0 :(得分:4)

这是一个初始化问题。核心问题是您从空流创建元文件,并且GDI +似乎会延迟实际创建本机元文件,直到它有充分的理由。有了怪癖(又名bug)GetHenhmetafile()不是一个足够好的理由。

解决方法是强制它这样做,在调用之前放置这行代码:

    using (Graphics.FromImage(M)) {}

请注意,在获取本机句柄之后,从元文件创建Graphics对象是不可能的,您将看到代码失败并出现相同的异常。目前尚不清楚为什么要这样做。

答案 1 :(得分:1)

我不确定这是否仅仅因为它已经在.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