3

在过去的两周里,我一直在徒劳地寻找这个问题的答案,但我很困惑。

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

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

根据这个来源,该异常清楚地表明该方法之前至少被调用过一次。但是看看我的代码。我肯定看不到我在哪里调用了它两次。也许你可以?

无论如何,我强烈怀疑我要么没有完全理解这些对象的使用方式(MemoryStreams、元文件或 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()在创建元文件本身之后直接将 放入构造函数中。我在一些笔记上这样做了,我发现这些笔记说每个实例只能调用一次此方法(例如,请参见此处)。对于喜欢冒险的人,可以在这里找到整个 repo 。

如果有帮助,这里是损坏代码中的异常详细信息(内部异常为空):

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: 
4

2 回答 2

4

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

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

    using (Graphics.FromImage(M)) {}

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

于 2013-12-05T15:29:25.607 回答
0

我不确定这是否只是因为它已在 .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
于 2015-06-22T19:49:56.430 回答