Selecting a serial device

From ISXKB

Revision as of 08:57, 23 April 2008 by Markus (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
This article is currently work in progress. You can help to improve the ISXKB by extending this article.


Contents

Introduction

Inno Setup's functionality can be extended by using Dynamic Link Libraries. DLLs provide shared code and data for calling applications.

This example uses the Windows API EnumPorts () as a demonstration. It is of course possible to call EnumPorts () directly from Inno Setup, but this is anything far from beeing trivial.

The development environment in this example is MS Visual C++ 6.0, because it seems to be the lowest common denominator for creating a dynamic link library and is additionally widely spread and used. There is no reason not to use any other (newer) version of Visual Studio or any other development environment, however you should avoid .NET environments as it would make it more complicated to ensure that it works. Basically, anything that can create DLLs apart from VB and .NET is suitable.

See DLLs on MSDN for more information.

Exceptions and requirements

The DLL could have any arbitrary content or could be built with any development environment with the following exceptions:

  • Inno Setup cannot handle wide character sets. Wide character sets can be used within the DLL, but not for the interface with Inno Setup.
  • 32 bit applications can only call 32 bit DLLs. Since Inno Setup is a 32 bit application, 64 bit DLLs cannot be called. This is why an old version of Visual Studio like VS C++ 6 is enough.
  • Visual Basic cannot build standard Windows DLLs (eventually it can, but only with a relatively huge amount of effort). It would require the runtime environment anyway.
  • .NET DLLs require the runtime environment to be installed, hence it is not quite the best choice either.

Because Inno Setup is an installer, we should consider the following requirements as well:

  • The DLL should not require any dependencies or runtime libraries. Any standard Windows environment without particular extensions should do.

The goal

The setup program asks the user to select a serial port (COM port) and provides this selection to the application in a configuration entry or in some other way.

Inno Setup Serial Port Selection
Inno Setup Serial Port Selection

A function in a DLL could be called that retrieves a complete list with all ports in the system, or the function only returns one serial port identified by number (or index).

In Inno Setup the new page is created inside InitializWizard ().

procedure InitializeWizard ();
var
	ComPort	: String;
	I		: Cardinal;
begin
	PortQueryPage := CreateInputOptionPage (wpSelectProgramGroup,
		'Select Serial Port', 'Which serial port would you like MyApp to use?',
		'Please select a serial port.',
		TRUE, TRUE);
	ComPort := StringOfChar ('#', 55);
	I := 0;
	while (QueryPort (ComPort, 50, I)) do
	begin
		if (Copy (ComPort, 1, 3) = 'COM') then
		begin
			PortQueryPage.Add (ComPort);
		end;
		I := I + 1;
	end;
end;

One could call it "Development from the back end", but it helps understand the desired specification for the DLL better. Everything should be handled by just one single function: QueryPort.

function QueryPort (pName: String; dwSize: Cardinal; dwIndex: Cardinal): Boolean;

The DLL function is first called with an index of 0. If at least one serial port device exists in the system it returns its name in pName. Since the DLL function overwrites everything pName points to, dwSize tells it how many bytes are permitted to be copied. If QueryPort () returns TRUE, the serial port name in pName is valid. If it returns FALSE, there are no more serial devices, or let's better say, no more serial devices could be found.

The development environment

Again, this article uses Microsoft Visual Studio C++ 6.0 to create the example. Creating DLLs without further dependencies became more and more complicated the higher the Visual Studio version became, but that does not mean that it is impossible or much more work. The freely available Express Editions of Visual Studio are completely suitable too.

When invoked, VS C++ 6.0 comes up with a screen similar to this one:

Microsoft Visual Studio 6 after startup
Microsoft Visual Studio 6 after startup

A new project is created by selecting "File"->"New". We can call it "EnumPorts".

New Project "EnumPorts"
New Project "EnumPorts"

In the next dialog window we want an "Empty DLL Project" with no additional files. VS would try to break our idea of a DLL without other dependencies except the standard Windows libraries anyway.

Newer versions of VS create these dependencies even if an empty project was selected. If you are using a newer version of Visual Studio you will have to remove all files again from the project after is has been created (in the "Workplace" or "Solution View") so that we can start from scratch. Remove all 'Source Files', 'Header Files', and 'Resource Files' if there are any. In VS C++ 6 they are all empty right after confirming the "Empty DLL Project", exactly how it is supposed to be (actually, how we want it in this tutorial).

No files in the project EnumPorts
No files in the project EnumPorts

Creating the DLL

To widen the range of compilers/development environments this example works it may be better to create it in pure C code without C++ elements. Therefore, only two files need to be added to the project. In this example we are going to add a header file too so that it looks tidier.

Required files

The code is going to reside in a file 'EnumPorts.c', the DLL definitions go into 'EnumPorts.def', and the headers go into 'EnumPorts.h'.

EnumPorts.c      This file contains the C source code for the DLL's function.
EnumPorts.def    This file decorates the exported functions' names.
EnumPorts.h      This is the header file for EnumPorts.c.

Simply create empty text files with these names and add them to the project. For this step Visual Studio 6 is a bit more painful than the newer versions: You have to go through "Project", "Add To Project", and "Files", then select the three files and add them to the project. More recent versions of MS VS support dragging and dropping. However, some of them lack the ability to move the header file under 'Header Files' automatically when dropped on the "Source Files" group, that needs to be done manually (actually, it doesn't really matter, but I personally believe this somehow looks tidier, though). VS 6 sorts them in correctly.

EnumPorts project including its files
EnumPorts project including its files

The first code for the DLL

A code DLL needs an Entry Point Function, hence this function is the center of the code file EnumPorts.c:

BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		break;	
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

There is no reason to distinguish between DLL_THREAD_DETACH and DLL_PROCESS_DETACH since the Inno Setup's [Code] section only uses one thread.

File contents

The header file (EnumPorts.h) could like like this:

#ifndef __EnumPorts_included__
#define __EnumPorts_included__
#include <winbase.h>
#define DllExport __declspec( dllexport )
#define STDCCONV  __stdcall
extern DllExport BOOL STDCCONV QueryPort (char *chPortName, DWORD dwSize, DWORD dwIndex);
#endif

A function like this is automatically exported with an undecorated name. That's why a .def file is required. EnumPorts.def tells the linker the name we want to use:

LIBRARY EnumPorts.dll
EXPORTS
	QueryPort=_QueryPort@12

That tells the linker to export the undecorated name '_QueryPort@12' as 'QueryPort'.

The function 'QueryPort' is implemented in EnumPorts.c. On first invocation, it uses the Windows API EnumPorts () to retrieve a list with all serial communication devices in the system and stores it in a buffer. Whenever the Inno Setup code function requests a port with the index nIndex it either returns that port's name and TRUE, or no name and FALSE, if no more ports could be found.

#include <windows.h>
#include <string.h>

#include "EnumPorts.h"

PORT_INFO_1	*byBuffer	= NULL;
DWORD		dwNeeded	= 0;
DWORD		dwReturned	= 0;

BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		break;	
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		// Free the allocated buffer again before we get detached.
		if (NULL != byBuffer)
			GlobalFree (byBuffer);
		byBuffer = NULL;
		break;
	}
	return TRUE;
}

DllExport BOOL STDCCONV QueryPort (char *chPortName, DWORD dwSize, DWORD dwIndex)
{
	//_ASSERT (FALSE);
	if (!dwReturned)
	{	// Query the buffer size.
		EnumPorts (
			NULL,				// Server name
			1,					// Information level
			(LPBYTE) byBuffer,	// Port information buffer
			0,					// Size of port information buffer
			&dwNeeded,			// Bytes received or required
			&dwReturned			// Number of ports received
		);
		// Allocate the required buffer size.
		byBuffer = GlobalAlloc (GMEM_FIXED, dwNeeded);
		if (NULL == byBuffer)
			return FALSE;
		EnumPorts (NULL, 1, (LPBYTE) byBuffer, dwNeeded, &dwNeeded, &dwReturned);
	}
	if ((NULL != byBuffer) && (dwIndex < dwReturned))
	{
		strncpy (chPortName, byBuffer [dwIndex].pName, dwSize);
		chPortName [dwSize - 1] = '\0';
		return TRUE;
	}
	return FALSE;
}

That's it already.

Before you compile make sure that you actually generate a DLL, switch off the use of precompiled headers, and don't create a wide character application.

See also

External links

Personal tools
Ads: