Showing posts with label API. Show all posts
Showing posts with label API. Show all posts

15 June 2010

HDS_FILTERBAR

There’s a ListViewFilter control in C# on codeproject from 2003. It displays a filter bar in the column headers by setting HDS_FILTERBAR.

If we start off with a ListView showing some random data in a few columns:

Filterbar3

And set the HDS_FILTERBAR style, then it will display the filter bar:

Filterbar2

But there’s a problem – it hasn’t resized the header controls – they need to be taller to fit the text in properly. The codeproject code makes the column resize using a hack. Here’s how it shows the filter bar:

// set/reset the flag for the filterbar
if ( hdr_filter ) style |= HDS_FILTR;
else style ^= HDS_FILTR;
SetWindowLong( Handle, W32_GWL.GWL_STYLE, style );

And here’s the self-confessed kludge that gets it to resize:

// now we have to resize this control.  we do this by sending
// a set item message to column 0 to change it's size.  this
// is a kludge but the invalidate and others just don't work.
hdr_hditem.mask = W32_HDI.HDI_HEIGHT;
SendMessage( Handle, W32_HDM.HDM_GETITEMW, 0, ref hdr_hditem );
hdr_hditem.cxy += ( hdr_filter ) ? 1 : -1;
SendMessage( Handle, W32_HDM.HDM_SETITEMW, 0, ref hdr_hditem );

It’s sending a message to alter a property of the header – using HDI_HEIGHT. If we look at the windows header files to see how it’s defined:

#define HDI_WIDTH               0x0001
#define HDI_HEIGHT              HDI_WIDTH

It’s actually the same as the width. The kludge increases the width of the column header by 1 when the filter is shown, and decreases it when the filter is not shown. Altering the width must be enough to make windows resize the control.

Searching for alternate solutions I found one that mentions using MoveWindow to resize the header control, so I tried that. Unfortunately when you resize the header it then overlaps the parent listview, hiding one or two of the ListViewItems by overlapping them.

The info needed to do it properly is in the “Header Controls” topic in the MSDN library, in the “header control size and position” paragraph. We have to send the HDM_LAYOUT message to the control, specifying the bounds of the parent. On return we get a WINDOWPOS structure which tells us the best bounds for the header so that it will sit in the bounds of the parent. All we then have to do is to resize the header control using SetWindowPos, and alter the bounds of the parent control so that its top lies below the header and its height isn’t too big.

HDLAYOUT layout = new HDLAYOUT();
RECT rect = new RECT();
rect.Right = parent.ClientSize.Width;
rect.Bottom = parent.ClientSize.Height;
layout.prc = Marshal.AllocHGlobal(Marshal.SizeOf(rect));
Marshal.StructureToPtr(rect, layout.prc, true);
layout.pwpos = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINDOWPOS)));
lresult = NativeMethods.SendMessage(Handle, HDM_LAYOUT, IntPtr.Zero, ref layout);
Marshal.FreeHGlobal(layout.prc);
WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(layout.pwpos, typeof(WINDOWPOS));
Marshal.FreeHGlobal(layout.pwpos);
bool res = NativeMethods.SetWindowPos(Handle, IntPtr.Zero, pos.X, pos.Y, pos.Width, pos.Height, 
    SetWindowPosFlags.NoMove | SetWindowPosFlags.NoZOrder | SetWindowPosFlags.FrameChanged);
res = NativeMethods.SetWindowPos(parent.Handle, IntPtr.Zero, 0, pos.Height, parent.Width, parent.Height - pos.Height, 
    SetWindowPosFlags.NoMove | SetWindowPosFlags.NoZOrder | SetWindowPosFlags.FrameChanged);
ClearAllFilters();  

Now the filterbar is shown with the correct size, it doesn’t overlap the top of the parent listview, and it resizes correctly when the filter bar is removed.

Filterbar1

23 January 2009

Set default Wave Out Audio Device - VB.Net / DRVM_MAPPER_PREFERRED_SET

You can set the default audio playback device in windows 2000, Me, and XP with the DRVM_MAPPER_PREFERRED_SET message, which is sent with waveOutMessage().

It doesn't work on vista - you get MMSYSERR_NOTSUPPORTED (8), returned.
You can do it in Vista - see http://vachanger.sourceforge.net/

Anyway, the code uses the Windows Media .Net library.

Imports MultiMedia ' http://windowsmedianet.sourceforge.net/
Imports System.Runtime.InteropServices
Imports System.ComponentModel

Public Class Form1

    Private DevicesComboBox As New ComboBox
    Private DefaultDeviceLabel As New Label
    Private WithEvents SetDefaultButton As New Button
    Private Const DRVM_MAPPER_PREFERRED_GET As Integer = &H2015
    Private Const DRVM_MAPPER_PREFERRED_SET As Integer = &H2016
    Private WAVE_MAPPER As New IntPtr(-1)

    ' This just brings together a device ID and a WaveOutCaps so 
    ' that we can store them in a combobox.
    Private Structure WaveOutDevice

        Private m_id As Integer
        Public Property Id() As Integer
            Get
                Return m_id
            End Get
            Set(ByVal value As Integer)
                m_id = value
            End Set
        End Property

        Private m_caps As WaveOutCaps
        Public Property WaveOutCaps() As WaveOutCaps
            Get
                Return m_caps
            End Get
            Set(ByVal value As WaveOutCaps)
                m_caps = value
            End Set
        End Property

        Sub New(ByVal id As Integer, ByVal caps As WaveOutCaps)
            m_id = id
            m_caps = caps
        End Sub

        Public Overrides Function ToString() As String
            Return WaveOutCaps.szPname
        End Function

    End Structure

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' I do use the IDE for this stuff normally... (in case anyone is wondering)
        Me.Controls.AddRange(New Control() {DevicesComboBox, DefaultDeviceLabel, SetDefaultButton})
        DevicesComboBox.Location = New Point(5, 5)
        DevicesComboBox.DropDownStyle = ComboBoxStyle.DropDownList
        DevicesComboBox.Width = Me.ClientSize.Width - 10
        DevicesComboBox.Anchor = AnchorStyles.Left Or AnchorStyles.Right
        DefaultDeviceLabel.Location = New Point(DevicesComboBox.Left, DevicesComboBox.Bottom + 5)
        DefaultDeviceLabel.AutoSize = True
        SetDefaultButton.Location = New Point(DefaultDeviceLabel.Left, DefaultDeviceLabel.Bottom + 5)
        SetDefaultButton.Text = "Set Default"
        SetDefaultButton.AutoSize = True
        RefreshInformation()
    End Sub

    Private Sub RefreshInformation()
        PopulateDeviceComboBox()
        DisplayDefaultWaveOutDevice()
    End Sub

    Private Sub PopulateDeviceComboBox()
        DevicesComboBox.Items.Clear()
        ' How many wave out devices are there? WaveOutGetNumDevs API call.
        Dim waveOutDeviceCount As Integer = waveOut.GetNumDevs()
        For i As Integer = 0 To waveOutDeviceCount - 1
            Dim caps As New WaveOutCaps
            ' Get a name - its in a WAVEOUTCAPS structure. 
            ' The name is truncated to 31 chars by the api call. You probably have to 
            ' dig around in the registry to get the full name.
            Dim result As Integer = waveOut.GetDevCaps(i, caps, Marshal.SizeOf(caps))
            If result <> MMSYSERR.NoError Then
                Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
                Throw New Win32Exception("GetDevCaps() error, Result: " & result.ToString("x8") & ", " & err.ToString)
            End If
            DevicesComboBox.Items.Add(New WaveOutDevice(i, caps))
        Next
        DevicesComboBox.SelectedIndex = 0
    End Sub

    Private Sub DisplayDefaultWaveOutDevice()
        Dim currentDefault As Integer = GetIdOfDefaultWaveOutDevice()
        Dim device As WaveOutDevice = DirectCast(DevicesComboBox.Items(currentDefault), WaveOutDevice)
        DefaultDeviceLabel.Text = "Defualt: " & device.WaveOutCaps.szPname
    End Sub

    Private Function GetIdOfDefaultWaveOutDevice() As Integer        
        Dim id As Integer = 0
        Dim hId As IntPtr
        Dim flags As Integer = 0
        Dim hFlags As IntPtr
        Dim result As Integer
        Try
            ' It would be easier to declare a nice overload with ByRef Integers.
            hId = Marshal.AllocHGlobal(4)
            hFlags = Marshal.AllocHGlobal(4)
            ' http://msdn.microsoft.com/en-us/library/bb981557.aspx
            result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, hId, hFlags)
            If result <> MMSYSERR.NoError Then
                Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
                Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
            End If
            id = Marshal.ReadInt32(hId)
            flags = Marshal.ReadInt32(hFlags)
        Finally
            Marshal.FreeHGlobal(hId)
            Marshal.FreeHGlobal(hFlags)
        End Try
        ' There is only one flag, DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY, defined as 1
        ' "When the DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY flag bit is set, ... blah ...,  
        ' the waveIn and waveOut APIs use only the current preferred device and do not search 
        ' for other available devices if the preferred device is unavailable. 
        Return id
    End Function

    Private Sub SetDefaultButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SetDefaultButton.Click
        If DevicesComboBox.Items.Count = 0 Then Return
        Dim selectedDevice As WaveOutDevice = DirectCast(DevicesComboBox.SelectedItem, WaveOutDevice)
        SetDefault(selectedDevice.Id)
        RefreshInformation()
    End Sub

    Private Sub SetDefault(ByVal id As Integer)
        Dim defaultId As Integer = GetIdOfDefaultWaveOutDevice()
        If defaultId = id Then Return ' no change.
        Dim result As Integer
        ' So here we say "change the Id of the device that has id id to 0", which makes it the default.
        result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_SET, New IntPtr(id), IntPtr.Zero)
        If result <> MMSYSERR.NoError Then
            Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
            Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
        End If
    End Sub

End Class

15 January 2009

Enable/Disable a device programmatically with VB.Net using the setup api.

Very quickly...

Check in device manager to see if the device has "Disable" as an option when you R click it. If so then look at the properties, and find the "class guid" and "device instance id".

1) Get a handle to a device info set using SetupDiGetClassDevs - this will get all devices in a class.
2) Get device info data for each device in the class using SetupDiEnumDeviceInfo
3) Get the device instance id for each device using the device info data from (2) and SetupDiGetDeviceInstanceId.
4) Fill in a structure to say you want a property change and call SetupDiSetClassInstallParams. This sets the property in the device info set.
5) Call SetupDiCallClassInstaller to get the installer to make the changes stick.

24 December 2008

VB.Net enumerate the system tray icons/buttons

Another msdn post. This one asked how to get the paths to the icons in the system tray. This sounds pretty simple, but isn't. You can start off by finding the window with Spy++, and then getting a handle to it. Then SendMessage with TB_BUTTONCOUNT to get the number of buttons. (TBBUTTON = TaskBarButton). Then you send TB_GETBUTTON repeatedly and get back TBBUTTON structures with information about the buttons. (See the last blog post for info on the x86/x64 mess)

typedef struct _TBBUTTON {    int        iBitmap;
    int        idCommand;
    BYTE     fsState;
    BYTE     fsStyle;
#ifdef _WIN64
    BYTE     bReserved[6]     // padding for alignment
#elif defined(_WIN32)
    BYTE     bReserved[2]     // padding for alignment
#endif
   DWORD_PTR   dwData;
    INT_PTR          iString;
} TBBUTTON, NEAR *PTBBUTTON *LPTBBUTTON;

The dwData field points to a NOTIFYICONDATA structure with some more gobbledegook:

typedef struct _NOTIFYICONDATA {
    DWORD cbSize;
    HWND hWnd;
    UINT uID;
    UINT uFlags;
    UINT uCallbackMessage;
    HICON hIcon;
    TCHAR szTip[64];
    DWORD dwState;
    DWORD dwStateMask;
    TCHAR szInfo[256];
    union {
        UINT uTimeout;
        UINT uVersion;
    };
    TCHAR szInfoTitle[64];
    DWORD dwInfoFlags;
    GUID guidItem;
    HICON hBalloonIcon;
} NOTIFYICONDATA, *PNOTIFYICONDATA;

We don't need all that, so we could trim it down. Earlier versions of windows used a smaller version anyway.

The difficulty lies in the fact that some of the pointers don't point anywhere useful. Say my process has a button at address 100. That address is local to my process, it has its own virtual memory space. If another process reads that pointer, then the value - 100 - will not mean anything as the other process has its own virtual memory space that maps to a different block of real memory. In our program, we SendMessage to the process hosting the tray buttons asking it to stick a TBBUTTON structure into some memory that is allocated in our home process. Unfortunately the pointer to the memory has no meaning to the other process, so the button goes astray.

To get around this, we ask the other process to create a blob of memory big enough for a tray button using VirtualAllocEx. Then we SendMessage, asking the other process to put the button into this blob of memory. Finally we read the contents of that memory with ReadProcessMemory:

Dim tbb As New TBBUTTON32
Dim friendlyTB As New TrayButton
friendlyTB.SetTrayIndex(index)
Dim pButton As IntPtr = IntPtr.Zero
Try
    ' Create memory in the explorer process to store a TBBUTTON strucutre:
    pButton = VirtualAllocEx(hProcess, Nothing, tbb.Size, MEM_COMMIT, PAGE_READWRITE)
    ' Use SendMessage to request that the memory is filled with the TBBUTTON @ index.
    Dim bResult As Boolean = SendMessage(hwndTrayToolbar, TB_GETBUTTON, index, pButton)
    If bResult = False Then Throw New Win32Exception
    ' And read the memory from the other process:
    Dim bytesReturned As Integer
    bResult = ReadProcessMemory(hProcess, pButton, tbb, tbb.Size, bytesReturned)
    If bResult = False Then Throw New Win32Exception

And here's an app that doesn't work too well as there are many complications to do with animated icons, OSes, x86/x64 that I haven't touched...

Structures that vary in size on x86/x64 - what to do?

Some structures can be difficult to marshal, as their size varies on x86 compared to x64:

typedef struct _TBBUTTON {
   int         iBitmap;
   int         idCommand;
   BYTE     fsState;
   BYTE     fsStyle;
#ifdef _WIN64
   BYTE     bReserved[6]     // padding for alignment
#elif defined(_WIN32)
   BYTE     bReserved[2]     // padding for alignment
#endif
   DWORD_PTR   dwData;
   INT_PTR          iString;
} TBBUTTON, NEAR *PTBBUTTON *LPTBBUTTON;
 
The layouts in x86 and x64 would look like this:
 
image 
 
In C the standard is for, say, an int to stay within a 4 byte boundary of the start of the structure. A short would
not cross a 2 byte boundary. Bytes can go where they like (well, they shouldn't cross the byte boundary, which I
guess they could by having bits spread over 2 bytes!) 
A pointer on x86 is a 4 byte value, and shouldn't cross a 4 byte boundary. A pointer on x64 is an 8 byte value, and
shouldn't cross an 8 byte boundary. Phew. Enough about byte boundaries...
TBBUTTON causes problems as it has the two BYTE fields in the middle. We need to pad, to get the dwData field
to start at a suitable location. Once it is in the correct place, iString will be fine.
With .Net we have a few ways to arrange structures. We can use StructLayout(LayoutKind.Sequential, Pack:=x).
Sequential means that .Net is not allowed to move the fields around at runtime, we want them to be arranged
in memory in the order that they appear in our declaration, this will match the C behaviour. Pack allows you to
specify the boundary to align the fields against. Often you would use sequential and pack = 1, then add fields of
padding to get it aligned.
Alternatively, you can use StructLayout(LayoutKind.Explicit) and hard-code where the fields should go. But,
this will only work if the structures have the same layout in x86 and x64. (Unfortunately you must use a
constant for the field offset, so you can't calculate it at runtime).
For TBBUTTON we are stuffed. There is no arrangement of pack and padding so that "one structure fits all", we will
need 2 declarations. And then we'll choose between them at runtime. (I think it might also be possible to just
compile it for x86 and run it under WOW64 on x64...)
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure TBBUTTON32
   Public bitmapIndex As Integer
   Public command As Integer
   Public state As TBStates
   Public style As TBStyles
   Public padding As UShort
   Public data As IntPtr
   Public iString As IntPtr
   Public Function Size() As Integer
       Return Marshal.SizeOf(Me)
   End Function
End Structure

<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure TBBUTTON64
   Public bitmapIndex As Integer
   Public command As Integer
   Public state As TBStates
   Public style As TBStyles
   Public padding1 As Integer
   Public padding2 As UShort
   Public data As IntPtr
   Public iString As IntPtr
   Public Function Size() As Integer
       Return Marshal.SizeOf(Me)
   End Function
End Structure
The function is there for convenience. The 6 bytes of padding in the 64 bit version are spread across two variables.
We could equally use field offsets, but it takes a bit longer as you have to calculate them all. Unless you brain finds
that easy. My brain prefers arranging them in blocks and having padding bytes, and doesn't consider the numbers.

One thing that you think might work is the conditional compile thingy in VB.Net:
#if Platform="x86"
but this depends on the build target, it's not determined at runtime. So again, you would end up with two builds.

10 December 2008

VB.Net Global keyboard hook to detect "print screen" keypress

As described...

Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices

Public Class Form1

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const WM_KEYUP As Integer = &H101
    Private Const WM_SYSKEYUP As Integer = &H105
    Private proc As LowLevelKeyboardProcDelegate = AddressOf HookCallback
    Private hookID As IntPtr

    Private Delegate Function LowLevelKeyboardProcDelegate(ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr

    <DllImport("user32")> _
    Private Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As LowLevelKeyboardProcDelegate, _
        ByVal hMod As IntPtr, ByVal dwThreadId As UInteger) As IntPtr
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function UnhookWindowsHookEx(ByVal hhk As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function CallNextHookEx(ByVal hhk As IntPtr, ByVal nCode As Integer, ByVal wParam As IntPtr, _
        ByVal lParam As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", CharSet:=CharSet.Unicode)> _
    Private Shared Function GetModuleHandle(ByVal lpModuleName As String) As IntPtr
    End Function

    Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.
        hookID = SetHook(proc)
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
        UnhookWindowsHookEx(hookID)
    End Sub

    Private Function SetHook(ByVal proc As LowLevelKeyboardProcDelegate) As IntPtr
        Using curProcess As Process = Process.GetCurrentProcess()
            Using curModule As ProcessModule = curProcess.MainModule
                Return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0)
            End Using
        End Using
    End Function

    Private Function HookCallback(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
        ' we check keyup for standard printscreen, and syskeyup incase it is alt+printscreen
        ' we aren't checking keydown, as that fires before the screenshot is taken.
        If nCode >= 0 AndAlso (wParam.ToInt32 = WM_KEYUP OrElse wParam.ToInt32 = WM_SYSKEYUP) Then
            Dim vkCode As Integer = Marshal.ReadInt32(lParam)
            If vkCode = Keys.PrintScreen Then
                Dim data As IDataObject = Clipboard.GetDataObject()
                If data.GetDataPresent(GetType(Bitmap)) Then
                    Me.BackgroundImage = DirectCast(data.GetData(GetType(Bitmap)), Bitmap)
                End If
            End If
        End If
        Return CallNextHookEx(hookID, nCode, wParam, lParam)
    End Function

End Class

SetClipboardViewer API VB.NET

This registers a form as a clipboard viewer. It then recieves notification when something happens to the clipboard. Some post on the msdn forum. It mentioned: http://www.radsoftware.com.au/articles/ClipboardMonitor_VB.txt, which I've tidied up to my liking (a stray long, a strange cast removed, you can't override Dispose (could you before?))
Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices

Public Class Form1

   Private Const WM_DRAWCLIPBOARD As Integer = &H308
   Private Const WM_CHANGECBCHAIN As Integer = &H30D

   Private mNextClipBoardViewerHWnd As IntPtr
   Private Event OnClipboardChanged()

   <DllImport("user32")> _
   Private Shared Function SetClipboardViewer(ByVal hWnd As IntPtr) As IntPtr
   End Function

   <DllImport("user32")> _
   Private Shared Function ChangeClipboardChain(ByVal hWnd As IntPtr, ByVal hWndNext As IntPtr) As _
       <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <DllImport("user32")> _
   Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, _
       ByVal lParam As IntPtr) As IntPtr
   End Function

   Sub New()
       InitializeComponent()
       mNextClipBoardViewerHWnd = SetClipboardViewer(Me.Handle)
       AddHandler Me.OnClipboardChanged, AddressOf ClipBoardChanged
   End Sub

   Protected Overrides Sub WndProc(ByRef m As Message)
       Select Case m.Msg
           Case WM_DRAWCLIPBOARD
               RaiseEvent OnClipboardChanged()
               SendMessage(mNextClipBoardViewerHWnd, m.Msg, m.WParam, m.LParam)

           Case WM_CHANGECBCHAIN
               If m.WParam.Equals(mNextClipBoardViewerHWnd) Then
                   mNextClipBoardViewerHWnd = m.LParam
               Else
                   SendMessage(mNextClipBoardViewerHWnd, m.Msg, m.WParam, m.LParam)
               End If
       End Select
       MyBase.WndProc(m)
   End Sub

   Private Sub ClipBoardChanged()
       Dim data As IDataObject = Clipboard.GetDataObject()
       If data.GetDataPresent(GetType(Bitmap)) Then
           Me.BackgroundImage = DirectCast(data.GetData(GetType(Bitmap)), Bitmap)
       End If
   End Sub

   Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
       ChangeClipboardChain(Me.Handle, mNextClipBoardViewerHWnd)
   End Sub

End Class

To use: start a few instances of the program. Copy an image into the clipboard - the backgrounds of the forms should change. Then close one of the forms and change the image in the clipboard - all the backgrounds should change.

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:

    Flasher.Flasher.Flash(Me.Handle)

  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.