DetailPrint From Inside .NET DLL

From NSIS Wiki
Jump to navigationJump to search
Author: rbchasetfb (talk, contrib)


Description

If you're using the Call .NET DLL methods plug-in to call .NET DLL functions to perform work during an install, one of the things that will be useful is to be able to update the Details list in the installer, from the DLL, while a .NET DLL function call is executing. Below is some VB.NET code, easily converted to C# at DEVELOPERfusion, that provides a method DetailPrint that adds an entry to the end of the Details list.

The VB.NET source code is for a complete namespace and class and includes an example function ChopString to test with. You'll also find an NSIS test script. Lastly, a zip file is attached with the complete Visual Studio 2005 project for the test .NET DLL and the NSIS test script. Also included in the zip is ListViewDeclarations.vb that has all the declarations necessary for working with List-view controls from VB.NET. This file also has the DetailPrint method...all this is contained in a VB.NET #Region you can drop in your .NET DLL.

VB.NET Source Code

Imports System
Imports System.Runtime.InteropServices
 
Namespace NSIS
   Public Class TestClass
      'The LVITEM stucture specifies or receives the attributes of a list-view item.
      <StructLayout(LayoutKind.Sequential)> _
      Public Structure LVITEM
         Public mask As Int32
         Public iItem As Int32
         Public iSubItem As Int32
         Public state As Int32
         Public stateMask As Int32
         Public pszText As String
         Public cchTextMax As Int32
         Public iImage As Int32
         Public lParam As IntPtr
      End Structure
 
      'SendMessage API declarations. Two different declarations to manage two lparam types.
      Public Declare Function SendMessageLV Lib "user32" Alias _
         "SendMessageA"(ByVal hwnd As IntPtr, _
                        ByVal wMsg As Int32, _
                        ByVal wParam As Integer, _
                        ByRef lParam As LVITEM) As Integer
      Public Declare Function SendMessage Lib "user32" Alias _
         "SendMessageA"(ByVal hwnd As IntPtr, _
                        ByVal wMsg As Int32, _
                        ByVal wParam As Integer, _
                        ByRef lParam As Integer) As Integer
 
      'Constants used by LVITEM and SendMessage 
      Public Const LVM_FIRST as Integer = &H1000 
      Public Const LVM_GETITEMCOUNT as Integer = LVM_FIRST + 4
      Public Const LVM_GETITEM as Integer = LVM_FIRST + 5
      Public Const LVM_INSERTITEM as Integer = LVM_FIRST + 7
      Public Const LVM_SCROLL as Integer = LVM_FIRST + 20
      Public Const LVIF_TEXT as Integer = &H0001
      Public Const LVIS_FOCUSED as Integer = &H0001
 
      'Here's the key function that writes to the Details list.
      Public Sub DetailPrint(ByVal hwnd As IntPtr, entry as String)
         Try
            'Get current list count
            Dim c as Integer = SendMessage(hwnd,LVM_GETITEMCOUNT,0,0)+1
            'Setup a LVITEM structure for the Insert message
            Dim lv As New LVITEM
            lv.iItem = c
            lv.pszText = entry
            lv.mask = LVIF_TEXT
            lv.stateMask = LVIS_FOCUSED
            lv.state = LVIS_FOCUSED
            'Insert the LVITEM into the list
            c = SendMessageLV(hwnd, LVM_INSERTITEM, 0, lv)
            'Scroll the list so the item is visible
            SendMessage(hwnd,LVM_SCROLL,0,12)
         Catch ex As Exception
            'Do Nothing, no sense throwing an error just for logging.
         End Try
      End Sub
 
      'Test function for calling from an NSIS installer using CLR.dll
      Public Function ChopString(ByVal hwnd as IntPtr, ByVal stringToChop as String, _
                                 ByVal chopToLength as Integer) As String
         Dim ret as String = ""
         If stringToChop.Length > chopToLength Then
            DetailPrint(hwnd, "FROM DLL: Chopping String '" & stringToChop & "'")
            ret = stringToChop.SubString(0,chopToLength)
            DetailPrint(hwnd, "FROM DLL: String chopped to '" & ret & "'")
         Else
            DetailPrint(hwnd, "FROM DLL: String '" & stringToChop & _
            "' insufficient length to chop to " & chopToLength & " characters")
            ret = "error"
         End If
         Return ret
      End Function
   End Class
End NameSpace

DetailPrint C# Source Code

This code uses C# 3.0 object initializers, which for earlier versions of C# should be changed to standard property initializations.

public static class NSIS
{
	private const int LVIF_TEXT = 0x1;
	private const int LVIS_FOCUSED = 0x1;
	private const int LVM_FIRST = 0x1000;
	private const int LVM_GETITEMCOUNT = LVM_FIRST + 4;
	private const int LVM_INSERTITEM = LVM_FIRST + 7;
	private const int LVM_SCROLL = LVM_FIRST + 20;
 
	[DllImport("user32.dll")]
	private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
 
	[DllImport("user32.dll")]
	private static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, ref LVITEM lParam);
 
	public static void DetailPrint(IntPtr hWndDetail, string entry)
	{
		try
		{
			int c = SendMessage(hWndDetail, LVM_GETITEMCOUNT, 0, 0) + 1;
			var lvitem = new LVITEM
			             	{
			             		iItem = c,
			             		pszText = entry,
			             		mask = LVIF_TEXT,
			             		stateMask = LVIS_FOCUSED,
			             		state = LVIS_FOCUSED
			             	};
 
			SendMessage(hWndDetail, LVM_INSERTITEM, 0, ref lvitem);
			SendMessage(hWndDetail, LVM_SCROLL, 0, 12);
		}
		catch
		{
		}
	}
 
	#region Nested type: LVITEM
 
	[StructLayout(LayoutKind.Sequential)]
	public struct LVITEM
	{
		public int mask;
		public int iItem;
		public int iSubItem;
		public int state;
		public int stateMask;
		public string pszText;
		public int cchTextMax;
		public int iImage;
		public IntPtr lParam;
	}
 
	#endregion
}

NSIS Test Script

Name "Test CLRDLL MakeLogEntry"
OutFile "TestCLRDLL.exe"
ShowInstDetails show
Var DetailsHWND
Page instfiles
 
Function .onGUIEnd
   CLR::Destroy
FunctionEnd
 
Section
   InitPluginsDir
   SetOutPath $PLUGINSDIR
   File "TestCLRDLL.dll"
   DetailPrint "===================================="
   FindWindow $0 "#32770" "" $HWNDPARENT
   GetDlgItem $DetailsHWND $0 1016
   CLR::Call /NOUNLOAD "TestCLRDLL.dll" \
      "NSIS.TestClass" \
      "ChopString" \
      3 \
      $DetailsHWND "Testing Make Log Entry" 9
   Pop $0
   DetailPrint "ChopString Result = $0"
   DetailPrint "===================================="
   DetailPrint "Chopping a short string to gen error"
      CLR::Call /NOUNLOAD "TestCLRDLL.dll" \
      "NSIS.TestClass" \
      "ChopString" \
      3 \
      $DetailsHWND "Testing" 9
   Pop $0
   DetailPrint "ChopString Result = $0"
SectionEnd

Prerequisites

You'll need to have the CLR.dll in your NSIS plugins directory. You can get it from the Call .NET DLL methods plug-in page or here CLR.zip (35 KB) (As Is).

Download

Here's the latest version of this little package including the Visual Studio 2005 project, NSIS Test Script and ListViewDeclarations.vb file. TestCLRDLL.zip (77 KB) (As Is)

Acknowledgements

Many thanks to claesabrandt (the creator of Call .NET DLL methods plug-in) for the back-and-forth on getting the CLR.dll working smoothly without the need for anything other than .NET 2.0 Framework. That was some great team-work! This tool is awesome and is helping make a potentially ugly install a lot smoother.

Version History

• 1.0 First release.

NSIS Forum Thread