Главная » C#, Компоненты » Лицензирование

0

Кроме повторного использования кода, цель разработки компонентов может заключаться в предоставлении компонентов другим разработчикам. Иными словами, в продаже компонентов. позволяет защитить компоненты от нелегального распространения. В этом разделе я хочу рассказать о средствах создания лицензирования, предоставляемых библиотекой .МЕТ Framework.

Прежде чем начать описание библиотеки и классов, нужно сделать очень важное замечание. Классы, о которых я буду рассказывать, не осуществляют реальную защиту компонентов! Они только лишь предоставляют механизм реализации защиты.

Провайдер лицензий

Библиотека .NET Framework разделяет логику проверки лицензии от кода самого компонента с помощью провайдера лицензий (license provider), представленного классом System.ComponentModel.LicenseProvider. Класс LicenseProvider является базовым абстрактным классом для всех провайдеров лицензий. Единственным его методом является метод GetLicenseO, возвращающий экземпляр класса License:

public abstract License GetLicense(LicenseContext context,

Type type, object instance, bool allowExceptions) ;

Первый параметр этого метода, context, предоставляет сервис для работы с лицензиями (о нем я расскажу чуть позже), второй параметр передает тип лицензируемого компонента, а третий параметр — экзампляр самого компонента. Последний параметр разрешает генерацию исключений, если файл лицензии некорректен.

Класс LicFileLicenseProvider

Класс LicFileLicenseProvider реализует простейшую защиту: если в каталоге, где расположен запускаемый файл, присутствует файл специального формата, лицензия считается корректной. Формат файла очень простой: О имя файла должно быть в формате

<пространет в о_ммен>.<тип_компонента>.lie

П файл должен содержать строку

<пространство__Р1мен>. <тип_компонента> is a licensed component.

Например, для класса Mycontгoi.GradientLabel файл должен называться

MyControl.GradientLabel.lie И содержать строку MyControl.GradientLabelis a licensed component.

Разумеется, назвать это защитой сложно, но этот класс дает возможность реализовать собственный механизм защиты, перекрыв виртуальный метод

protected virtual bool IsKeyValid{string key, Type type);

В реализации наследника можно (и нужно) использовать более сложный механизм, чем простое сравнение строк.

Подключение провайдера лицензий к компоненту

Подключение провайдера лицензий к компоненту производится с помощью атрибута LicenseProvider!

[LiсеnseProvider(typeof(LicFileLicenseProvider))]

public partial class GradientLabel : Label {

}

Само по себе определение атрибута еще не означает включение механизма проверки лицензии. Это просто определение провайдера лицензий. Для проверки правильности лицензии используется вызов метода validate о класса LicenseManager:

License license = LicenseManager.Validate(this.GetType(), this); О классах Linense И LicenseManager Я расскажу в следующих разделах.

Класс License

Класс License является абстрактным базовым классом для лицензий. Он имеет всего одно абстрактное свойство и один метод:

public abstract string LicenseKey { get; } public abstract void Dispose!);

Свойство LicenseKey возвращает строку лицензии (ключ), относящуюся к лицензируемому компоненту. В качестве ключа может быть любая строка. Разумеется, не имеет смысла возвращать лицензионную информацию открытым текстом.

Метод Dispose О (класс License реализует интерфейс iDisposable) должен освобождать все занятые ресурсы, если класс лицензии больше не нужен.

Класс LicenseContext

Теперь вернемся к классу LicenseContext, о котором я обещал рассказать в начале этого раздела. Этот класс имеет следующее описание:

public class LicenseContext : IServiceProvider

{

public LicenseContext();

public virtual LicenseUsageMode UsageMode { get/ } public virtual string GetSavedLicenseKey(

Type type, Assembly resourceAssembly); public virtual object GetService(Type type);

public virtual void SetSavedLicenseKey{Type type, string key);

}

Виртуальное свойство UsageMode возвращает режим, в котором может использоваться лицензия. Режим задается перечислением LicenseUsageMode, которое содержит два значения:

public enum LicenseUsageMode {

Runtime = О, Designtime – 1,

}

Как не трудно догадаться из названия, Runtime означает, что лицензия может использоваться в режиме выполнения, a DesignTime означает возможность использования лицензии в режиме разработки. По умолчанию возвращается значение Runtime.

Виртуальный метод GetSavedLicenseKey{) возвращает значение ключа лицензии для указанного типа компонента, сохраненное как встроенный ресурс в указанной сборке. По умолчанию метод возвращает null. Виртуальный метод SetSavedLicenseKey {) задает лицензионный ключ. Метод GetService () предоставляет доступ к сервисам (см. главу 7).

Класс LicenseManager

Класс LicenseManager предоставляет набор статических методов для работы с лицензиями:

public sealed class LicenseManager

{

public static LicenseContext CurrentContext { get; set; ) public static LicenseUsageMode UsageMode { get; )

public static object CreateWithContext(Type type,

LicenseContext creationContext); public static object CreateWithContext(Type type,

LicenseContext creationContext, object[] args); public static bool IsLicensed(Type type); public static bool IsValid(Type type); public static bool IsValid(Type type,

object instance, out License license); public static void Validate(Type type);

public static License Validate{Type type, object instance); public static void LockContext{object contextUser); public static void UnlockContext(object contextUser);

}

Свойство currentcontext позволяет получить или установить контекст лицензии LicenseContext (СМ. разд. 14.5.5).

Свойство UsageMode возвращает режим, в котором может использоваться текущая лицензия (см. разд. 14.5.5).

Две реализации метода CreateWithContext () используются для создания лицензии указанного типа компонента (параметр type) и контекста (параметр creationContext). С помощью третьего параметра можно указать дополнительные параметры создания объекта.

Метод isLicensedo возвращает true, если объект указанного типа имеет связанную лицензию.

Методы isvaiido возвращают true, если лицензия корректна. Первая реализация проверяет лицензию по типу (параметр type), а вторая — для указанного экземпляра компонента, возвращая при этом ссылку на лицензию или null, если лицензия некорректна. Если метод вернул объект лицензии, то программист должен вызвать метод Dispose о этого объекта после завершения использования. Метод возвращает false, если компонент не имеет лицензии или лицензия некорректна.

Методы validate{) аналогичны методам isvaiido.

Методы LockContext {) и UnlockContext () позволяют заблокировать доступ к лицензии и разблокировать его. Эти методы используются при синхронизации работы с лицензиями из нескольких потоков.

Реализация собственного алгоритма лицензирования

Разумеется, проверка лицензионности с помощью текстовой строки известного содержания, как это делает класс LicFiieLicenseProvider,— не решение. Зато, с помощью этого класса, можно легко реализовать собственный механизм проверки лицензий.

Например, достаточно перекрыть метод isKeyValid (), чтобы реализовать другой алгоритм контроля:

class MyLicenseProvider : LicFileLicenseProvider {

protected override bool IsKeyValid(string key, Type type) {

return string.Format("MyLicenseProvider: {0)",

type.ToString()).Equals(key);

}

}

Теперь строка корректной лицензионной информации будет выглядеть так:

MyLicenseProvider: MyControl.GradientLabel

Реализация собственного алгоритма организации этой строки может включать разные алгоритмы защиты. Например, в ней может быть зашифрована дата, до которой работает лицензия, режим работы (разработки или выполнения) или какая-либо другая информация. Вообще говоря, сюда же может быть включена некоторая информация, идентифицирующая компьютер или пользователя. В качестве примера в следующем разделе я покажу, как получить информацию, идентифицирующую жесткий диск и BIOS компьютера.

Получение уникальной информации о компьютере

Уникальная информация о компьютере пользователя может быть использована для генерации ключа, который будет работать только на этом компьютере. По моему мнению, привязка ключа к компьютеру — не самый хороший путь, но для тех, кто считает иначе, информация этого раздела может пригодиться.

Для получения информации о характеристиках компьютера можно использовать данные реестра, которые можно получить, как показано в листинге 14.8.

Информацию о производителе компьютера можно получить с помощью методов WMI (листинг 14.9). Следует только помнить, что эти методы требуют администраторских прав на компьютере. Код программ, получающих другие характеристики с помощью методов WMI, можно найти в [4].

Лиоинг 14 8 Получение характеристик компьютера из реестра

using System;

using Microsoft.Win32;

namespace ReadRegistry {

class Classl {

[STAThread]

static void Main{string[] args)

{

RegistryKey hklm = Registry.LocalMachine; hklm=hklm. OpenSubKey("HARDWARE\\DESCRIPTION\\System\\

CentralProcessorWO") ; Object obp=hklm.GetValue{"Identifier"); Console.WriteLine("Processor Identifier :{0)obp); RegistryKey hklp =Registry.LocalMachine; hklp=hklp. OpenSubKey ("HARDWARE WDESCRIPT ION \\ Sys tem\\

CentralProcessorWO") ; Object obc=hklp.GetValue("Vendorldentifier"); Console.WriteLine("Vendor Identifier :{0}obc); RegistryKey biosv =Registry.LocalMachine; biosv=biosv.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\

MultiFunctionAdapter\\4"); Object obv=biosv.GetValue("Identifier"); Console.WriteLine("Bios Status  :{0}", obv);

RegistryKey biosd =Registry.LocalMachine;

biosd=biosd.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\"); Object obd=biosd.GetValue("SystemBiosDate"); Console.WriteLine("Bios Date         :{0)",obd);

RegistryKey bios =Registry.LocalMachine;

bios=bios . OpenSubKey ( "HARDWARE WDESCRIPT ION WSystemW" ) ;

Object obs=bios.GetValue("Identifier");

Console.WriteLine("System Identifer                  :(0)",obs);

}

)

}

Листинг 14 9 Получение характеристик компьютера с помощью WMI

class Classl <

[STAThread]

static void Main{string[] args) {

WqlObjectQuery query = new WqlObjectQuery(

"SELECT * FROM Win32_ComputerSystemProduct"); ManagementObjectSearcher find =

new ManagementObjectSearcher(query);

foreach (ManagementObject mo in find.GetO ) {

Console.WriteLine{"Description." + mo["Description"]); Console.WriteLine("Identifying number (usually

serial number)." + mo["IdentifyingNumber"]); Console.WriteLine ("Commonly used product name.+ mo["Name"]); Console.WriteLine("Universally Unique Identifier of

product." + mo["UUID"]); Console. WriteLine (”Vendor of product." + mo["Vendor”j ) ;

}

!

}

Код, показанный в листинге 14.10, возвращает серийный номер диска. Правда, этот номер можно задать при форматировании диска, но и такой способ проверки может пригодиться:

Volumelnfo gv – new Volumelnfo();

string serialNumber = gv.GetVolumeSerial(‘C*);

Заводской серийный номер можно прочитать только с помощью метода DeviceioControi (), который позволяет передавать команды напрямую драйверу жесткого диска. Соответствующий код показан в листинге 14.11. Код работает только в Windows NT/2000 (подробный разбор листинга не входит в задачи этой книги). Вот как его можно использовать:

HardDisklnfo hdd = AtapiDevice.GetHddlnfo(O); string serialNumber = hdd.SerialNumber;

С помощью этих и многих других методов можно реализовать необходимый уровень лицензирования компонентов.

Листинг 14 10 Получение серийного номера логического диска

using System; using System.Text;

using System.Runtime.InteropServices;

namespace Volume {

public class Volumelnfo

{

[Dlllmport {"kernel32.dll") ]

private static extern long GetVolumelnformation( string PathName, StringBuilder volumeNameBuffer, UInt32 volumeNameSize, ref UInt32 VolumeSerialNumber, ref UXnt32 MaximumComponentLength, ref UInt32 FileSystemFlags, StringBuilder FileSystemNameBuffer, UInt32 FileSystemNameSize);

/// <summary>

/// Возвращает серийный номер указанного диска III </summary>

/// <param name="strDriveLetter">ByKBa диска</рагат>

public string GetVolumeSerial(char driveLetter) {

string driveName = new string(driveLetter, 1);

uint serialNurnber = 0; uint. maxCompLen = 0;

StringBuilder volumeLabel = new StringBuilder(256); UInt32 volumeFlags = new UInt32();

StringBuilder fileSystemName — new StringBuilder(256); driveName +=

GetVolumelnformation(driveName,

volumeLabel, (Uint32)volumeLabel.Capacity,

ref serialNurnber, ref maxCompLen, ref volumeFlags,

fileSystemName, (UInt32)fileSystemName.Capacity);

return Convert.ToString(serialNurnber);

}

}

}

Листинг 14.11 Получение заводского сорийного номера диска

using System;

using System.Runtime.InteropServices; using System.Text;

namespace HardwareVolumelnfo {

[Serializable] public struct HardDisklnfo

}

public string ModuleNumber; public string Firmware; public string SerialNumber; public uint Capacity;

}

[StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct GetVersionOutParams

{

public byte bVersion; public byte bRevision; public byte bReserved; public-,- byte bIDEDeviceMap; public uint fCapabilities;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public uint[] dwReserved;

}

[StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct IdeRegs

{

public byte bFeaturesReg; public byte bSectorCountReg; public byte bSectorNumberReg; public byte bCylLowReg; public byte bCylHighReg; public byte bDriveHeadReg; public byte bCommandReg; public byte bReserved;

}

[StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct SendCmdlnParams

}

public uint cBufferSize; public IdeRegs irDriveRegs; public byte bDriveNumber;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]

public byte[] bReserved;

[MarshalAs(UnmanagedType.ByValArray, SizeConst =4)3 public uint[] dwReserved; public byte bBuffer;

}

[StructLayout(LayoutKind.Sequential, Pack = 1)]

internal struct DriverStatus

{

public byte bDriverError; public byte bIDEStatus;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] bReserved;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public uint[] dwReserved;

}

[StructLayout(LayoutKind.Sequential, Pack = 1)}

internal struct SendCmdOutParams {

public uint cBufferSize; public DriverStatus DriverStatus; public IdSector bBuffer;

}

[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 512)]

internal struct IdSector {

public ushort wGenConfig; public ushort wNumCyls; public ushort wReserved; public ushort wNumHeads; public ushort wBytesPerTrack; public ushort wBytesPerSector; public ushort wSectorsPerTrack;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)) public ushort[] wVendorUnique;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]

public byte[] sSerialNumber;

public ushort wBufferType;

public ushort wBufferSize;

public ushort wECCSize;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]

public byte[] sFirmwareRev;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] public byte[} sModelNumber; public ushort wMoreVendorUnique; public ushort wDoubleWordIO; public ushort wCapabilities; public ushort wReservedl; public ushort wPIOTiming; public ushort wDMATiming; public ushort wBS; public ushort wNumCurrentCyls; public ushort wNumCurrentHeads; public ushort wNumCurrentSectorsPerTrack; public uint ulCurrentSectorCapacity; public ushort wMultSectorStuff; public uint ulTotalAddressableSectors; public ushort wSingleWordDMA; public ushort wMultiWordDMA;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] bReserved,-

}

public class AtapiDevice [

[DllImport("kerne132.dll", SetLastError = true)] static extern int CloseHandle{IntPtr hObject);

[DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr CreateFile{ string IpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr IpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

[Dlllmport("kernei32.dll")] static extern int DeviceloControl( IntPtr hDevice, uint dwioControlCode, IntPtr IplnBuffer, uint nlnBufferSize,

ref GetVersionOutParams lpOutBuffer, uint nOutBufferSize, ref uint lpBytesReturned, [Out] IntPtr lpOverlapped);

[DllImport("kernel32.dll")] static extern int DeviceloControl( IntPtr hDevice, uint dwIoControlCode, ref SendCmdlnParams lpInBuffer, uint nlnBufferSize, ref S endCmdOu t Pa rams lpOutBuffer, uint nOutBufferSize, ref uint lpBytesReturned, [Out] IntPtr lpOverlapped);

const uint DFP_GET_VERSlON = 0x00074080; const uint DFP_SEND DRIVE COMMAND = 0x0007c084; const uint DFP_RECEIVE_DRIVE_DATA = 0x0007c088;

const uint GENERIC_READ = 0x80000000; const uint GENERIC_WRITE – 0x40000000; const uint FILE SHARE READ – 0x00000001; const uint FILE_SHARE_WRITE – 0x00000002; const uint CREATE_NEW = 1; const uint OPEN_EX1STING = 3;

public static HardDisklnfo GetHddlnfo(byte drivelndex) {

GetVersionOutParams vers – new GetVersionOutParams(); SendCmdlnParams inparam ~ new SendCmdlnParams(); SendCmdOutParams outParam = new SendCmdOutParams(); uint bytesReturned – 0;

// Код работает только в Windows NT/2000 IntPtr hDevice – CreateFile{

string. Format (@”\\ . \PhysicalDrive { 0 } ", drivelndex) ,

GENERIC_READ | GENERIC_WRITE,

FILE_SHARE_READ | FILE_S HAR E_WRIТЕ,

IntPtr.Zero,

OPEN_EXISTING,

0,

IntPtr.Zero);

if (hDevice == IntPtr.Zero) {

throw new Exception("Ошибка выполнения CreateFile.");

}

if (0 == DeviceloControl( hDevice,

DFP_GET_VERSION, IntPtr.Zero, 0,

ref vers,

(uint)Marshal.SizeOf(vers) , ref bytesReturned, IntPtr.Zero)}

{

CloseHandle(hDevice) ;

throw new Exception(string.Format(

"Диск {0} не существует.", driveIndex + 1) ) ;

S

if (0 — (vers.fCapabilities & 1)) i

CloseHandle(hDevice);

throw new Exception("Ошибка: IDE-команды не поддерживаются.");

}

if (0 != (driveIndex & 1))

{

inParam.irDriveRegs.bDriveHeadReg = OxbO;

}

else {

inParam.irDriveRegs.bDriveHeadReg = OxaO;

}

if (0 != (vers.fCapabilities & (16 » drivelndex))) {

CloseHandle(hDevice);

throw new Exception(string.Format(

"Диск {0} является ATAPI-устройством. drivelndex + l))?

}

else

{

inParam.irDriveRegs.bCommandReg = Oxec;

inParam.bDriveNumber = drivelndex; inParam.irDriveRegs.bSectorCountReg = 1; inParam.irDriveRegs.bSectorNumberReg = 1; inParam.cBufferSize = 512;

if {0 =- DeviceloControl( hDevice,

DFP_RECE IVE_DRIVE__DATA, ref inParam,

(uint)Marshal.SizeOf{inParam), ref outParam,

{uint)Marshal.SizeOf{outParam), ref bytesReturned, IntPtr.Zero))

{

CloseHandie(hDevice); throw new Exception(

"Ошибка DeviceloControl: DFP_RECEIVE_DRIVE_DATA");

}

CloseHandie(hDevice);

return GetHardDisklnfo(outParam.bBuffer);

}

private static HardDisklnfo GetHardDisklnfo(IdSector phdinfo) {

HardDisklnfo hddlnfo = new HardDisklnfo();

ChangeByteOrder{phdinfo.sModelNumber)• hddlnfo.ModuleNumber = Encoding.ASCII.GetString(

phdinfo.sModelNumber).Trim();

ChangeByteOrder(phdinfo.sFirmwareRev); hddlnfo.Firmware = Encoding.ASCII.GetString(

phdinfo.sFirmwareRev) .TrimO ;

ChangeByteOrder{phdinfo.sSerialNumber); hddlnfo.SerialNumber = Encoding.ASCII.GetString{

phdinfo.sSerialNumber).Trim();

hddlnfo.Capacity = phdinfo.ulTotalAddressableSectors / 2 / 1024; return hddlnfo;

private static void ChangeByteOrder(byte[] charArray)

{

byte temp;

for (int i – 0; i < charArray.Length; i += 2)

{

temp = charArray[i];

charArray[i] – charArray[i +1];

charArray[i + 1] – temp;

}

}

}

}

 

Литература:

Агуров П. В. C#. Разработка компонентов в MS Visual Studio 2005/2008. – СПб.: БХВ-Петербург, 2008. — 480 е.: ил.

По теме:

  • Комментарии