16 November 2008

C++ Interop example. FlashWindowEx

I've been using C++ occasionally instead of P/Invoke. I'm not a c++ programmer.

Here's how to use c++ express 2008 and vb.net 2008 together to call a platform method.

  1. First start C++.
  2. File->New->Project-> Project Type = CLR and use the Class Library template, set the project name to Flasher.
  3. Insert some code into Stdafx.h so that it looks like this:

    #pragma once #define STRICT #define WIN32_LEAN_AND_MEAN #include <windows.h> using namespace System;

    You can see that we have included windows.h, which will give us access to many standard c++ types, such as BOOL. Windows.h is huge, so we set lean and mean too, which strips some infrequently used items -- they won't be included. #define STRICT is similar to Option Strict in vb.net. It stops you being lazy!

  4. Next we need to declare a class and a static method (which is a vb.net Shared method):

    // Flasher.h

    #pragma once using namespace System; namespace Flasher { public ref class Flasher { public: static void Flash(IntPtr); }; }
  5. Now we add method code in the cpp file:

    // This is the main DLL file.

    #include "stdafx.h" #include "Flasher.h" namespace Flasher { void Flasher::Flash(IntPtr hWndManaged) { FLASHWINFO info; ZeroMemory(&info, sizeof(FLASHWINFO)); info.uCount = 5; info.dwFlags = FLASHW_CAPTION; info.dwTimeout = 0; info.hwnd = (HWND) hWndManaged.ToPointer(); info.cbSize = sizeof(info); BOOL result = FlashWindowEx(&info); }


    First we include those header files, so we get all of the methods and types declared in windows.h, and our own Flash method. Next we have the Flash method. To call FlashWindowEx you send a FLASHWINFO structure. If we were going to P/Invoke this, then we would need to declare our own versions of FlashWindowEx and FLASHWINFO. Here we don't have to, as we have the header files included. At the same time, this is a .Net dll, so our Flash method will be usable from VB.Net. After declaring a FLASHWINFO variable, we zero the memory. This is because C++ doesn't do it for us, we could get all sorts of junk in the new structure's fields if we don't do this. The & in &info is telling ZeroMemory the address where the info structure lives. We tell it to flash 5 times. We tell it to flash the caption. We tell it to flash at the default rate (= the systems cursor blink rate) by setting timeout to 0. Next we need to convert the managed IntPtr that is the windows Handle, into the unmanaged HWND type. This involves the ToPointer method, and a cast. Finally we set the structure size and send it off.

  6. Does it build?

  7. No it doesn't. Unresolved token, and unresolved external symbol. There is a linker error, look at the msdn page for FlashWindowEx: Library: Use User32.lib This means that we need to link against User32.lib. We aren't, so we get the error. Right click the project name "Flasher" in the solution explorer. Click properties. Look at: Configuration Properties/Linker/Input/Additional Dependencies Make sure the additional dependencies line is selected and click the ellipsis (...). A quick glance makes you think user32 is linked, but actually the list is a list of thinks that can be inherited, but we have $(NoInherit) set. Add user32.lib to the listbox. Once done, click Ok, and you should see: Additional Dependencies: user32.lib $(NOINHERIT)

  8. Does it build?

  9. No it doesn't. This time it's my antivirus software keeping the file open, causing a file access error: "mt.exe : general error c101008d: Failed to write the updated manifest to the resource of file" One that's sorted, it builds. Add an antivirus exception for mt.exe. For me it lives here: (C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin)

  10. Start a new VB.Net project. Add a button. Click Project->Add Reference, select the browse tab, navigate to the C++ dll that was built and add that.

  11. Add the following to the button click code:


  12. Does it build? For me - yes!

  13. Does it run? For me - no! I'm using Vista 64, and get: "Could not load file or assembly 'Flasher, Version=1.0.3245.7081, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format." This is fixed by setting the Target for the VB.Net program to x86, it was on AnyCPU, the dll is win32! In vb express you might need to do: Tools->Options->Projects and Solutions->General->Show Advanced Build Configurations then, you will need: Build->Configuration Manager->Active Solution Platform -- select <New> and x86

  14. It finally works.

For a simple win32 api call this is too much work. It might be worth it for performance if you are calling something a lot. If you have a complicated api, then it pays off as you don't have to make managed versions of everything. If the method is in a static library then this is a good technique.

No comments:

Post a comment