May 16, 2009
Today I researched how to implement in C# a method that forces a COM port to close when it is open by another process. This was found to be a complex task that involves using undocumented Win32 API functions. I didn’t finish it because I found another way to workaround the problem but the idea is the following:
1) Enumarate all Win32 processes.
2) In each process enumerate all its handles and find the handle to be closed (e.g. \Device\Serial0).
3) Close the handle.
The first task is trivial. We can use System.Diagnostics.Process.GetProcesses(). The second is possible with the Windows NT undocumented function NtQuerySystemInformation. The third task is simple. Once we have the process ID (pid) and the handle of the open COM port, we can close it with the following CloseHandleEx function:
const uint PROCESS_DUP_HANDLE = 0x0040;
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, IntPtr dwProcessId);
[Flags]
enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DuplicateHandle(IntPtr hSourceProcessHandle,
IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle,
uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions);
[Flags]
enum DuplicateOptions : uint
{
DUPLICATE_CLOSE_SOURCE = (0x00000001),
DUPLICATE_SAME_ACCESS = (0x00000002),
}
public static bool CloseHandleEx(IntPtr pid, IntPtr handle)
{
IntPtr hProcess = OpenProcess(ProcessAccessFlags.DupHandle, false, pid);
IntPtr dupHandle = IntPtr.Zero;
bool success = DuplicateHandle(hProcess, handle, IntPtr.Zero, out dupHandle, 0, false, DuplicateOptions.DUPLICATE_CLOSE_SOURCE);
CloseHandle(hProcess);
return success;
}
Tags: api functions, dllimport, IntPtr, kernel, kernel32 dll, pid, source language, task, true return, UnmanagedType
May 15, 2009
I am working on an ActiveX control that allows using a device for scanning personal ID cards in a Web application (based on ASP.NET). The card scanning device (called RTE reader) is accessed using a native DLL that communicates with the device through the COM port by using a proprietary protocol. It is pretty complicated to get this working in all Web browsers so we conviced the customer to use Internet Explorer only.
The typical way to implement this functionality in Internet Explorer is to write an ActiveX control. I created the ActiveX control in C# using .NET Framework 2.0, Windows Forms and few native DLLs. How to create an ActiveX in C# in another interesting story, but once I got ready with the ActiveX control, I needed to write an installation program that changes the security policy of Internet Explorer to ensure the control is allowed to run without security restrictions. The control uses native DLL calls so it needs special permissions that can be assigned by the .NET Framework 2.0 Configuration Tool or its console version (caspol.exe).
There are two steps needed to get running an ActiveX control implemented as .NET assembly without security restrictions in Internet Explorer:
- Add the Web site to the “Trusted sites” security zone in Internet Explorer. This allows this Web site to install and run ActiveX controls.
- Assign full trust permission set to the assembly implementing the ActiveX in the .NET Security Policy.
While the first task is less challengeable (we just need to add few registry values), the second requires to run the following command:
caspol.exe -quiet -machine -chggroup Trusted_Zone FullTrust
The caspol.exe is standard part of .NET Framework 2.0 and hence is available in any machine that has .NET 2.0, 3.0 or 3.5 (because all of the use CRL 2.0). It is typically located here:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\caspol.exe
The following source code (implemented as Windows Forms desktop application) performs the above mentioned two steps to allow an ActiveX written in C# to run without security limitations in Internet Explorer:
public partial class FormMain : Form
{
const int TRUSTED_SITES_ZONE = 2;
public FormMain()
{
InitializeComponent();
}
private void ShowMessage(string msg)
{
this.Invoke(new MethodInvoker(delegate()
{
textBoxMessages.Text = textBoxMessages.Text + msg + "\r\n";
textBoxMessages.SelectionStart = textBoxMessages.Text.Length;
textBoxMessages.SelectionLength = 0;
textBoxMessages.ScrollToCaret();
}));
}
private void FormMain_Load(object sender, EventArgs e)
{
backgroundWorker.RunWorkerAsync();
}
private void buttonOK_Click(object sender, EventArgs e)
{
Close();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
this.Invoke(new MethodInvoker(delegate()
{
buttonOK.Enabled = false;
}));
try
{
ShowMessage("Installing ...");
ShowMessage("\r\nAdding trusted Web sites in Externet Explorer ...");
AddTrustedSitesToInternetExplorer();
ShowMessage("\r\nChanging the .NET Framework Security Policy ...");
ChangeDotNetSecurityPolicyForTrustedZoneAssignFullTrust();
ShowMessage("\r\nInstallation completed successfully.");
}
catch (Exception ex)
{
ShowMessage(ex.Message);
ShowMessage("\r\nInstallation failed.");
}
this.Invoke(new MethodInvoker(delegate()
{
buttonOK.Enabled = true;
}));
}
private void AddTrustedSitesToInternetExplorer()
{
// Change this to your Web site hosting the ActiveX control
AddTrustedSiteToInternetExplorer("http://www.mysite.com");
AddTrustedSiteToInternetExplorer("https://www.mysite.com");
}
private void AddTrustedSiteToInternetExplorer(string url)
{
Match match = Regex.Match(url, @"\A(.+)://((.+)\.)?([^.]+\.[^.]+)\Z");
string protocol = match.Groups[1].Value;
string subdomain = match.Groups[3].Value;
string domain = match.Groups[4].Value;
if (domain == "")
{
match = Regex.Match(url, @"\A(.+)://(.+)\Z");
protocol = match.Groups[1].Value;
subdomain = "";
domain = match.Groups[2].Value;
}
if (protocol == "" || domain == "")
{
throw new Exception(" Error: invalid URL " + url);
}
string key = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\ZoneMap\\Domains\\" + domain;
RegistryKey regKeyDomain = Registry.CurrentUser.CreateSubKey(key);
using (regKeyDomain)
{
if (subdomain == "")
{
regKeyDomain.SetValue(protocol, TRUSTED_SITES_ZONE);
}
else
{
RegistryKey regKeySubdomain = regKeyDomain.CreateSubKey(subdomain);
using (regKeySubdomain)
{
regKeySubdomain.SetValue(protocol, TRUSTED_SITES_ZONE);
}
}
}
ShowMessage(" The site " + url + " is added to the Internet Explorer trusted zone.");
}
private void ChangeDotNetSecurityPolicyForTrustedZoneAssignFullTrust()
{
// Get .NET Framework Runtime location in the file system
Assembly systemAssembly = Assembly.GetAssembly(typeof(System.String));
string netFrameworkPath = Path.GetDirectoryName(systemAssembly.Location);
// Allow assemblies coming from trusted Web sites to run with full permissions
// by executing caspol.exe -quiet -machine -chggroup Trusted_Zone FullTrust
string caspolPath = netFrameworkPath + Path.DirectorySeparatorChar + "caspol.exe";
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = caspolPath;
startInfo.Arguments = "-quiet -machine -chggroup Trusted_Zone FullTrust";
startInfo.ErrorDialog = false;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process process = Process.Start(startInfo);
if (process != null)
{
ShowMessage(" Added .NET security policy to run all assemblies coming from trusted Web sites with no security restrictions.");
}
else
{
throw new Exception(" Error: caspol.exe could not be executed.");
}
}
}
Tags: activex control, activex controls, control, Explorer, internet explorer security, native dll, NET, proprietary protocol, ShowMessage, Value
May 10, 2009
I needed to enumerate all COM ports in C# and get their name (e.g. COM1, COM2, …) and their description shown in the Windows Device Manager (e.g. “Communications Port”, “RT USB MRTD Reader”). You could find lots of unmanaged code for doing this but I think this C# code could be helpful to people who perform communication through the serial interface in .NET:
using System;
using System.Text;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using System.Collections.Generic;
public class Win32DeviceMgmt
{
private const UInt32 DIGCF_PRESENT = 0x00000002;
private const UInt32 DIGCF_DEVICEINTERFACE = 0x00000010;
private const UInt32 SPDRP_DEVICEDESC = 0x00000000;
private const UInt32 DICS_FLAG_GLOBAL = 0x00000001;
private const UInt32 DIREG_DEV = 0x00000001;
private const UInt32 KEY_QUERY_VALUE = 0x0001;
private const string GUID_DEVINTERFACE_COMPORT = "86E0D1E0-8089-11D0-9CE4-08003E301F73";
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
public Int32 cbSize;
public Guid ClassGuid;
public Int32 DevInst;
public UIntPtr Reserved;
};
[DllImport("setupapi.dll")]
private static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("setupapi.dll")]
private static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, Int32 MemberIndex, ref SP_DEVINFO_DATA DeviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData,
uint property, out UInt32 propertyRegDataType, StringBuilder propertyBuffer, uint propertyBufferSize, out UInt32 requiredSize);
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid gClass, UInt32 iEnumerator, IntPtr hParent, UInt32 nFlags);
[DllImport("Setupapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetupDiOpenDevRegKey(IntPtr hDeviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint scope,
uint hwProfile, uint parameterRegistryValueKind, uint samDesired);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegQueryValueExW", SetLastError = true)]
private static extern int RegQueryValueEx(IntPtr hKey, string lpValueName, int lpReserved, out uint lpType,
StringBuilder lpData, ref uint lpcbData);
[DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int RegCloseKey(IntPtr hKey);
[DllImport("kernel32.dll")]
private static extern Int32 GetLastError();
public struct DeviceInfo
{
public string name;
public string decsription;
}
public static List<DeviceInfo> GetAllCOMPorts()
{
Guid guidComPorts = new Guid(GUID_DEVINTERFACE_COMPORT);
IntPtr hDeviceInfoSet = SetupDiGetClassDevs(
ref guidComPorts, 0, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDeviceInfoSet == IntPtr.Zero)
{
throw new Exception("Failed to get device information set for the COM ports");
}
try {
List<DeviceInfo> devices = new List<DeviceInfo>();
Int32 iMemberIndex = 0;
while (true)
{
SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA();
deviceInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
bool success = SetupDiEnumDeviceInfo(hDeviceInfoSet, iMemberIndex, ref deviceInfoData);
if (!success)
{
// No more devices in the device information set
break;
}
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.name = GetDeviceName(hDeviceInfoSet, deviceInfoData);
deviceInfo.decsription = GetDeviceDescription(hDeviceInfoSet, deviceInfoData);
devices.Add(deviceInfo);
iMemberIndex++;
}
return devices;
}
finally {
SetupDiDestroyDeviceInfoList(hDeviceInfoSet);
}
}
private static string GetDeviceName(IntPtr pDevInfoSet, SP_DEVINFO_DATA deviceInfoData)
{
IntPtr hDeviceRegistryKey = SetupDiOpenDevRegKey(pDevInfoSet, ref deviceInfoData,
DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
if (hDeviceRegistryKey == IntPtr.Zero)
{
throw new Exception("Failed to open a registry key for device-specific configuration information");
}
StringBuilder deviceNameBuf = new StringBuilder(256);
try
{
uint lpRegKeyType;
uint length = (uint)deviceNameBuf.Capacity;
int result = RegQueryValueEx(hDeviceRegistryKey, "PortName", 0, out lpRegKeyType, deviceNameBuf, ref length);
if (result != 0)
{
throw new Exception("Can not read registry value PortName for device " + deviceInfoData.ClassGuid);
}
}
finally
{
RegCloseKey(hDeviceRegistryKey);
}
string deviceName = deviceNameBuf.ToString();
return deviceName;
}
private static string GetDeviceDescription(IntPtr hDeviceInfoSet, SP_DEVINFO_DATA deviceInfoData)
{
StringBuilder descriptionBuf = new StringBuilder(256);
uint propRegDataType;
uint length = (uint)descriptionBuf.Capacity;
bool success = SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, ref deviceInfoData, SPDRP_DEVICEDESC,
out propRegDataType, descriptionBuf, length, out length);
if (!success)
{
throw new Exception("Can not read registry value PortName for device " + deviceInfoData.ClassGuid);
}
string deviceDescription = descriptionBuf.ToString();
return deviceDescription;
}
}
Tags: CharSet, const, DeviceInfo, g communications, IntPtr, private const, ref, serial interface, setupapi, stringbuilder