Новости из Блогов Жизненный RFID

Discussion in 'Мировые новости. Обсуждения.' started by d3l3t3, 28 Jul 2012.

  1. d3l3t3

    d3l3t3 Banned

    Joined:
    3 Dec 2010
    Messages:
    1,771
    Likes Received:
    98
    Reputations:
    10
    Жизненный RFID


    [​IMG]

    Последний месяц выдался не слишком продуктивным, так как приходилось много ездить по разнообразным медицинским учреждениям. На вторую неделю поездок выяснилось, что большинство учреждений используют RFID-карты для разграничения доступа во внутренних помещениях. Таким образом, под конец месяца у меня набралось великое множество таких карт, что было дико неудобно: приехал в очередное место, открыл рюкзак, достал кипу карт и ищешь где именно та, которую тебе дали на прошлой неделе местные сотрудники. Ещё одним негативным моментом этих поездок было время, которое приходилось проводить в транспорте. В итоге я решил избавиться от одного из неудобств, а именно купить программатор и написать программу-менеджер, которая избавит от необходимости таскать с собой кучу карт (все равно ношу в рюкзаке нетбук, а программатор много веса не добавит) и позволит вести базу, по которой можно будет быстро найти карту от нужного помещения для заданного учреждения, записать идентификатор на болванку и воспользоваться им по назначению. Сказано - сделано, вчера, во время очередной серии поездок, написал соответствующую программку, которую далее и рассмотрю подробнее.
    Также обозначим формат карт, который, как оказалось, является доминирующим по неведомой мне причине - это EM-4100. Мимоходом, в магазине со всякой электроникой, был куплен программатор китайского производства, к которому прилагались драйвера для USB-UART моста модели CP210x производства Silicon Laboratories и стремный софт с китайским интерфейсом (имеющий в своем арсенале только функции чтения и записи идентификатора карты), который все же пригодился в дальнейшем.

    Первым делом встал вопрос, чем пользоваться для взаимодействия с устройством. Беглый обзор доступных библиотек не особо меня вдохновил, поэтому я решил взять дизассемблер и посмотреть как устроен прилагающийся продукт. Софт оказался написан на Visual Basic, о чем намекнула секция импорта, состоящая из одной MSVBVM60.DLL.

    [​IMG]

    VB Decompiler показал, что используются следующие функции из сторонних библиотек:

    [​IMG]

    Таким образом, софт оказался завязан на некую MasterRD.dll, которая, в свою очередь, использовала MasterCom.dll. С помощью отладчика я выяснил, что для подключения к устройству использовалась функция rf_init_com(int port, int baud_rate), для отключения rf_ClosePort(void), для чтения идентификатора карты Read_Em4001(void * dst) и для записи Standard_Write(char a1, char a2, const void * src, char a4). Также обнаружилась полезная функция rf_beep(unsigned short hz, unsigned char ms) и обращение к Reset_Command(void) непонятно зачем.

    Результирующий перечень прототипов:

    PHP:
    int WINAPI rf_init_com(int portint baud_rate);
    int WINAPI rf_ClosePort();
    int WINAPI rf_beep(unsigned short hzunsigned char ms);
    int WINAPI Read_Em4001(void dst);
    int __cdecl Reset_Command();
    int WINAPI Standard_Write(char a1char a2, const void srcchar a4);
    Аргументы первых пяти функций не вызвали затруднений, однако с последней сначала возникло некоторое недопонимание. Через некоторое время выяснилось, что для записи десятизначного идентификатора карты (10 символов в hex или 5 байт данных) эта функция вызывается три раза примерно следующим образом:

    PHP:
    Standard_Write(20ptr1);
    Standard_Write(20ptr2);
    Standard_Write(20ptr0);
    Поэксперементировав с записью, обнаружил, что в третьем вызове в ptr всегда хранится следующая последовательность байт:

    0x00, 0x14, 0x80, 0x40​

    Закономерность для первых двух вызовов оставалась загадкой. На помощь пришел гугл, который помог найти формат кодирования данных. Все оказалось довольно просто:

    [​IMG]

    Допустим, мы хотим записать последовательность 123456789A на карту. По сути это 5 байтов или 40 бит данных (D00..D39), которые дополняются статичным заголовком (9 единичных битов), битом четности для каждых четырех байт (P0..P9) и стоп-битом в конце (S0). Плюс считается бит четности для каждого из столбцов бит (PC0..PC3). Далее все это складывается в здоровенное 64 битное число, которое и передается в два захода (по 4 байта) первыми двумя вызовами Standard_Write. Подробнее о протоколе можно почитать тут (отсюда и была взята картинка для наглядности).
    Теперь перейдем к коду. Первоочередной задачей является написание функции, которая будет преобразовывать записываемый идентификатор вышеописанным образом.

    PHP:
    //Заинлайним повторяющийся небольшой кусок кода
    inline void append_bits(vector<unsigned char>& vecunsigned int valueunsigned charvector_posunsigned charused_bits)
    {
        
    //Нам нужны только младшие 5 битов
        
    value &= 0x1F;
        
    //Дополняем биты в вектор по 5 штук. Такое преобразование - следствие
        //того, что 5 битов и байт (8 битов) никак не кореллируют
        
    vec[vector_pos] |= used_bits <= value << (used_bits) : value >> (used_bits 3);
        
    used_bits += 5;
        
    //Если при дополнении нам не хватило места
        
    if(used_bits >= 8)
        {
            
    used_bits -= 8;
            
    //Запишем оставшиеся биты в следующий по счету байт
            
    vec[++vector_pos] |= value << (used_bits);
        }
    }
     
    vector<unsigned charid_transform(const string hex)
    {
        
    //Заранее выделенный вектор байтов
        
    vector<unsigned charret(80);
        
    //Первые 9 битов всегда такие
        
    ret[0] = 0xFF;
        
    ret[1] = 0x80;
     
        
    //Преобразуем hex-строку в вектор байтов
        
    vector<unsigned charbin hex2bin(hex);
        
    //Контроль четности
        
    unsigned int col_parity 0;
     
        
    //Временная величина
        
    unsigned int temp;
        
    //Количество занятых битов в байте вектора ret
        //Сначала равно единице (см. начало функции, 9 битов занято)
        //(т.е. 1 байт и 1 бит в следующем байте)
        
    unsigned char used_bits 1;
        
    //Текущая позиция, то бишь количество полностью занятых байтов
        //Сейчас это 1, как описано выше
        
    unsigned char curr_vector_pos 1;
     
        
    //Перебираем входные байты
        
    for(vector<unsigned char>::const_iterator it bin.begin(); it != bin.end(); ++it)
        {
            
    unsigned char c = (*it);
            
    //Берем старшие 4 бита
            
    temp = (>> 4) << 1;
            
    //Сдвигаем их на 1 влево, а в освободившийся бит пишем четность от этих четырех
            
    temp |= (char)compute_parity>> );
     
            
    //Считаем четность
            
    col_parity ^= temp;
     
            
    //Добавляем полученные биты в вектор
            
    append_bits(rettempcurr_vector_posused_bits);
     
            
    //Теперь то же самое - с младшими битами
            
    temp = (0x0F) << 1;
            
    temp |= (char)compute_parity0x0F );
     
            
    append_bits(rettempcurr_vector_posused_bits);
     
            
    col_parity ^= temp;
        }
     
        
    //Обнуляем все, кроме 1 - 5 битов четности
        
    col_parity &= 0x1E;
        
    //Ее тоже дописываем в вектор, получив в итоге 8 полных байтов на выходе
        
    ret[curr_vector_pos] |= used_bits <= col_parity << (used_bits) : col_parity >> (used_bits 3);
     
        
    //Возвращаем результат
        
    return ret;
    }
     
    /* [Вообще тут была довольно интуитивная реализация в южноазиатском стиле, но потом пришли умные люди и сказали, что так не кошернo */
    Костяк взаимодействия готов, реализуем небольшой GUI с маджонгом и гейшами WinAPI и говнокодом. Результат будет выглядеть примерно так:

    [​IMG]

    Начнем, как обычно, с заголовочного файла и вспомогательных функций:

    PHP:
    #include <Windows.h>
    #include <TlHelp32.h>
    #include <Commctrl.h>
     
    #include <algorithm>
    #include <bitset>
    #include <iostream>
    #include <sstream>
    #include <fstream>
    #include <map>
    #include <set>
     
    #include "IniFile.h"
    #include "str_util.h"
    #include "resource.h"
     
     
    #pragma comment(lib, "comctl32")
     
     
     
    using namespace std;
     
    static const 
    string ini_file "cards.ini";
    PHP:
    /* Функция добавления потомка к корневому элементу Tree View */
    HTREEITEM insert_child(HWND tree, const wstring titlemap<HTREEITEMpair<wstringwstring>> & cardsHTREEITEM parent)
    {
        
    TVINSERTSTRUCT insert;
        
    wchar_t temp[256];
     
        
    ZeroMemory(&insertsizeof(TVINSERTSTRUCT));
        
    wcscpy_s(temp_countof(temp), title.c_str());
     
        
    insert.hParent parent;
        
    insert.hInsertAfter TVI_LAST;
        
    insert.item.mask TVIF_TEXT;
        
    insert.item.pszText temp;
        
    insert.item.cchTextMax _countof(temp);
     
        return 
    TreeView_InsertItem(tree, &insert);
    }
    /* Функция добавления корневого элемента */
    HTREEITEM insert_root(HWND tree, const wstring titlemap<wstringHTREEITEM> * buildings)
    {
        
    HTREEITEM root;
        
    TVINSERTSTRUCT insert;
        
    wchar_t temp[256];
     
        
    ZeroMemory(&insertsizeof(TVINSERTSTRUCT));
        
    wcscpy_s(temp_countof(temp), title.c_str());
     
        
    insert.hParent NULL;
        
    insert.hInsertAfter TVI_ROOT;
        
    insert.item.mask TVIF_TEXT TVIF_CHILDREN;
        
    insert.item.cChildren 1;
        
    insert.item.pszText temp;
        
    insert.item.cchTextMax _countof(temp);
     
        
    root TreeView_InsertItem(tree, &insert);
     
        if(
    buildings != 0)
            
    buildings->insert(make_pair(titleroot));
     
        return 
    root;
    }
    /* Функция заполнения Tree View элементами из ini-файла */
    map<HTREEITEMpair<wstringwstring>> fill_tree_view(CIniFile iniHWND tree)
    {
        
    HTREEITEM root;
        
    wstring vdn;
        
    map<wstringHTREEITEMbuildings;
        
    map<HTREEITEMpair<wstringwstring>> cards;
        
    map<wstringHTREEITEM>::const_iterator b_it;
     
     
        
    vector<CIniFile::Recordr;
        
    /* Получаем список секций из INI-файла */
        
    vector<stringini.GetSectionNames(ini_file);
     
     
        for(
    vector<string>::const_iterator it s.begin(); it != s.end(); ++it)
        {
            
    str2wstr(ini.GetValue("building", (*it), ini_file));
     
            
    /* Пропускаем карту без поля building */
            
    if(v.empty())
                continue;
     
            if(
    buildings.find(v) == buildings.end())
            {
                
    /* Добавляем корневой элемент */
                
    root insert_root(treev, &buildings);
            }
            else
            {
                
    /* Ищем хендл существующего корневого элемента */
                
    b_it buildings.find(v);
                if(
    b_it != buildings.end())
                    
    root = (*b_it).second;
            }
     
     
            
    ini.GetSection((*it), ini_file);
     
            
    /* Добавляем дочерний элемент */
            
    root insert_child(treestr2wstr(r[0].Section), cardsroot);
     
            
    str2wstr(ini.GetValue("data", (*it), ini_file));
            
    str2wstr(ini.GetValue("note", (*it), ini_file));
     
            
    cards.insert(make_pair(rootmake_pair(dn)));
        }
     
        return 
    cards;
    }
    Теперь WinMain и DlgProc:

    PHP:
    int MainDlgProc(HWND hWndUINT uMsgWPARAM wParamLPARAM lParam)
    {
        
    TV_ITEM item;
        
    HTREEITEM parent;
        static 
    bool conn_state false;
        
    unsigned int i;
        
    wchar_t name[256], data[256], note[256], building[256];
     
        static 
    CIniFile ini;
     
        static 
    HMODULE dll;
        static 
    HWND tree;
        static 
    map<HTREEITEMpair<wstringwstring>> cards;
     
        
    map<HTREEITEMpair<wstringwstring>>::iterator it;
        
    vector<unsigned charexch_data;
     
     
        switch(
    uMsg)
        {
            case 
    WM_INITDIALOG:
                
    /* Подгружаем DLL'ку и заполняем указатели на функции */
                
    dll LoadLibrary(L"MasterRD.dll");
                if(
    dll == NULL)
                {
                    
    MessageBox(hWndL"Failed to load MasterRD.dll"L"Error"MB_OK MB_ICONERROR);
                    
    EndDialog(hWnd0);
     
                    break;
                }
     
                (
    FARPROC &)rf_init_com =  GetProcAddress(dll"rf_init_com");
                (
    FARPROC &)rf_ClosePort =  GetProcAddress(dll"rf_ClosePort");
                (
    FARPROC &)rf_beep =  GetProcAddress(dll"rf_beep");
                (
    FARPROC &)Read_Em4001 =  GetProcAddress(dll"Read_Em4001");
                (
    FARPROC &)Standard_Write =  GetProcAddress(dll"Standard_Write");
                (
    FARPROC &)Reset_Command =  GetProcAddress(dll"Reset_Command");
     
                if
                (
                    
    rf_init_com == NULL || rf_ClosePort == NULL || rf_beep == NULL
                    
    ||
                    
    Read_Em4001 == NULL || Standard_Write == NULL || Reset_Command == NULL
                
    )
                {
                    
    MessageBox(hWndL"Failed to obtain procedure address from MasterRD.dll"L"Error"MB_OK MB_ICONERROR);
                    
    EndDialog(hWnd0);
     
                    break;
                }
                
    /* Получаем хендл на Tree View и заполняем его данными */
                
    tree GetDlgItem(hWndIDC_TREE);
                
    cards fill_tree_view(initree);
                
    /* Добавляем перечень COM-портов в ComboBox */
                
    SendDlgItemMessage(hWndIDC_COMCB_ADDSTRING0reinterpret_cast<LPARAM>(L"COM1"));
                
    SendDlgItemMessage(hWndIDC_COMCB_ADDSTRING0reinterpret_cast<LPARAM>(L"COM2"));
                
    SendDlgItemMessage(hWndIDC_COMCB_ADDSTRING0reinterpret_cast<LPARAM>(L"COM3"));
                
    SendDlgItemMessage(hWndIDC_COMCB_ADDSTRING0reinterpret_cast<LPARAM>(L"COM4"));
                
    /* Ограничиваем размер полей ввода */
                
    SendDlgItemMessage(hWndIDC_DATAEM_LIMITTEXT100);
                
    SendDlgItemMessage(hWndIDC_NAMEEM_LIMITTEXT2560);
                
    SendDlgItemMessage(hWndIDC_NOTEEM_LIMITTEXT2560);
            break;
     
            case 
    WM_NOTIFY:
                switch((
    reinterpret_cast<LPNMHDR>(lParam))->code)
                {
                    
    /* При изменении выбранного элемента в Tree View */
                    
    case TVN_SELCHANGED:
                        
    /* Ищем хендл элемента в мэпе */
                        
    it cards.find(reinterpret_cast<LPNMTREEVIEW>(lParam)->itemNew.hItem);
                        if(
    it != cards.end())
                        {
                            
    /* Получаем имя элемента */
                            
    ZeroMemory(&itemsizeof(TV_ITEM));
     
                            
    item.mask TVIF_HANDLE TVIF_TEXT;
                            
    item.hItem = (*it).first;
                            
    item.pszText data;
                            
    item.cchTextMax _countof(data);
     
                            
    TreeView_GetItem(tree, &item);
                            
    /* Отображаем полученные данные в соответствующих контролах */
                            
    SetDlgItemText(hWndIDC_NAMEdata);
                            
    SetDlgItemText(hWndIDC_DATA, (*it).second.first.c_str());
                            
    SetDlgItemText(hWndIDC_NOTE, (*it).second.second.c_str());
     
                            
    /* Получаем имя корневого элемента */
                            
    parent TreeView_GetParent(tree, (*it).first);
     
                            
    ZeroMemory(&itemsizeof(TV_ITEM));
     
                            
    item.mask TVIF_HANDLE TVIF_TEXT;
                            
    item.hItem parent;
                            
    item.pszText data;
                            
    item.cchTextMax _countof(data);
     
                            
    TreeView_GetItem(tree, &item);
     
                            
    /* Выводим имя корневого элемента в контрол */
                            
    SetDlgItemText(hWndIDC_BLDdata);
                            
    /* А можно было не заморачиваться и все брать из ini-файла... */
                        
    }
                    break;
                }
            break;
     
            case 
    WM_COMMAND:
                switch(
    LOWORD(wParam))
                {
                    
    /* Обработчик кнопки записи */
                    
    case IDC_WRITE:
                        
    GetDlgItemText(hWndIDC_DATAdatasizeof(data));
                        
    /* ID-карты должен состоять из 10 символов */
                        
    if(!= 10)
                        {
                            
    SetDlgItemText(hWndIDC_STATUSL"Erroneous ID size");
                            break;
                        }
                        
    /* Преобразовываем данные в формат, используемый библиотекой */
                        
    exch_data id_transform(wstr2str(data));
                        
    reverse(exch_data.begin(), exch_data.end());
                        
    /* Пишем данные на карту */
                        
    if
                        (
                            
    Standard_Write(20, &exch_data[0], 1) == 0
                            
    &&
                            
    Standard_Write(20, &exch_data[4], 2) == 0
                            
    &&
                            
    Standard_Write(20"\x00\x14\x80\x40"0) == 0
                        
    )
                        {
                            
    /* Оповещаем пользователя об успешной записи */
                            
    SetDlgItemText(hWndIDC_STATUSL"Data was successfully written");
                            
    rf_beep(06);
                        }
                        else
                        {
                            
    SetDlgItemText(hWndIDC_STATUSL"Can't write data");
                        }
     
                        
    Reset_Command();
                    break;
                    
    /* Обработчик кнопки чтения */
                    
    case IDC_READ:
                        
    exch_data.resize(5);
                        
    /* Читаем данные с карты и выводим в HEX в соответствующий контрол */
                        
    if(Read_Em4001(&exch_data[0]))
                        {
                            
    SetDlgItemText(hWndIDC_STATUSL"Can't read card data");
                        }
                        else
                        {
                            
    SetDlgItemText(hWndIDC_DATAstr2wstr(bin2hex(exch_data)).c_str());
                            
    rf_beep(06);
                        }
                    break;
                    
    /* Обработчик кнопки подключения к COM-порту */
                    
    case IDC_CONNECT:
                        
    /* Если мы уже подключены */
                        
    if(conn_state)
                        {
                            
    /* Закрываем порт, меняем состояние контролов */
                            
    conn_state false;
                            
    rf_ClosePort();
     
                            
    SetDlgItemText(hWndIDC_CONNECTL"Connect");
                            
    SetDlgItemText(hWndIDC_STATUSL"Disconnected");
     
                            
    EnableWindow(GetDlgItem(hWndIDC_READ), FALSE);
                            
    EnableWindow(GetDlgItem(hWndIDC_WRITE), FALSE);
                        }
                        else
                        {
                            
    EnableWindow(GetDlgItem(hWndIDC_CONNECT), FALSE);
     
                            
    SendDlgItemMessage(hWndIDC_COMCB_GETCURSEL00);
                            
    /* CB_GETCURSEL возвращает индекс элемента начиная с 0, а нам необходимо с 1 */
                            
    i++;
                            
    /* Инициализируем подключение на скорости 9600 бод */
                            
    if(rf_init_com(i9600))
                            {
                                
    SetDlgItemText(hWndIDC_STATUSL"Can't connect to device");
                                break;
                            }
                            
    /* Оповещаем пользователя, меняем надпись на кнопке и включаем нужные контролы */
                            
    conn_state true;
                            
    SetDlgItemText(hWndIDC_CONNECTL"Disconnect");
                            
    SetDlgItemText(hWndIDC_STATUSL"Connected");
     
                            
    EnableWindow(GetDlgItem(hWndIDC_CONNECT), TRUE);
                            
    EnableWindow(GetDlgItem(hWndIDC_READ), TRUE);
                            
    EnableWindow(GetDlgItem(hWndIDC_WRITE), TRUE);
                        }
                    break;
                    
    /* Обработчик кнопки удаления карты из списка */
                    
    case IDC_DELETE:
                        
    /* Ищем карту в мэпе */
                        
    it cards.find(TreeView_GetSelection(tree));
                        if(
    it == cards.end())
                            break;
                        else
                            
    /* Удаляем карту из ini-файла */
                            
    ini.DeleteSection(wstr2str(name), ini_file);
                    
    /* break опущен умышленно */
                    /* Обработчик кнопки обновления Tree View */
                    
    case IDC_REFRESH:
                        
    /* Очищаем Tree View и повторно заполняем из данных файла */
                        
    TreeView_DeleteAllItems(tree);
                        
    cards fill_tree_view(initree);
                    break;
                    
    /* Обработчик клавиши save */
                    
    case IDC_SAVE:
                        
    /* Считываем данные из контролов */
                        
    GetDlgItemText(hWndIDC_NAMEname_countof(name));
                        
    GetDlgItemText(hWndIDC_DATAdata_countof(data));
                        
    GetDlgItemText(hWndIDC_NOTEnote_countof(note));
                        
    GetDlgItemText(hWndIDC_BLDbuilding_countof(building));
     
     
                        
    /* Проверяем наличие карты с таким именем в файле, если нет такой, то создаем соответствующую секцию */
                        
    if(ini.GetSection(wstr2str(name), ini_file).empty())
                            
    ini.AddSection(wstr2str(name), ini_file);
                        
    /* Обновляем данные */
                        
    ini.SetValue("data"wstr2str(data), wstr2str(name), ini_file);
                        
    ini.SetValue("note"wstr2str(note), wstr2str(name), ini_file);
                        
    ini.SetValue("building"wstr2str(building), wstr2str(name), ini_file);
                        
    /* Очищаем Tree View и повторно заполняем из данных файла */
                        
    TreeView_DeleteAllItems(tree);
                        
    cards fill_tree_view(initree); 
                    break;
                }
            break;
     
            case 
    WM_CLOSE:
                
    rf_ClosePort();
                
    EndDialog(hWnd0);
            break;
     
            default:
                return 
    0;
        }
     
        return 
    1;
    }
     
     
    int WINAPI WinMain(HINSTANCE hInstanceHINSTANCE hPrevInstanceLPSTR lpCmdLineint nCmdShow)
    {
        
    /* Инициализируем контролы для работы с Tree View и Status Bar */
        
    INITCOMMONCONTROLSEX ccl =
        {
            
    sizeof(INITCOMMONCONTROLSEX),
            
    ICC_BAR_CLASSES ICC_TREEVIEW_CLASSES
        
    };
     
        
    InitCommonControlsEx(&ccl);
     
        
    DialogBoxParam(hInstanceMAKEINTRESOURCE(IDD_MAIN), 0, (DLGPROCMainDlgProc0);
     
        return 
    0;
    }
    INI-файл, как видно из кода выше, хранит в себе сохраненные идентификаторы карт и дополнительные пользовательские данные, которые можно указать в интерфейсе программы. То есть можно довольно быстро найти нужную карту, записать на пустую болванку и воспользоваться.
    Опишем формат хранения данных в INI-файле, класс для работы с которыми (CIniFile) использовался в коде выше:

    PHP:
    ;Имя карты в Tree View
    [card1]
    ;
    Расположениеа также имя корневого раздела
    building
    =Hospital1
    ;Данные с карты
    data
    =DEADBEEF
    ;Примечание
    note
    =cool
    [card3]
    building=Hospital3
    data
    =12345678
    note
    =
    И, наконец, файл ресурсов (за вычетом студийной генеренки), который можно было и не приводить, но пусть будет.

    PHP:
    IDD_MAIN DIALOGEX 00342169
    STYLE DS_SETFONT 
    DS_MODALFRAME DS_CENTER WS_POPUP WS_CAPTION WS_SYSMENU
    CAPTION 
    "RFID Manager"
    FONT 10"Verdana"40000xCC
    BEGIN
        CONTROL         
    "",IDC_TREE,"SysTreeView32",TVS_HASLINES WS_BORDER WS_HSCROLL WS_TABSTOP,1,4,96,128
        EDITTEXT        IDC_DATA
    ,156,12,180,13,ES_AUTOHSCROLL
        GROUPBOX        
    "Card info",IDC_STATIC,102,0,240,102
        LTEXT           
    "Data",IDC_STATIC,114,12,17,8
        LTEXT           
    "Name",IDC_STATIC,114,30,19,8
        EDITTEXT        IDC_NAME
    ,156,30,180,14,ES_AUTOHSCROLL
        LTEXT           
    "Note",IDC_STATIC,114,48,17,8
        EDITTEXT        IDC_NOTE
    ,156,48,180,14,ES_AUTOHSCROLL
        PUSHBUTTON      
    "Save",IDC_SAVE,287,84,50,12
        COMBOBOX        IDC_COM
    ,156,114,48,30,CBS_DROPDOWN CBS_SORT WS_VSCROLL WS_TABSTOP
        LTEXT           
    "COM Port",IDC_STATIC,114,114,32,8
        GROUPBOX        
    "Controls",IDC_STATIC,102,102,240,48
        PUSHBUTTON      
    "Connect",IDC_CONNECT,210,114,42,12
        PUSHBUTTON      
    "Read",IDC_READ,287,114,50,12,WS_DISABLED
        PUSHBUTTON      
    "Write",IDC_WRITE,287,132,50,12,WS_DISABLED
        CONTROL         
    "Ready",IDC_STATUS,"msctls_statusbar32",0x3,0,156,342,12
        PUSHBUTTON      
    "Refresh",IDC_REFRESH,1,138,96,12
        PUSHBUTTON      
    "Delete",IDC_DELETE,234,84,50,12
        EDITTEXT        IDC_BLD
    ,156,66,180,14,ES_AUTOHSCROLL
        LTEXT           
    "Building",IDC_STATIC,114,66,26,8
    END
    Не были рассмотрены реализация класса для работы с INI-файлами, так как она была найдена в интернете, и небольшой вспомогательный файл для работы со строками.
    Итак, поставленная цель достигнута всего-то за день поездок, что не может не радовать.

    Проект для MSVC 2010 и скомпилированный экзешник: скачать

    автор: Kaimi
    http://kaimi.ru/2012/07/rfid/
     
    ldnol likes this.