C# and the Platform Invoke system

What is the platform invoke system?

In short, it offers a way to use unmanaged code in C#, which is a managed language. Learning to use it can open up access to a lot of existing, well tested, well supported library’s written in unmanaged code.

The easiest example of this is the Win32 api.

How do I use it?

Pinvoke.net is great.

In a lot of cases, all you need to do is simply find the API you want to use on msdn, and then check this website. Often the website will contain the pinvoke signature or native structure with examples of how to use it.

For instance, the FlashWindow api can be found on pinvoke easily and has an example. Here is the code from the pinvoke page found here for FlashWindow API found on msdn here.


[DllImport("user32.dll")]
 private static extern bool FlashWindow(IntPtr hwnd, bool bInvert);

public static void FlashWindow(System.Windows.Forms.Form window)
 {
 FlashWindow(window.Handle, false);
 }

But it is not that great

Sometimes you will need to do a little research on your own. Pinvoke,net does not always have it right, and some times no examples, and some times its just straight up missing from the site.

How to make use of the invoke platform on your own

I think the best way to show how to make use of pinvoke is to go over how I go about creating a pinvoke call.

I recently wanted to record AVI videos. So lets choose the method AVIFileCreateStream found in the avifil32.dll. You may find the msdn link to this method here.

Right away it gives us a description of the method and how it looks in C++. This is important because we are using that C++ code in our C# program.

Here is what it shows us for the method description and signature.
// The AVIFileCreateStream function creates a new stream in an existing-
// file and creates an interface to the new stream.
STDAPI AVIFileCreateStream(
PAVIFILE pfile,
PAVISTREAM *ppavi,
AVISTREAMINFO *psi
);
Note* If you do not know any C++, now might be a time to learn a little. It can be good to know a little about unmanaged languages to help you understand the importance of proper object management and dispose patterns in C#.

So one thing to notice and it is why I chose this example is it has a 3rd parameter named AVISTREAMINFO. This is a structure passed as a parameter, so we will google the msdn page for it. Once again, the AVISTREAMINFO is found on msdn here.

As msdn did with the AVIFileCreateStream method, it shows the basic description of the structure and its signature. It looks like this.

// The AVISTREAMINFO structure contains information for a single stream.
typedef struct {
  DWORD fccType;
  DWORD fccHandler;
  DWORD dwFlags;
  DWORD dwCaps;
  WORD  wPriority;
  WORD  wLanguage;
  DWORD dwScale;
  DWORD dwRate;
  DWORD dwStart;
  DWORD dwLength;
  DWORD dwInitialFrames;
  DWORD dwSuggestedBufferSize;
  DWORD dwQuality;
  DWORD dwSampleSize;
  RECT  rcFrame;
  DWORD dwEditCount;
  DWORD dwFormatChangeCount;
  TCHAR szName[64];
} AVISTREAMINFO;
Converting the C++ code to usable C#

Lets get right to it and show our C# versions of the above struct, included is the RECT struct as it is contained in the avi struct as well.

AVISTREAMINFO:
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct AVISTREAMINFO
        {
            public int fccType;
            public int fccHandler;
            public int dwFlags;
            public int dwCaps;
            public short wPriority;
            public short wLanguage;
            public int dwScale;
            public int dwRate;
            public int dwStart;
            public int dwLength;
            public int dwInitialFrames;
            public int dwSuggestedBufferSize;
            public int dwQuality;
            public int dwSampleSize;
            public RECT rcFrame;
            public int dwEditCount;
            public int dwFormatChangeCount;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
            public string szName;
        }

RECT:


        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left { get; set; }
            public int Top { get; set; }
            public int Right { get; set; }
            public int Bottom { get; set; }

            public RECT(int left, int top, int right, int bottom)
            {
                Left = left;
                Top = top;
                Right = right;
                Bottom = bottom;
            }

            public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom)
            {

            }

            public int X
            {
                get { return Left; }
                set { Right -= (Left - value); Left = value; }
            }

            public int Y
            {
                get { return Top; }
                set { Bottom -= (Top - value); Top = value; }
            }

            public int Height
            {
                get { return Bottom - Top; }
                set { Bottom = value + Top; }
            }

            public int Width
            {
                get { return Right - Left; }
                set { Right = value + Left; }
            }

            public System.Drawing.Point Location
            {
                get { return new System.Drawing.Point(Left, Top); }
                set { X = value.X; Y = value.Y; }
            }

            public System.Drawing.Size Size
            {
                get { return new System.Drawing.Size(Width, Height); }
                set { Width = value.Width; Height = value.Height; }
            }
        }

Now  you have all you need to use this unmanaged code in your managed code. Since we have all the structures use in the parameters of the AVIFileCreateStream method, we define it.


        [DllImport("avifil32.dll")]
        public static extern int AVIFileCreateStream(
            IntPtr pfile,
            out IntPtr ppavi,
            ref Avistreaminfo psi);

 

You now have a working pinvoke! For an example of a practical use of pinvoke, here is a class that contains most of the useful avifil32.dll and can be used to create avi readers/writers in c#.

You can find a complete AVI reader and writer class that uses the below pinvoke class in my recent contribute to a github project on my fork found here .

 


    //  Static class containing pinvoke methods for the avifil32.dll api. 
    //  Review msdn and other avifil32.dll documentation for usage.
    public static class Avifil32
    {
        // Warnings disabled due to the nature of pinvokes.
        // Review msdn and other existing documentation for more information on contents contained here.
        // AVI Functions.

        // Initialize the AVIFile library
        [DllImport("avifil32.dll")]
        public static extern void AVIFileInit();

        // Exit the AVIFile library 
        [DllImport("avifil32.dll")]
        public static extern void AVIFileExit();

        // Open an AVI file
        [DllImport("avifil32.dll", CharSet = CharSet.Unicode)]
        public static extern int AVIFileOpen(
            out IntPtr ppfile,
            string szFile,
            OpenFileMode mode,
            IntPtr pclsidHandler);

        // Release an open AVI stream
        [DllImport("avifil32.dll")]
        public static extern int AVIFileRelease(
            IntPtr pfile);

        // Get address of a stream interface that is associated
        // with a specified AVI file
        [DllImport("avifil32.dll")]
        public static extern int AVIFileGetStream(
            IntPtr pfile,
            out IntPtr ppavi,
            int fccType,
            int lParam);

        // Create a new stream in an existing file and creates an interface to the new stream
        [DllImport("avifil32.dll")]
        public static extern int AVIFileCreateStream(
            IntPtr pfile,
            out IntPtr ppavi,
            ref Avistreaminfo psi);

        // Release an open AVI stream
        [DllImport("avifil32.dll")]
        public static extern int AVIStreamRelease(
            IntPtr pavi);

        // Set the format of a stream at the specified position
        [DllImport("avifil32.dll")]
        public static extern int AVIStreamSetFormat(
            IntPtr pavi,
            int lPos,
            ref Bitmapinfoheader lpFormat,
            int cbFormat);

        // Get the starting sample number for the stream
        [DllImport("avifil32.dll")]
        public static extern int AVIStreamStart(
            IntPtr pavi);

        // Get the length of the stream
        [DllImport("avifil32.dll")]
        public static extern int AVIStreamLength(
            IntPtr pavi);

        // Obtain stream header information
        [DllImport("avifil32.dll", CharSet = CharSet.Unicode)]
        public static extern int AVIStreamInfo(
            IntPtr pavi,
            ref Avistreaminfo psi,
            int lSize);

        // Prepare to decompress video frames from the specified video stream
        [DllImport("avifil32.dll")]
        public static extern IntPtr AVIStreamGetFrameOpen(
            IntPtr pavi,
            ref Bitmapinfoheader lpbiWanted);

        [DllImport("avifil32.dll")]
        public static extern IntPtr AVIStreamGetFrameOpen(
            IntPtr pavi,
            int lpbiWanted);

        // Releases resources used to decompress video frames
        [DllImport("avifil32.dll")]
        public static extern int AVIStreamGetFrameClose(
            IntPtr pget);

        // Return the address of a decompressed video frame
        [DllImport("avifil32.dll")]
        public static extern IntPtr AVIStreamGetFrame(
            IntPtr pget,
            int lPos);

        // Write data to a stream
        [DllImport("avifil32.dll")]
        public static extern int AVIStreamWrite(
            IntPtr pavi,
            int lStart,
            int lSamples,
            IntPtr lpBuffer,
            int cbBuffer,
            int dwFlags,
            IntPtr plSampWritten,
            IntPtr plBytesWritten);

        // Retrieve the save options for a file and returns them in a buffer
        [DllImport("avifil32.dll")]
        public static extern int AVISaveOptions(
            IntPtr hwnd,
            int flags,
            int streams,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] ppavi,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] plpOptions);

        // Free the resources allocated by the AVISaveOptions function
        [DllImport("avifil32.dll")]
        public static extern int AVISaveOptionsFree(
            int streams,
            [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] IntPtr[] plpOptions);

        // Create a compressed stream from an uncompressed stream and a
        // compression filter, and returns the address of a pointer to
        // the compressed stream
        [DllImport("avifil32.dll")]
        public static extern int AVIMakeCompressedStream(
            out IntPtr ppsCompressed,
            IntPtr psSource,
            ref Avicompressoptions lpOptions,
            IntPtr pclsidHandler);

        // Replacement of mmioFOURCC macros
        public static int MmioFourcc(string str)
        {
            return (byte) str[0] |
                   ((byte) str[1] << 8) |
                   ((byte) str[2] << 16) |
                   ((byte) str[3] << 24);
        }

        // Inverse of mmioFOURCC
        public static string decode_mmioFOURCC(int code)
        {
            var chs = new char[4];

            for (var i = 0; i < 4; i++)
            {
                chs[i] = (char) (byte) ((code >> (i << 3)) & 0xFF);
                if (!char.IsLetterOrDigit(chs[i]))
                {
                    chs[i] = ' ';
                }
            }
            return new string(chs);
        }

        // --- public methods

        // Version of AVISaveOptions for one stream only
        //
        // I don't find a way to interop AVISaveOptions more likely, so the
        // usage of original version is not easy. The versionn makes it more
        // usefull
        //
        public static int AviSaveOptions(IntPtr stream, ref Avicompressoptions opts, IntPtr owner)
        {
            var streams = new IntPtr[1];
            var infPtrs = new IntPtr[1];

            // alloc unmanaged memory
            var mem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Avicompressoptions)));

            // copy from managed structure to unmanaged memory
            Marshal.StructureToPtr(opts, mem, false);

            streams[0] = stream;
            infPtrs[0] = mem;

            // show dialog with a list of available compressors and configuration
            var ret = AVISaveOptions(IntPtr.Zero, 0, 1, streams, infPtrs);

            // copy from unmanaged memory to managed structure
            opts = (Avicompressoptions) Marshal.PtrToStructure(mem, typeof(Avicompressoptions));

            // free AVI compression options
            AVISaveOptionsFree(1, infPtrs);

            // clear it, because the information already freed by AVISaveOptionsFree
            opts.cbFormat = 0;
            opts.cbParms = 0;
            opts.lpFormat = 0;
            opts.lpParms = 0;

            // free unmanaged memory
            Marshal.FreeHGlobal(mem);

            return ret;
        }
    }

 

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s