4

I'm trying to make use of the ImageMagick COM object (ImageMagickObject) in a .NET library. This library is intended to be called from IronRuby, but that isn't all that important. I want to take this approach because it will fit with my existing calls, which currently call the ImageMagick binaries as external processes. The COM object will take the same arguments as the binaries, but will save the process creation and are about 5x faster overall.

My only hurdle is that the "Compare" method for the COM object returns its result to STDERR. This is also a problem with the binary, but it's easy to pipe that back into STDOUT, where I was expecting it. With the COM object, I'm getting my results from function return values.

How can I redirect the result from "Compare" to a string buffer or even a file instead of STDERR?

I have tried the following, which does stop the output from reaching STDERR, but it doesn't write to the file as expected:

using ImageMagickObject;
...

public class ImageMagickCOM
{
    [DllImport("Kernel32.dll", SetLastError = true)]
    public static extern int SetStdHandle(int device, IntPtr handle);

    private const int STDOUT_HANDLE = -11;
    private const int STDERR_HANDLE = -12;

    private ImageMagickObject.MagickImage magickImage = null;

    private FileStream filestream = null;
    private StreamWriter streamwriter = null;

    public ImageMagickCOM()
    {
        IntPtr handle;
        int status;

        filestream = new FileStream("output.txt", FileMode.Create);
        streamwriter = new StreamWriter(filestream);
        streamwriter.AutoFlush = true;

        //handle = filestream.Handle; // deprecated
        handle = filestream.SafeFileHandle.DangerousGetHandle(); // replaces filestream.handle
        status = SetStdHandle(STDOUT_HANDLE, handle);
        status = SetStdHandle(STDERR_HANDLE, handle);

        Console.SetOut(streamwriter);
        Console.SetError(streamwriter);

        magickImage = new ImageMagickObject.MagickImage();
    }

    public string Compare()
    {
        object[] args = new object[] { "-metric", "AE", "-fuzz", "10%", "imageA.jpg", "imageB.jpg", "diff.png" };
        return (string)this.magickImage.Compare(ref args);
    }

    public void Close()
    {
        if (this.magickImage != null)
        {
            Marshal.ReleaseComObject(magickImage);
            this.magickImage = null;
        }
        if (this.streamwriter != null)
        {
            this.streamwriter.Flush();
            this.streamwriter.Close();
            this.streamwriter = null;
            this.filestream = null;
        }
    }
}

Only the "Compare" action seems to use STDERR to send a result (it uses the return value as a success indicator). All of the other methods (Identify, Convert, Mogrify, etc) work as you would expect.

For reference, it gets called something like this (from IronRuby):

require 'ImagingLib.dll'
im = ImagingLib::ImageMagickCOM.new
im.compare # returns nil
im.close

And output.txt is created, but empty. Nothing gets printed to STDOUT or STDERR.

EDITS: For clarity regarding streamwriter flush/close and how the sample is used from IronRuby.

cgyDeveloper
  • 1,901
  • 3
  • 21
  • 34

4 Answers4

1

Did you try Disposing the(or flushing) writer and stream? It could have died stuck in the buffer. A using block might help there.

    using(filestream = new FileStream("output.txt", FileMode.Create))
    using(streamwriter = new StreamWriter(filestream))
    {

... }

James Michael Hare
  • 37,767
  • 9
  • 73
  • 83
1

After adding the debug option I finally got some text in the file.

Is that the result you are expecting? If so, see debug option of the compare command-line tool.

Please note that setting the Console.Error or Console.Out to streamwriter will cause an exception if you try to log anything directly or indirectly using the aforementioned properties.

If you really need to set those properties, set them to another instance of StreamWriter. If you do not need them, omit the calls to Console.SetOut and Console.SetError.

I have also noticed that once ImageMagickObject.MagickImage instance is created, it is not possible to redirect the standard error. If the standard error redirection needs to be undone, or performed multiple times, try executing the code in another application domain.

  • Compare definitely writes to STDERR. If I take out the SetStdHandle calls, it will write to STDERR every time. With those calls in place, it never does (nor does it write to file). – cgyDeveloper Feb 14 '11 at 16:01
  • This isn't meant to be run from the command line, so that solution won't be valid. It needs to be available as a library call. – cgyDeveloper Feb 14 '11 at 16:03
  • I should also add one last note that the COM interop does not necessarily use the same handle as Console.Error and, in my case, it does not. Console.Error redirection will work for calls from .NET, but it is not always sufficient to redirect STDERR calls from unmanaged code (such as the COM object I'm interacting with here). So calling Console.Error is not a valid test for my condition, although it does appear logical and your solution would work in that case. – cgyDeveloper Feb 14 '11 at 16:04
  • Could you provide the link to version of `ImageMagickObject` you are using? Is source code is available, I would like to see how `Compare` method is writing to `stderr`. I hope it is not caching the previous `stderr` handle somewhere inside the method or a function called by the method. –  Feb 14 '11 at 18:01
  • I'm using ImageMagick 6.6.7, their source package FTP server is at ftp://ftp.imagemagick.org/pub/ImageMagick/. The source in question is in the directory at ImageMagick\contrib\win32\ATL7\ImageMagickObject. I've not installed from source, but used their Windows installer located at http://www.imagemagick.org/script/binary-releases.php#windows. – cgyDeveloper Feb 14 '11 at 18:43
  • As to your question, the key statements being used here are pretty standard fare along the lines of: fprintf(stderr, "%g", distortion); fprintf(stderr, "\n"); – cgyDeveloper Feb 14 '11 at 18:50
  • According to [stdin, stdout, stderr](http://msdn.microsoft.com/en-us/library/3x292kth.aspx), `stderr` is a constant. If that is the case, `SetStdHandle` will not be enough. I have seen an example that might be of help. Will post as soon as I find it again. –  Feb 14 '11 at 18:54
  • @cgyDeveloper: Found it, will post as soon as I port it to C#. –  Feb 14 '11 at 19:19
  • Much appreciated, I look forward to it. – cgyDeveloper Feb 14 '11 at 23:34
  • @cgyDeveloper: I was unable to get any error from `Compare` when commenting the code that sets the standard error handle. Will give it another try later this day. –  Feb 15 '11 at 08:51
  • Very strange that only when "debug" is used, will the STDERR output be redirected to the file. As a side note, no, I don't actually need the Console.Out and Console.Error redirection if SetStdHandle works. I'll have to dig into the IM source to see how the debug output is treated differently. Thanks for the digging :) – cgyDeveloper Feb 17 '11 at 16:05
  • It seems that by default nothing is actually written to `stderr`, therefore there is nothing to redirect. You are welcome :) –  Feb 17 '11 at 16:15
  • The debug statements in ImageMagick use windows.h method OutputDebugString, which apparently are sent to the standard handle for Windows' STDERR. However, this still doesn't fix all of the fprintf(stderr, ...) calls, including the critical one that I need. – cgyDeveloper Feb 17 '11 at 17:26
  • There absolutely is something written to stderr. I see it in the IronRuby console if I don't redirect the handle, and I suspect you'd see it in any other .NET console created for the process. – cgyDeveloper Feb 17 '11 at 17:27
  • Could you add the text you are expecting in the redirected file so I know when I hit it? –  Feb 17 '11 at 17:38
  • It will be a number representing the number of pixels that differ between the two files followed by a new line. If you try using the binary compare.exe it should become obvious what the output looks like. – cgyDeveloper Feb 17 '11 at 17:47
0

It looks like .Handle is deprecated, have you tried .SafeFileHandle.DangerousGetHandle() instead?

Ref: http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.dangerousgethandle.aspx

(Additionally: can you confirm you close the StreamWriter after the data has been written? Like, if you move it to just before the app closes, to see if the data makes its way out?)

Kieren Johnstone
  • 41,277
  • 16
  • 94
  • 144
  • Positive about the closing the streamwriter. I've also tried both handle methods since VS gave me the deprecation warning (they seem to be equivalent). – cgyDeveloper Feb 10 '11 at 16:41
0

According to the documentation "FileShare.Read is the default for those FileStream constructors without a FileShare parameter"... Perhaps it could help to have FileAccess.Read, FileAccess.Write, FileShare.Read and FileShare.Write set on in the FileStream c-tor?

I don't have ImageMagick exe to play with... if you can post the link, it would be great.

globalheap
  • 107
  • 4
  • File permissions aren't a problem in this case, ImageMagick is here: http://imagemagick.org/script/binary-releases.php#windows. Make sure to install the COM object during install to use it as I am in the example. – cgyDeveloper Feb 17 '11 at 17:45