Программирование PC Visual Studio C++: решение общих проблем, FAQ Fri, December 13 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Visual Studio C++: решение общих проблем, FAQ Печать
Добавил(а) microsin   

050918 - 18 сентября 2005

Столкнулся с ошибкой линкера LNK2019:

module.obj : error LNK2019: unresolved external symbol __imp__mciSendCommandA@16 referenced in function "long __stdcall WndProc(struct HWND__ *, unsigned int, unsigned int, long)" (?WndProc@@YGJPAUHWND__@@IIJ@Z)

Устранил ошибку следующим образом:
- Нашёл (через Help\Index...) подпрограмму mciSendCommand. В конце справки там была указана используемая библиотека с этой функцией - Winmm.lib.
- Через Project\ИмяПроекта Properties...\Configuration Properties\Linker\Input\Additional Dependencies добавил в список библиотек Winmm.lib.

051001 - 1 октября 2005

Полную информацию по процессу линковки даёт опция линкера /VERBOSE, устанавливается через Project\ИмяПроекта Properties...\Configuration Properties\Linker\General\ShowProgress\Display All Progress Messages (/VERBOSE). Иногда Help\Index... даёт неверное имя библиотеки. Тогда может помочь поиск по содержимому всех файлов c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Lib\*.lib, на появление строки с искомой функцией (для нашего примера mciSendCommand). Один из найденных файлов надо добавить в список библиотек (через Project\ИмяПроекта Properties...\Configuration Properties\Linker\Input\Additional Dependencies).

Можно использовать целые типы INT и UINT, которые являются производными от int и unsigned int (задаются в файле windef.h).

Для получения помощи по функции в коде иногда лучше не просто нажать F1, когда курсор стоит на имени функции, а скопировать имя функции в поле LookFor окна поиска Help\Index... - тогда будут видны все варианты описаний прототипов функции.

Изменение поведения (кода) методов класса - открыть исходный файл *.cpp нашего нового класса, и выбрать в меню View\Properties Window (или нажать Alt+Enter). В появившемся окне нажать кнопку Overrides (на ней значок в виде зелёного кубика). Появится список методов класса. Выбираем нужный метод, выбираем из выпадающего списка имя_метода. В модуль класса вставляется пустышка, которую предстоит изменить для доработки поведения метода класса.

В конструкторе класса окна m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON1);

Что происходит, когда мы добавляем (привязываем) переменную к ресурсу (например, полю ввода)? Когда мы добавляем переменную, связанную с ресурсом окна ввода, то на самом деле в *.h-файл, описывающий класс главного окна (например, диалога, в котором есть окно ввода), добавляется в раздел public класса главного окна добавляется переменная. В моём примере *.h-файл ciconfigDlg.h описывал класс главного диалогового окна CciconfigDlg (тип CDialog), и в этом файле в раздел public описания класса CciconfigDlg добавлялась переменная CEdit IPaddress.

Два метода получить текст из переменной типа CEdit (переменной, связанной с окном ввода). В нашем случае переменная, куда считываем текст, это CString ip, а переменная CEdit IPaddress связана с ресурсом окна ввода IDC_EDIT1.

CString ip;
GetDlgItemText (IDC_EDIT1, ip);
IPaddress.GetWindowText(ip);

Когда меняем свойства ресурса, то меняется его текстовое описание в файле *.rc. Например, имеем IDC_LIST1 (окно - список вывода) и связанную с ним переменную CListBox LogList. Если у IDC_LIST1 свойство Sort == False, то описание у него в *.rc-файле будет таким:

LISTBOX  IDC_LIST1,7,36,306,157,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP

Если Sort == True, то таким:

LISTBOX IDC_LIST1,7,36,306,157,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP

Указатель на экземпляр класса диалогового окна можно получить с помощью ключевого слова this

void CciconfigDlg::OnBnClickedButton1(){
   // TODO: Add your control notification handler code here
   ...
   pSocket = new CClientSocket(this);
   ...}

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

Ошибка Compiler Error C2582 'operator =' function is unavailable in 'class' возникает, когда в правой части стоит указатель на экземпляр класса, в котором нет определения оператора "=". Например, наш класс, не имеющий этого оператора CciconfigDlg. На выражение присваивания

CClientSocket::CClientSocket(CciconfigDlg* Dlg){
   m_Dlg = Dlg;}

генерится ошибка C2582. Тогда лечится проблема добавлением определения оператора "=" в заголовочный файл класса CciconfigDlg.h:

public:
...void operator = (const CciconfigDlg& bbb){};

Здесь bbb произвольное имя. Кроме того, эта ошибка может возникнуть тогда, когда в левой части выражения с "=" стоит не указатель на экземпляр класса CciconfigDlg, а сам экземпляр класса, например, переменная m_Dlg определена ошибочно вот так:

class CClientSocket : public CSocket{
   public:
   ..
   protected:
   CciconfigDlg m_Dlg;};

а нужно вот так, тогда ошибки C2582 не будет даже без определения оператора "=" в классе CciconfigDlg:

class CClientSocket : public CSocket{
   public:
   ..
   protected:
   CciconfigDlg* m_Dlg;};

Текстовую метку можно поменять командой:

m_Dlg->SetDlgItemText(IDC_STATIC, "new label text");

где m_Dlg - указатель на экземпляр класса окна диалога, в котором размещена данная метка.

При работе с CSocket нужно быть осторожным с ->Close() и delete - например, эти функции вызывать в обработчике кнопки, в котором создался экземпляр CSocket, иначе не получает управления обработчик приёма ::OnReceive.

Нельзя никакие операции, вызывающие блокировку, выполнять в обработчиках событий кнопок, иначе приложение "виснет", пока не закончится операция. Такие операции нужно запускать в отдельном потоке.

Частенько бывает что нужно воспользоваться некоторыми функциями WinAPI, но если проект создается с поддержкой MFC, то ничего не выходит. Для вызова функций WinApi из MFC используй оператор :: перед именем функции, например ::GetWindowText(hWnd, ....

Использование опции вывода "wt" подпрограммы fopen выводит в файл дополнительные символы \r в конце каждой строки.

060125 - 25 января 2006

В Visual Studio есть очень удобный менеджер конфигураций проекта. Конфигурация - это набор установок для компилируемой и линкуемой программы. По умолчанию существует 2 конфигурации - Debug и Release, назначение которых очевидно из названия. Когда я научился их применять (достаточно в тулбаре выбрать одну из них в выпадающем списке) - одну для отладки, другую для получения готового приложения, то мне захотелось автоматически упаковывать exe-шник релиза упаковщиком AsPack. Оказалось, для этого можно воспользоваться Build Evens:

- Project\имя_приложения Properties..., выбираем тип конфигурации Release
- Build Events\Post Build Event\Command Line, вставляем туда "c:\Program Files\AsPack\ASPack.exe" "$(ProjectDir)$(OutDir)\$(TargetFileName)" /Q /B-

Здесь "$(ProjectDir)$(OutDir)\$(TargetFileName)" означает полный путь к упаковываемому экзешнику (путь этот составлен из специальных макроопределений Visual Studio), а /Q /B- просто опции для AsPack, которые указывают без вопросов всё сделать и не делать бэкапа.

- для отладки макроопределений удобно в командную строку Build Event подставить команду echo макроопределение(я) >testfile.txt. В результате в файле testfile.txt можно прочитать, что подставляется вместо макроопределения.

060203 - 3 февраля 2006

Глобальные переменные в C++ можно определять как внутри основного тела программы _tmain, так и до нее - сразу после одной или нескольких (если есть) строк include. Инициализировать переменные, объявленные до _tmain, можно только вместе с объявлением.

Уже существующие (написанные ранее) процедуры из файлов *.cpp удобно использовать следующим образом:

- наш *.cpp файл с нужными функциями переписываем в корневую папку проекта;
- добавляем *.cpp файл в проект - в Solution Explorer щёлкаем правой кнопкой на папке Source Files, выбираем Add\Add Existing Item..., выбираем наш файл *.cpp;
- в тело модуля, где вызывается процедура любая процедура из *.cpp (в этом примере процедура WLog), вставляем в глобальный блок объявления extern:

extern void WLog (CString S);

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

060212 - 12 февраля 2006

compound-statement означает простой набор операторов за фигурными скобками (или один оператор).

060225 - 26 февраля 2006

Оказывается, любые константы, у которых не указана разрядность (или тип), по умолчанию считаются компилятором за 32-х разрядные, то есть, например, если мы укажем 0xE0, то это на самом деле будет означать 0x000000E0. На первый взгляд, ну и что тут такого? Однако из-за приведения типов это может порождать непредсказуемый результат. Например, вариант 1 нерабочий, а вариант 2 работает нормально.

//Вариант 1:
...char ch0;
...
 switch (ch0){case 0x00:
   ...
   break;case 0xE0:
   ...
   break;default:
   ...}
 //Вариант 2:
...char ch0;
...
 switch (ch0){case (char)0x00:
   ...
   break;case (char)0xE0:
   ...
   break;default:
   ...}

В варианте 1 по метке case 0xE0 управление НИКОГДА не передаётся, независимо от значения переменной ch0. Дизассемблирование показывает, что в начале оператора switch происходит приведение переменной к типу int с помощью инструкции movsx, которая заполняет все старшие биты слова битом 7 значения 0xE0, то есть получается при этом 0xFFFFFFE0. Далее это значение по метке case 0xE0 уже сравнивается с 0x0000000E0. Пример дизассемблированного кода варианта 1:

switch (ch0)
0049436A movsx eax,byte ptr [ch0] ;тут 0xE0 превращается в 0xFFFFFFE0
0049436E mov dword ptr [ebp-190h],eax
00494374 cmp dword ptr [ebp-190h],0
0049437B je 0049438B
0049437D cmp dword ptr [ebp-190h],0E0h; а тут сравн. 0xFFFFFFE0 с 0x000000E0
00494387 je 00494391 ; по метке 00494391 управление НИКОГДА не передается
00494389 jmp 00494397 { case 0x00:...
0049438B ...break;
0049438F jmp 004943A3 case 0xE0:...
00494391 ...break;
00494395 jmp 004943A3
default: ...
00494397 ...
0049439A ...
0049439B ...
004943A0 ...
}
004943A3 jmp
...

Само собой, 0xFFFFFFE0 никогда не равняется 0x0000000E0, и код не работает.

В заключение привожу правильный код варианта 2:

switch (ch0)
0049436A mov al,byte ptr [ch0]
0049436D mov byte ptr [ebp-190h],al
00494373 cmp byte ptr [ebp-190h],0E0h
0049437A je 0049438D
0049437C cmp byte ptr [ebp-190h],0
00494383 je 00494387
00494385 jmp 00494393 { case (char)0x00:...
00494387 ...break;
0049438B jmp 0049439F case (char)0xE0:...
0049438D ...break;
00494391 jmp 0049439F
default: ...
00494393 ...
00494396 ...
00494397 ...
0049439C ...
}0049439F jmp
...

Другой метод побороть проблему - применить тип переменной unsigned.

060302 - 2 марта 2006

   Перехват Ctrl-Break в консольном приложении можно установить с помощью функции SetConsoleCtrlHandler - позволяет, кроме того, реагировать на Ctrl-C и события закрытия консоли. Пример:

1. Описываем обработчик консоли:

BOOL CtrlHandler( DWORD fdwCtrlType )/* Обработчик исключительных ситуаций консоли, типа нажатия на Ctrl-Break 
или щелчка на кнопке с крестиком */
{ CString ctrl_message_descr; switch( fdwCtrlType ) { // Handle the CTRL-C signal. case CTRL_C_EVENT: //поскольку мы запустили поток, обрабатывающий клавиатурные // нажатия, сюда управление никогда не попадает ctrl_message_descr = "Ctrl-C event"; WLog (ctrl_message_descr); return( TRUE );   // CTRL-CLOSE: confirm that the user wants to exit. case CTRL_CLOSE_EVENT: ctrl_message_descr = "Ctrl-Close event"; WLog (ctrl_message_descr); CleanUp(); bStop = true; Beep( 600, 200 ); return ( FALSE );   // Pass other signals to the next handler. case CTRL_BREAK_EVENT: ctrl_message_descr = "Ctrl-Break event"; WLog (ctrl_message_descr); printf (ctrl_message_descr); CleanUp(); bStop = true; Beep( 900, 200 ); return FALSE;   case CTRL_LOGOFF_EVENT: ctrl_message_descr = "Ctrl-Logoff event"; WLog (ctrl_message_descr); printf (ctrl_message_descr); CleanUp(); bStop = true; Beep( 1000, 200 ); return FALSE;   case CTRL_SHUTDOWN_EVENT: ctrl_message_descr = "Ctrl-Shutdown event"; WLog (ctrl_message_descr); printf (ctrl_message_descr); CleanUp(); bStop = true; Beep( 750, 500 ); return FALSE;   default: ctrl_message_descr = "Unknown event!!!"; WLog (ctrl_message_descr); printf (ctrl_message_descr); CleanUp(); bStop = true; return FALSE; } }

2. Инициализация в начале main:

...if( SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE ) ) { 
   if (iDebugLevel == 1)
      WLog("Control Handler (Ctrl-Break interception) is installed.");} else {
   messtr = "ERROR: Could not set control handler";
   WLog(messtr);
   printf(messtr);
   nRetCode = 2;
   Error = true;}
...

См. также, как выполнять действия при выходе из программы Win32 (функция atexit()).

060710 - 10 июля 2006

В dialog-based MFC приложении размер диалоговой формы задается в rc-файле, в строке с ключевым словом DIALOGEX, например:

IDD_QOSTEST_DIALOG DIALOGEX 0, 0, 402, 236

где QOSTEST - имя класса окна, 402 - размер по горизонтали, 236 - по вертикали. Окно About получает свои размеры аналогично:

IDD_ABOUTBOX DIALOGEX 0, 0, 235, 55

Чтобы точно изменить размеры окон, придется вручную подредактировать числа, в самом Visual Studio можно менять размеры только перетаскиванием мышью.

Многострочное окно текста можно выводить, воспользовавшись простым Edit Control (обычно он используется для редактирования однострочного текста). По шагам:

- перетаскиваем его на конструируемое окно.
- меняем размеры перетаскиванием границ. Для того, чтобы выбрать наш Edit Control, можно пользоваться выпадающим списком панели Properties (иногда иначе не получается, например, если Edit Control лежит поверх какого-нибудь Tab Control).
- устанавливаем у Edit Control свойство Multiline = True
- при необходимости включаем полосы прокрутки (Horizontal Scroll и/или Vertical Scroll присваиваем True)
- привязываем к нашему Edit Control переменную - правой кнопкой Add Variable..., из выпадающего списка Control ID выбираем наш Edit Control (например IDC_EDIT1), указываем Variable name (например, edVar).
- для вывода текста используем метод SetWindowText (смотри также другие методы класса CEdit в справке Visual Studio), например:

edVar.SetWindowText("Hello World!");

Чтобы вывести несколько строк, используйте разделитель \r\n, например:

edVar.SetWindowText("Line 1rnLine 2");

Чтобы очистить окно текста, используем:

edVar.SetWindowText("");

Чтобы добавить текст в конец, используем:

CString csTempText;
edVar.GetWindowText(csTempText);
csTempText += "этот текст добавим ";
edVar.SetWindowText(csTempText);

Чтобы прокрутить текст, например, до конца:

::SendMessage(edVar.m_hWnd, WM_VSCROLL, SB_BOTTOM, 0);

Чтобы прокрутить текст на одну строку:

::SendMessage(edVar.m_hWnd, WM_VSCROLL, SB_LINEDOWN, 0);

Это Вам не Delphi и не CBuilder, привыкайте к порядку!

060711 - 11 июля 2006

Как использовать закладки (CTabCtrl).

- создаем новый проект, тип проекта Visual C++ Projects\MFC\MFC Application, даем название проекту (пусть это будет test003).
- открывается MFC Application Wizard. Выбираем Application Type\Dialog based, Use MFC in a static library, жмем Finish.
- открываем Resource View, выбираем test003\test003.rc\Dialog\IDD_TEST003_DIALOG, двойным щелчком открываем редактор диалога.
- удаляем с формы диалога "TODO: Place dialog controls here.". Находим на Toolbox элемент Tab Control, перетаскиваем его на форму диалога, меняем размеры по вкусу.
- привязываем к нашему Tab Control переменную - правая кнопка на нём, выбираем Add Variable..., Variable name указываем m_Tabs, жмем Finish.
- Теперь если запустить программу, то у Tab Control закладок не будет. Нужен код, который изначально создает 2 закладки. Этот код добавляем в место, где инициализируется наш диалог - в метод Ctest003Dlg::OnInitDialog(),
после подсказки "TODO: Add extra initialization here":

   TC_ITEM tci;   // эта структура нужна для вставки закладки
   memset(&tci,0,sizeof(tci)); // очистка структуры
   tci.mask = TCIF_TEXT;       // у закладки будет только текст
   tci.pszText = "Закладка 1";
   m_Tabs.InsertItem(0, &tci); // первая закладка имеет индекс 0
   tci.pszText = "Закладка 2";
   m_Tabs.InsertItem(1, &tci); // вставляем вторую закладку

- если запустить программу, то увидим закладки. Для того, чтобы поместить на закладки содержимое, нужно предварительно для каждой закладки подготовить форму диалога и нарисовать на каждой форме свои элементы управления. Для этого в Resource View на папке Dialog щелкаем правую кнопку, выбираем Add Resource..., в появившемся окошке выбираем Dialog и нажимаем кнопку New. Переименуем наш ресурс - Resource View, выбираем test003\test003.rc\Dialog\, правый щелчок на IDD_DIALOG1, выбираем Properties. Свойство ID содержит имя IDD_DIALOG1, меняем его на IDD_TABPAGE1.
- меняем свойства и содержимое нашей закладки - двойной щелчок на Resource View\test003\test003.rc\Dialog\IDD_TABPAGE1, в панели Properties откроются свойства нашей закладки. Меняем свойство Style на Child, свойство Border на None, удаляем кнопки Ok и Cancel, чтобы осталась чистая форма.
- делаем копию созданной закладки - правая кнопка на Resource View\test003\test003.rc\Dialog\IDD_TABPAGE1, выбираем Copy, правая кнопка на Resource View\test003\test003.rc\Dialog, выбираем Paste. Появляется IDD_TABPAGE2.
- добавляем элементы управления, которые нам нужны - предположим, на первой страничке будет строка редактирования Edit Control, а на второй страничке будет IP Address Control. Перетаскиваем с Toolbox соответствующие элементы на каждую страничку IDD_TABPAGE1 и IDD_TABPAGE2.
- добавляем к каждой страничке свой класс - правый щелчок на страничке в визуальном редакторе, выбираем Add Class..., указываем Class name CTabPage1, Base Class выбираем CDialog, жмем Finish. Для второй странички делаем тоже самое, имя класса указываем CTabPage2. Эти классы нужны, чтобы в обработчике смены закладок запускать создание соответствующей закладки вызовом конструктора класса (например, new CTabPage1).
- чтобы классы были CTabPage1 и CTabPage2 были видны в реализации основного класса диалога программы, в начало файла test003Dlg.cpp добавим строчки:

#include "TabPage1.h"#include "TabPage2.h"

- добавляем в описание класса Ctest003Dlg (файл test003Dlg.h) переменную m_pTabDialog - указатель на память, в которой будет храниться отображаемая страничка Tab Control (этот указатель присваивается вызовом конструктора классов CTabPage1 и CTabPage2):

class Ctest003Dlg : public CDialog{
  ...public:
CTabCtrl m_Tabs;
CDialog* m_pTabDialog; // < --- добавить
  ...};

В конструктор класса Ctest003Dlg (файл test003Dlg.cpp) Добавим код, обнуляющий эту переменную:

Ctest003Dlg::Ctest003Dlg(CWnd* pParent /*=NULL*/):CDialog(Ctest003Dlg::IDD, pParent){
   m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
   m_pTabDialog = 0;         // < --- добавить}

- Нужно добавить обработчик события смены закладки. Двойным щелчком открываем Resource View\test003\test003.rc\Dialog\IDD_TEST003_DIALOG (нашу главную форму диалога), правая кнопка на Tab Control, выбираем Add Event Handler..., проследим, чтобы в окне Message type было выбрано TCN_SELCHANGE, жмем кнопку Add and Edit.
- Теперь добавим код в обработчик события смены закладки:

void Ctest003Dlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT *pResult){
  // TODO: Add your control notification handler code here
  int id; // ID диалога
 
  // надо сначала удалить предыдущий диалог в Tab Control'е:
  if (m_pTabDialog)
  {
     m_pTabDialog->DestroyWindow();
     delete m_pTabDialog;
  }
 
  // теперь в зависимости от того, какая закладка выбрана, 
  // выбираем соотв. диалог
  switch( m_Tab.GetCurSel()+1 ) // +1 для того, чтобы порядковые номера закладок
                               // и диалогов совпадали с номерами в case
  {
   // первая закладка
   case 1 :
      id = IDD_TABPAGE1;
      m_pTabDialog = new CTabPage1;//вызываем конструктор класса
      // тип указателя соответствует нужному диалогу,
      // иначе добавленный в диалог код не будет функционировать
   break;
 
   // вторая закладка
   case 2 :
      id = IDD_TABPAGE2;
      m_pTabDialog = new CTabPage2;//вызываем конструктор класса
   break;
 
   // все остальные закладки, если они есть,
   // будут здесь тоже представлены, каждая - отдельным case
 
   // а если обработка такого номера не предусмотрена
   default:
      m_pTabDialog = new CDialog; // новый пустой диалог
      return;
 
   } // end switch
 
  // создаем диалог
  m_pTabDialog->Create (id, (CWnd*)&m_Tabs); //параметры: ресурс диалога и родитель
 
  CRect rc; 
 
  m_Tab.GetWindowRect (&rc); // получаем "рабочую область"
  m_Tab.ScreenToClient (&rc); // преобразуем в относительные координаты
 
  // исключаем область, где отображаются названия закладок:
  m_Tab.AdjustRect (FALSE, &rc); 
 
  // помещаем диалог на место..
  m_pTabDialog->MoveWindow (&rc);
 
  // и показываем:
  m_pTabDialog->ShowWindow ( SW_SHOWNORMAL );
  m_pTabDialog->UpdateWindow();
 
  *pResult = 0;}

- все, программа работоспособна, за исключением того, что при запуске не отображается содержимое первой закладки. Для этого в BOOL Ctest003Dlg::OnInitDialog() нужно добавить код:

BOOL Ctest003Dlg::OnInitDialog(){
   ...
   m_Tabs.InsertItem(1, &tci); // вставляем вторую закладку
   //-----------------
   // добавить:
   NMHDR hdr;
 
   hdr.code = TCN_SELCHANGE;
   hdr.hwndFrom = m_Tabs.m_hWnd;
   SendMessage ( WM_NOTIFY, m_Tabs.GetDlgCtrlID(), (LPARAM)&hdr );
   //-----------------
   ...}

Можно еще проще:

BOOL Ctest003Dlg::OnInitDialog(){
   ...
   m_Tabs.InsertItem(1, &tci); // вставляем вторую закладку
   //-----------------
   // добавить:
   LRESULT dummy_var;
   OnTcnSelchangeTab1(NULL, &dummy_var);
   //-----------------
   ...}

Ну что, в тоске вспоминаете Delphi и CBuilder =)? Это Вам не хухры-мухры, это Visual Studio, почувствуйте разницу! Использовалась статья http://gzip.rsdn.ru/archive/vc/issues/pvc012.htm.

Это можно сделать, удалив из дерева Solution Explorer\имя_проекта\Source Files\имя_класа.cpp и Solution Explorer\имя_проекта\Header Files\имя_класа.h. Соответствующий класс автоматически пропадет из списка Class View. При этом файлы имя_класа.cpp и имя_класа.h с диска не удаляются, это нужно сделать вручную. В противном случае при попытке создать класс с тем же именем появится предупреждающее сообщение.

060716 - 16 июля 2006

- чтобы поменять высоту выпадающего списка, нужно щелкнуть в визуальном редакторе ресурсов на стрелочку вниз в CComboBox. Появится возможность поменять высоту выпадающего списка перетаскиванием нижнего квадратика. При этом в файле *.rc, в разделе "Dialog" меняется значение высоты выпадающего списка (здесь число 134):

/////////////////////////////////////////////////////////////////////////////// Dialog//
...BEGIN
   ...
   COMBOBOX IDC_COMBO2,33,22,280,134,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP
   ...END
...

- свойство Type меняет поведение CComboBox. Если выбран тип Dropdown, то в окне редактирования можно вводить текст, если выбрано Drop List, то вводить ничего нельзя, можно только выбирать из списка.
- в свойство Data можно добавлять свои строки в режиме редактирования ресурса, каждая строка отделяется от другой точкой с запятой.
- в режиме выполнения очищается список функцией ResetContent(), добавляются строки в конец списка AddString(), вставляются InsertString(). В InsertString() указывается индекс строки, перед которой будет вставка, если указать -1, то вставка будет в конец, по аналогии с AddString().
- SetCurSel() выбирает строку, параметром является индекс выбираемой строки. 0 означает 1-ю строку, 1 - вторую и т. д. (если указать -1, то не будет выбрана никакая строка).

   Здесь показано, как выполнить действия по завершению программы. Для этого в момент инициализации (для приложения - диалогового окна это будет, например, тело BOOL имя_класса_диалога::OnInitDialog()) запускается подпрограмма atexit(). Пример:

...void OnExit (void){
  Beep(4000, 200);}
...BOOL Cping5bDlg::OnInitDialog(){
   ... // TODO: Add extra initialization here
   ... atexit(OnExit);
  return TRUE; // return TRUE unless you set the focus to a control}

Можно также использовать функцию _onexit(), пример:

int OnExit (){
   Beep(4000, 200);
   CString param_list;
   param_list = "";
   for (int i=0; i<Include.GetSize();i++)
      param_list += Include.GetAt(i) + ";";
   csSaveIni("common_settings", "Include", param_list);
   return 0;}
...BOOL CsyslogvDlg::OnInitDialog(){
       ...
   // TODO: Add extra initialization here
       ...
   _onexit(OnExit);
 
   return TRUE; // return TRUE unless you set the focus to a control}

Ограничения - в теле OnExit нельзя использовать переменные, связанные с окнами, поскольку они уже уничтожены; нужно использовать заранее заполненные глобальные переменные (в этом примере массив Include.). Кроме того, у меня не получилось объявить метод OnExit как экземпляр класса окна CsyslogvDlg (в целях доступа к переменным класса), пришлось сделать простую глобальную процедуру. См. также пример, как перехватывать момент выхода из консоли (функция SetConsoleCtrlHandler()).

Окно Output выглядит примерно так:

Compiling...
имя_файла.cpp
c:\VisualStudioProjects\ping5b\имя_файла.cpp(33) : fatal error C1010: unexpected end of file while looking for precompiled header directive

Build log was saved at "file://c:\VisualStudioProjects\ping5b\Debug\BuildLog.htm"
ping5b - 1 error(s), 0 warning(s)

---------------------- Done ----------------------
   Build: 0 succeeded, 1 failed, 0 skipped

У меня эта ошибка возникала, когда в начало файла имя_файла.cpp забыл вставить строчку #include "stdafx.h".

[Через имя класса приложения]

Нужно добраться до свойств объекта CWinApp, а там обилие возможностей - например, у него есть переменная m_pszExeName, которая как раз и содержит имя исполняемого файла без расширения. Если, например, у вас приложение типа MFC-диалог, то у него обязательно есть класс приложения, обычно называемый Cимя_программыApp (реализация класса в имя_программы.cpp, заголовочный файл имя_программы.h). Этот класс Cимя_программыApp является производным от класса CWinApp (задается в имя_программы.h). Экземпляр класса объявлен в имя_программы.cpp, и обычно носит имя theApp. Что нужно сделать по шагам:

- узнаем название класса приложения (виден в корне дерева Class View, название класса заканчивается на App). Например, название класса Cping5bApp.
- ищем название экземпляра класса (переменной) - либо в помощью поиска по файлам на название класса Cping5bApp, либо открыв файл определения класса, в нашем случае это будет ping5b.cpp. В этом файле будет строка, в которой и будет задаваться переменная класса:

Cping5bApp theApp;

- в файле, где нужно обратиться к theApp.m_pszExeName, нужно добавить в начало строку для нашего примера:

#include "ping5b.h"

Теперь в этом файле можно обращаться к переменным и методам объекта theApp:

CString LogFileName;
LogFileName = theApp.m_pszExeName + (CString)".log";

[С помощью AfxGetAppName()]

CString csExe;
csExe = AfxGetAppName();
printf("Имя приложения без расширения: %s\n", csExe);

060719 - 19 июля 2006

CWnd->EnableWindow(false); //запрещает окно (кнопка, список, любой Control)
CWnd->EnableWindow(true);  //разрешает окно (кнопка, список, любой Control)
CWnd->IsWindowEnabled();   //получить состояние запрещенности окна

Поскольку все Control-ы являются наследниками класса CWnd, то в вышеуказанных выражениях нужно просто подставить указатель на переменную нужного Control.

pButton->SetWindowText("Текст на кнопке");

060726 - 26 июля 2006

void CTestPaintDlg::OnButton1() {
  CDC* dc;
  dc=GetDC();
  dc->TextOut(20,20,"GetDC() example");  }

У этого метода куча недостатков. Текст выводится некультяпистым шрифтом на белом фоне, так что на серой форме это будет смотреться некрасиво - происходит из-за того, что по умолчанию используется для вывода непрозрачный фон (OPAQUE). Тот же эффект дает вызов:

dc->SetBkMode(OPAQUE);

Чтобы это поправить, можно использовать

dc->SetBkMode(TRANSPARENT);

Теперь фон текста будет прозрачный.

Текст рисуется поверх всех элементов управления на окне, как будто их и не было. Если текст попадает при выводе за пределы окна, он обрезается или не прорисовывается.
При передвижении окна или перерисовке нарисованный текст будет пропадать. Это легко исправить, если код поместить в OnPaint.

void CTestPaintDlg::OnPaint() {
   ...........
   }
   else
   {
       CDialog::OnPaint();
   }
   CDC* dc;
   dc=GetDC();
   dc->TextOut(20,20,"GetDC() example"); }

Кроме GetDC, существует еще GetWindowDC:

   CDC* dc;
   dc=GetWindowDC();
   dc->TextOut(20,20,"dc=GetWindowDC() example");

Разница в работе GetWindowDC() и GetDC() в том, что GetDC() выводит тест относительно верхнего левого угла серой формы, сразу под голубой плашкой окна, а GetWindowDC() выводит тест относительно верхнего левого угла всего окна целиком, вместе с плашкой.

060727 - 27 июля 2006

Есть как минимум две разновидности этой Функции:

int CWnd::MessageBox (
   LPCTSTR lpszText,
   LPCTSTR lpszCaption = NULL,
   UINT nType = MB_OK );

и

int MessageBox(
   HWND hWnd,
   LPCTSTR lpText,
   LPCTSTR lpCaption,
   UINT uType);

Вторую разновидность нужно вызывать как ::MessageBox, иначе она в зависимости от контекста окажется принадлежащей какому-нибудь оконному классу. Если вместо hWnd указать NULL, то отображаемое окно не будет иметь родительского окна. Это означает, что он становится "независимым" от основной программы - и к окну программы, и к окошку MessageBox можно одновременно получить доступ через интерфейс GUI. Если же в качестве параметра hWnd указать m_hWnd, то появляющееся окно MessageBox станет модальным - не закрыв его, нельзя получить доступ в главное окно. Остальные параметры у обоих функций MessageBox эквивалентны, как и возвращаемые значения, и подробно описаны в MSDN.

060905 - 5 сентября 2006

Класс CListCtrl предоставляет возможность создать список а-ля списка окна Explorer - объектами списка могут быть маленькие иконки, большие иконки (туда можно загрузить картинку), список может быть простой и расширенный, в виде таблицы. Отображение зависит от стиля, который можно назначить при разработке программы (в окне свойств Properties меняем значение свойства View, выбрав из выпадающего списка один из вариантов Icon, Small Icon, List или Report). Манипулируем экземпляром CListCtrl, привязав, как обычно, к нему переменную, и потом в коде обращаемся к методам этой переменной. Вот примеры действий над экземпляром CListCtrl:

1. Получить размеры

CRect rect;
m_cListCtrl.GetClientRect(&rect);

2. Добавить столбцы (только если стиль выбран в Report)

int nColInterval = rect.Width()/10;
m_cListCtrl.InsertColumn(0, _T("Time"), LVCFMT_LEFT, nColInterval);
m_cListCtrl.InsertColumn(1, _T("IP"), LVCFMT_LEFT, nColInterval*2);
m_cListCtrl.InsertColumn(2, _T("Event"), LVCFMT_LEFT, nColInterval*2);
m_cListCtrl.InsertColumn(3, _T("Description"), LVCFMT_LEFT, rect.Width() - 5*nColInterval);

3. Вставляем экземпляры объектов в CListCtrl

LVITEM lvi;
CString strItem;// Insert the first item
lvi.mask = LVIF_IMAGE | LVIF_TEXT;
strItem.Format("%s",evnt.time);
lvi.iItem = idx;
lvi.iSubItem = 0;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.InsertItem(&lvi);// Set subitem 1
strItem.Format("%s", evnt.IP);
lvi.iSubItem =1;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.SetItem(&lvi);// Set subitem 2
strItem.Format("%s", evnt.evID);
lvi.iSubItem =2;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.SetItem(&lvi);// Set subitem 3
strItem.Format("%s", evnt.evDescr);
lvi.iSubItem =3;
lvi.pszText = (LPTSTR)(LPCTSTR)(strItem);
m_cListCtrl.SetItem(&lvi);

4. Очистка

m_cListCtrl.DeleteAllItems();

Класс CStringArray позволяет работать с динамическим массивом строк. Вот примеры действий с таким массивом:

1. Очистка

CStringArray csArray;
csArray.RemoveAll();

2. Добавление строки в массив

csArray.Add( "Эту строку добавляем в массив" );

3. Получение количества строк в массиве, получение строк из массива (индексация элементов, начиная с 0):

for (int i=0; i<csArray.GetSize();i++)
   MessageBox (NULL, csArray.GetAt(i), "", MB_OK);

061223 - 23 декабря 2006

Как определить размер объекта (массива, переменной, класса) в байтах:

CSliderCtrl sl;
...int size_in_bytes = sizeof(sl);

Как определить размер массива (количество элементов в массиве).

CSliderCtrl sl[9];
...int elements_in_array = sizeof(sl) / sizeof(sl[0]);  // ==9

070403 - 3 апреля 2007

[Строки]

AnsiString             -> CString
byte                   -> char
ParamStr(param_num)    -> argv[param_num]
AnsiString.Pos(строка) -> CString.Find(строка)
AnsiString.SubString() -> CString.Left() или CString.Mid()
AnsiString.Length()    -> CString.GetLength()
AnsiString.UpperCase() -> CString.MakeUpper()

При работе со строками важно учитывать следующее отличие AnsiString и CString - нумерация символов в AnsiString начинается с 1, а у CString с 0. Например, так нужно получить последний символ и удалить его в AnsiString:

 char sym = asPattern[asPattern.Length()];
 asPattern.Delete(asPattern.Length(), 1);

А так делаем все то же самое в CString:

char sym = asPattern[asPattern.Length() - 1]; asPattern.Delete(asPattern.Length() - 1, 1);

[Дата и время]

DecodeDate(), Date() -> CTime, CTime::GetCurrentTime(), CTime.GetYear(), CTime.GetMonth(), CTime.GetDay(), CTime.Format()

Пример на CBuilder:

 WORD Year, Month, Day;
 AnsiString asYear, asMonth, asDay;
 DecodeDate(Date(), Year, Month, Day);
 asYear  = IntToStr(Year);
 asMonth = IntToStr(Month);
 asDay   = IntToStr(Day);

То же самое на Visual C:

 WORD Year, Month, Day;
 CString asYear, asMonth, asDay;
 CTime ctTime = CTime::GetCurrentTime();
 Year  = ctTime.GetYear();
 Month = ctTime.GetMonth();
 Day   = ctTime.GetDay();
 asYear  = ctTime.Format( "%Y" );
 asMonth = ctTime.Format( "%m" );
 asDay   = ctTime.Format( "%d" );

[Файлы]

1. ExtractFileName() -> _splitpath()
    ExtractFileDir () -> _splitpath()

Пример на CBuilder:

 AnsiString asDir, asName;
 asName = ExtractFileName(asFullPath);
 asDir = ExtractFileDir (asFullPath);
 AnsiString asOldName, asNewName;
 asOldName = asFullPath;
 asNewName = asDatePart + asName;
 if (!asDir.IsEmpty())
    asNewName = asDir + "\\" + asNewName;

Пример на Visual C:

 CString csDrive, csDir, csName, csExt;
 char drive;
 char dir[256];
 char fname[256];
 char ext[256];
 _splitpath(csFullPath, &drive, dir, fname, ext);
 csDrive = (CString)drive;
 csDir   = (CString)dir;
 csName  = (CString)fname;
 csExt   = (CString)ext;
 if (csDrive != "")
   csDir  = csDrive + ":" + csDir;
 CString csOldName, csNewName;
 csOldName = csFullPath;
 csNewName = csDatePart + csName + csExt;
 if (!csDir.IsEmpty())
   csNewName = csDir + "\\" + csNewName;

2. FileExists()  -> PathFileExists() (#include "shlwapi.h")
3. FileGetAttr() -> GetFileAttributes()
4. FileSetAttr() -> SetFileAttributes()
5. DeleteFile()  -> DeleteFile()
6. FileOpen()    -> _open()
7. Получение длины файла
   FileSeek() -> _filelength()
8. FileSeek() -> _lseek()
9. FileRead() -> _read()
10. FileWrite() -> _write()
11. FileClose() -> _close()
12. RenameFile() -> rename()
13. CopyFile()   -> CopyFile()

070409 - 9 апреля 2007

1. Скачиваем RegexVc71.EXE (см. Download на http://www.tropicsoft.com/Components/RegularExpression/index.html ).

2. Устанавливаем компонент Regular Expression Component Library Vc71 в любую папку, я поставил в C:\Program Files\Microsoft Visual Studio .NET 2003\Regular Expression Component Library Vc71\

3. В Visual Studio C++ 7.1 делаем File\New Project...\Project Types:Win32\Win32 Console Project, указываем имя проекта, например, regextest, Жмем OK, в Application Settings должно быть выбрано Console application, и можно поставить галочку на Add support for:MFC, жмем Finish.

4. Добавляем

#include < RegularExpressionClass.h >

В Project\Properties\Configuration Properties\C/C++\General\Additional Include Directories
добавляем "$(VSInstallDir)\Regular Expression Component Library Vc71\Include"

5. В Project\Properties\Configuration Properties\Linker\General\Additional Library Directories добавляем "$(VSInstallDir)\Regular Expression Component Library Vc71\Lib"

6. В теле main или до него определяем экземпляр класса регулярного выражения.

7. В раздел // TODO: code your application's behavior here. добавляем код, использующий класс (код чисто тестовый):

RegexClass::RegularExpression re;
CString csPattern = "YYMMDD";
CString csRegExp = "(Y{2,4}|M{2}|D{2}|h{2}|m{2}|s{2}|.){1,}";
re.SetExpression(csRegExp.GetBuffer());
re.StringToMatch = csPattern.GetBuffer();if (re.MatchAll())
     MessageBox(NULL, "Matched!", "O.K.", MB_OK);else
     MessageBox(NULL, "No match...", "Err.", MB_ICONASTERISK);

8. Все это мы проделали в конфигурации Debug. Для конечного релиза нужно сделать то же самое, только еще целесообразно в Project\Properties\Configuration Properties\General\Use of MFC\ выбрать Use MFC in a Static Library, а также для предотвращения ошибки Linker Tools Error LNK2005 в Configuration Properties\Linker\Command Line\Additional Options:\ добавляем /FORCE:MULTIPLE (см. совет Ошибка линкера LNK2005).

9. Преобразование текста в IP.

void TEXTtoIP (CString csIP, byte* addr){
   //проверка IP
   RegexClass::RegularExpression re;
   CString csRegExp = (CString)"^([0-9]|[0-9][0-9]|[01][0-9][0-9]|2[0-4][0-9]|25[0-5])"
              + (CString)"(\\.([0-9]|[0-9][0-9]|[01][0-9][0-9]|2[0-4][0-9]|25[0-5])){3}";
   re.SetExpression(csRegExp.GetBuffer());
   re.StringToMatch = csIP.GetBuffer();
   if (!re.MatchAll())
   {
      bError = true;
      WLog("ini IP error - bad address" + csIP);
      exit(1);
   }
   //выделение байт из IP
   int iStringIdx;
   int iByteIdx = 0;
   csIP.Trim();
   CString csByte;
   while (!csIP.IsEmpty())
   {
      iStringIdx = csIP.Find('.');
      if (-1 != iStringIdx)
      {
         csByte = csIP.Mid(0, iStringIdx);
         addr[iByteIdx] = atoi (csByte);
         csIP.Delete(0, iStringIdx+1);
      }
      else
      {
         addr[iByteIdx] = atoi (csIP);
         csIP = "";
      }
      csIP.Trim();
      iByteIdx++;
   }/* while */}

10. Преобразование из строки в дату.

void TEXTtoOleDateTime (CString csDate, COleDateTime* datetime){
   RegexClass::RegularExpression re;
   CString csRegExp = (CString)"^\\d{1,2}([-. /])\\d{1,2}\\1\\d{2,4}";
   re.SetExpression(csRegExp.GetBuffer());
   re.StringToMatch = csDate.GetBuffer();
   if (!re.MatchAll())
   {
     bError = true;
     WLog("ini IP error - bad date" + csDate);
     exit(1);
   }
   int iStringIdx;
   byte iParam = 0;
   int iYear, iMonth, iDay;
   csDate.Trim();
   CString csParam;
   while (!csDate.IsEmpty())
   {
     iStringIdx = csDate.Find('.');
     if (-1 != iStringIdx)
     {
         csParam = csDate.Mid(0, iStringIdx);
         csDate.Delete(0, iStringIdx+1);
     }
     else
     {
         csParam = csDate;
         csDate = "";
     }
     switch (iParam)
     {
     case 0:
         iDay = atoi (csParam);
         break;
     case 1:
         iMonth = atoi (csParam);
         break;
     case 2:
         iYear = atoi (csParam);
         break;
     }
     csDate.Trim();
     iParam++;
   }
   datetime->SetDate(iYear, iMonth, iDay);}

11. Разбиение строки на части (по разделителю - пробелу). В этом примере на входе строка csLine, на выходе части помещаются в вектор out. Части извлекаются с помощью итератора it, первая часть помещается в csPart.

#include < vector >
 ...CString csLine = "part1 part2 part 3";
CString csPart;
CString csRegExp = "\\s+";
RegexClass::RegularExpression re;
std::vector<std::string> out;
re.SetExpression(csRegExp.GetBuffer());
re.StringToMatch = csLine.GetBuffer();
re.Split(out);
std::vector<std::string>::iterator it = out.begin();
csPart = it->c_str();

070410 - 10 апреля 2007

Ошибка линкера LNK2005: Linker Tools Error LNK2005 "symbol already defined in object" быстро устраняется добавлением опции /FORCE:MULTIPLE в командную строку линкера: Configuration Properties\Linker\Command Line\Additional Options:\ добавляем /FORCE:MULTIPLE.

После компиляции получаем кучу страшных предупреждений, но программа работает:

Compiling resources...
Linking...
nafxcw.lib(afxmem.obj) : warning LNK4006: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) already defined in libcpmt.lib(newop.obj); second definition ignored
nafxcw.lib(afxmem.obj) : warning LNK4006: "void * __cdecl operator new[](unsigned int)" (??_U@YAPAXI@Z) already defined in libcpmt.lib(newaop.obj); second definition ignored
Release/autoname.exe : warning LNK4088: image being generated due to /FORCE option; image may not run
LINK : warning LNK4089: all references to 'SHELL32.dll' discarded by /OPT:REF
LINK : warning LNK4089: all references to 'comdlg32.dll' discarded by /OPT:REF
LINK : warning LNK4089: all references to 'ole32.dll' discarded by /OPT:REF

Build log was saved at "file://c:\VisualStudioProjects\autoname\Release\BuildLog.htm"
autoname - 0 error(s), 6 warning(s)
---------------------- Done ----------------------
   Rebuild All: 1 succeeded, 0 failed, 0 skipped
--------------------------------------------------------------------------------

Если Вы создали приложение с помощью визарда как диалоговое окно, и добавили обработчики сообщений для диалогового окна WM_KEYDOWN или WM_KEYUP (напомню, что это делается через Class View\правая кнопка на классе диалогового окна, Properties\кнопка Messages, далее выбираем сообщение и добавляем для него обработчик), то этот обработчик все равно срабатывать не будет. Проблема (и её решение) описана тут - http://www.winterdom.com/dev/mfc/pretrans.html . Проблема решается так:

1. В заголовочном файле класса (имя_класса.h), в секции public класса диалогового окна добавляется строка с переназначением процедуры PreTranslateMessage:

...DECLARE_MESSAGE_MAP()public:
...BOOL PreTranslateMessage(MSG* pMsg);};

2. В файле класса диалогового окна (имя_класса.cpp) вставляется код процедуры PreTranslateMessage (в этом примере отслеживается нажатие кнопок Ctrl+C):

BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg){
   if (pMsg->message == WM_KEYDOWN)
   {
     if ((pMsg->wParam == 17) && (pMsg->lParam == 1900545))
         //Ctrl нажата
         bCtrl = true;
     else if (bCtrl && (pMsg->wParam == 67))
     {
         //нажата комбинация Ctrl+C
         Beep (2000, 100);
         bStop = true;
     }
   }
   if (pMsg->message == WM_KEYUP)
   {
     if ((pMsg->wParam == 17) && (pMsg->lParam == -1071841279))
         //Ctrl отпущена
         bCtrl = false;
     else if (bCtrl && (pMsg->wParam == 67))
         //отпущена комбинация Ctrl+C
         Beep (1000, 100);
   }
   return CDialog::PreTranslateMessage(pMsg);}

Этот метод позволяет вставлять в процедуру PreTranslateMessage обработку и других сообщений.

070418 - 18 апреля 2007

Почитать про это можно тут: http://www.rsdn.ru/article/controls/tiptoe.xml и тут: http://www.rsdn.ru/article/qna/ui/dlgtips.xml

Общее описание метода:

1. Добавить private или protected переменную типа CToolTipCtrl в класс вашего диалога.
2. Добавить в класс управляющую переменную (control member variable), для каждого элемента, у которого будет подсказка. Это можно сделать с помощью ClassWizard (на закладке Member Variable).
3. Переопределить CDialog::OnInitDialog и вызвать в нем CToolTipCtrl::Create. Затем вызвать CToolTipCtrl::AddTool для каждого элемента с подсказкой, передавая адрес управляющей переменной и текст подсказки в качестве параметров.
4. Переопределить CDialog::PreTranslateMessage и вызвать в ней CToolTipCtrl::RelayEvent для каждого сообщения, передаваемого в функцию. Это нужно для того, чтобы элемент ToolTip получал все необходимые сообщения мыши.

Практическая реализация по шагам (на примере простейшего приложения на основе стандартного диалога):

1. Открываем файл имя_класса_диалога.h (у меня был файл unusedportsDlg.h), в определение класса (у меня был класс class CunusedportsDlg : public CDialog), в раздел public, в конец вставляем переменную класса типа CToolTipCtrl:

class CunusedportsDlg : public CDialog{
  ...protected:
  ...public:
  ...
  afx_msg void OnClose();
  CToolTipCtrl hints;};

Еще добавляем переопределение в классе CDialog процедуры PreTranslateMessage:

class CunusedportsDlg : public CDialog{
  ...protected:
  ...public:
  ...BOOL PreTranslateMessage(MSG* pMsg);
  afx_msg void OnClose();
  CToolTipCtrl hints;};

Добавляем в имя_класса_диалога.cpp (у меня был файл unusedportsDlg.cpp), добавляем код процедуры PreTranslateMessage:

BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg){
   // TODO: Add your specialized code here and/or call the base class
   if (pMsg->message == WM_KEYDOWN)
   {
     ...
   }
   if (pMsg->message == WM_KEYUP)
   {
     ...
   }
   hints.RelayEvent(pMsg);
   return CDialog::PreTranslateMessage(pMsg);}

2. Идем на закладку Resource View, далее выбираем в дереве проекта папку Dialog, двойным щелчком открываем редактор диалога, правой кнопкой на каждом элементе диалога добавляем переменную (в контекстном меню выбираем Add Variable..., далее запускается Wizard, указываем в поле Variable name: что-то типа varOkBtn, жмем Finish). Так надо поступить с каждом элементом управления, для которого будет всплывающая подсказка (для кнопки, строки редактирования, пикера даты и т. п.).

3. В файле имя_класса_диалога.cpp (у меня был файл unusedportsDlg.cpp), в подпрограмме имя_класса::OnInitDialog() (у меня была подпрограмма BOOL CunusedportsDlg::OnInitDialog()) добавляем инициализацию хинтов, код должен быть наподобие такого:

...//включаем систему подсказок
hints.Create(pDlg);
hints.AddTool(&varDateBeg, "select START data to analize free ports");
hints.AddTool(&varDateEnd, "select END data to analize free ports");
hints.AddTool(&varOutputList, "analize result will be here");
hints.AddTool(&varOkBtn, "click to start analize free ports");
...

Теперь при запуске приложения у нас нужные элементы управления получат нужные надписи подсказок.

4. В процедуре имя_класса::PreTranslateMessage (у меня была процедура BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg)) добавляем в конец код:

BOOL CunusedportsDlg::PreTranslateMessage(MSG* pMsg){
   ...
   hints.RelayEvent(pMsg);
   return CDialog::PreTranslateMessage(pMsg);}

Все, теперь при наведении курсора на элемент управления будут всплывать желтенькие подсказки. Если нужно переопределить эти подсказки runtime, то нужно пользоваться методом DelTool:

   ...
   hints.DelTool(&varOkBtn);
   hints.AddTool(&varOkBtn, "новый текст хинта");
   ...

Для работы с файлами есть два основных метода - _open/_filelength/_read/_close, и fopen/fread/feof/fclose. Опишем оба метода, их достоинства и недостатки.

_open/_filelength/_read/_close

Работа с файлами через хендл. Самая большая неприятность этого метода - файл всегда открывается в текстовом режиме. Это означает, что часть информации будет при чтении потеряна - например, последовательность \r\n преобразуется в \n, и это не всегда удобно. Удобство в том, что есть подпрограмма определения размера файла (_filelength). Пример использования:

int fhSour;int iFileLength, iBytesReaded;char *buf;
fhSour = _open(catalog + "\\" + csFN, _O_RDONLY);if (-1 == fhSour){
   WLog( "The file " + catalog + "\\" + csFN + " was not opened" );
   exit (1);}
iFileLength = _filelength(fhSour);
buf = (char*)malloc(iFileLength);//iButesReaded может быть меньше iFileLength
iBytesReaded = _read(fhSour, buf, iFileLength);
_close(fhSour);if (-1 == iBytesReaded){
   WLog("Error read file " + csFN);
   exit (1);}//делаем ASCIIZ-строку
buf[iBytesReaded] = 0;
CString csFileContent = (CString)buf;free(buf);

fopen/fread/feof/fclose

Работа с файлами через потоки, хороший выбор. Удобство в том, что можно открывать файлы в двоичном виде. Неудобство в том, что нельзя сразу определить размер файла, не прочитав его до конца (конец файла тестируется feof). Пример использования:

FILE *stream;size_t count, total = 0;char buffer[100];
CString csOutput = "";if( (stream  = fopen( csFileName, "rb" )) == NULL )
   WLog( "Error open (not exist?): " + csFileName );else{
   while( !feof( stream ) )
   {
     /* Attempt to read in 99 bytes: */
     count = fread( buffer, sizeof( char ), 99, stream );
     if( ferror( stream ) )
     {
         WLog( "Error read file " + csFileName);
         break;
     }
     buffer[count] = 0;
     total += count;
     csOutput += (CString)buffer;
   }
   fclose( stream );
   varOutputList.SetWindowText(csOutput);}

Постоянно забываю эти простые специальные символы.
CR, Carriage Return, код 0x0D, использование в C как \r
LF, Line Feed, код 0x0A, использование в C как \n
CRLF, \r\n, используется в файлах DOS и Windows как последовательность, завершающая строку (0x0D 0x0A).

070426 - 26 апреля 2007

- Resource View\папка Bitmap\правая кнопка Add Resource...\выбираем Bitmap\нажимаем кнопку New
- в папке ресурсов Bitmap появляется новая запись IDB_BITMAP1 (это ID картинки) и одновременно открывается окно редактора картинки. IDB_BITMAP1 лучше поменять на что-то более подходящее по смыслу, например IDB_MYBUTTON (делается через свойства картинки, параметр ID).
- меняем у нашей кнопки свойство Bitmap на True, а свойство Caption очищаем (свойство Caption очищать не обязательно, все равно картинка закроет надпись на кнопке).
- рисуем картинку. Проще всего открыть какой-нибудь ресурс в Интернете, посвященный иконкам, например http://www.iconsfree.org/free-icons/language/eng/c/categoriesRating/icons-categories-rating.html, выбрать нужную иконку и прямо с экрана скопировать в буфер обмена растр картинки (для этого очень рекомендую программу Kleptomania - копирует с экрана и графику, и текст), а затем вставить содержимое буфера прямо в редакторе ресурсов Visual Studio.
- к кнопке, в которую будем вставлять картинку, привязываем переменную. Для этого на кнопке в контекстном меню выбираем Add Variable..., в окно Variable name: подставляем что-то типа varMyBtn
- в процедуре инициализации формы диалога OnInitDialog() добавляем следующий код (процедура WLog пишет ошибки в текстовый файл):

HBITMAP bmMyBtn;
bmMyBtn = LoadBitmap(theApp.m_hInstance, MAKEINTRESOURCE(IDB_MYBUTTON));
CString csMsg;char* lpMsgBuf;if (NULL == bmMyBtn){
   FormatMessage(
           FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
           NULL,
           GetLastError(),
           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
           (LPTSTR) &lpMsgBuf,
           0,
           NULL);
   csMsg = (char*)lpMsgBuf;
   csMsg.Trim();
   WLog("error load bitmap for button: " + csMsg);}
varMyBtn.SetBitmap( bmMyBtn );

CString csFileName = "C:\\boot.ini";
ShellExecute(NULL, "open", "notepad.exe", csFileName, NULL, SW_SHOWNORMAL);

- скачиваем ZipArchive Library с сайта http://www.artpol-software.com/Download.aspx, это 2 файла - ziparchive.zip (Sources, Documentation and Samples) и setup.zip (Installation Setup).

- запускаем файл setup.exe (из архива setup.zip), указываем директорию для установки C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library. Если у Вас не MSDN версии 6, то галочку "интегрировать help" не ставим.

- теперь нам надо получить откомпилированную библиотеку ZipArchive.lib той версии, которая подходит к нашей версии Visual Studio 7.0. Для этого из архива ziparchive.zip, который мы скачали, распаковываем 2 файла ZipArchive.vcproj и ZipArchive.sln в папку C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\. Те файлы, что там есть (версии 8.0), можно спокойно стереть, поскольку в ziparchive.zip есть их копии в папке _projects\Visual Studio 2005\ZipArchive\. Двойным щелчком открываем файл C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\ZipArchive.vcproj, выбираем конфигурацию Release, компилируем. В результате получаем файл C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\Release\ZipArchive.lib.

- создаем новый проект в Visual Studio - Visual C++ Projects\Win32\Win32 Console Project, поддержку ATL и MFC не добавляем.

- в свойствах проекта, раздел C/C++\Additional Include Directories добавляем путь к папке, где лежат h-файлы:

"C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive"

- в свойствах проекта, раздел Linker\Additional Library Directories добавляем путь к папке, где лежит откомпилированная библиотека ZipArchive.lib:
"C:\Program Files\Microsoft Visual Studio .NET 2003\ZipArchive library\ZipArchive\Release"

- в свойствах проекта, в раздел Linker\Input\Additional Dependencies добавляем ZipArchive.lib.

070502 - 2 мая 2007

theApp.m_lpCmdLine - указатель на командную строку, в которой только опции, разделенные пробелами (без имени приложения).

MessageBox(theApp.m_lpCmdLine, "", MB_OK);

090421

Ругань началась при попытке компиляции c-кода в cpp-проекте на строку *HidDevices = calloc (*NumberDevices, sizeof (HID_DEVICE)):
путь_до_проекта\usbhid.cpp(70): error C2440: '=' : cannot convert from 'void *' to 'PHID_DEVICE'

Причина - переменная *HidDevices имела тип PHID_DEVICE, а calloc возвращает тип void *. Дело в том, что в проекте CPP компилятор более строго проверяет соответствие типов, чем в проекте на чистом C. Вариантов решения 2 - либо поменять тип проекта (с CPP на чистый C), либо применить явное преобразование типа, например так:
*HidDevices = (PHID_DEVICE) calloc (*NumberDevices, sizeof (HID_DEVICE));

Решение проблемы было найдено на http://winprog.org/tutorial/errors.html

exit(код_возврата)

или

CDialog* pDlg;
pDlg = (CDialog*)theApp.m_pMainWnd;
...pDlg->EndDialog(код_возврата);

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

Библиотека позволяет делать красивый современный дизайн для проекта, добавляет новые возможности.

- официальный сайт - http://www.prof-uis.com/, скачать отсюда - http://www.prof-uis.com/download/profuis264_freeware.zip , документация здесь - http://www.prof-uis.com/download/help/profuishelp.zip (если скачали profuis264_freeware.zip, то доку можно не скачивать), дока по настройке (Getting started with Prof-UIS) здесь - http://www.prof-uis.com/ArticleRead.aspx?AID=220

- из архива profuis264_freeware.zip распаковать каталог Prof-UIS в каталог c:\Program Files\Microsoft Visual Studio .NET 2003\

- как настраивать тут - http://www.prof-uis.com/FAQView.aspx?CID=101

- внутри Visual Studio идем Tools\Options...\Projects\VC++ Directories, Show directories for настраиваем по таблице (здесь $(VCInstallDir) равно c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7):
Include files    .../Prof-UIS/Include или $(VCInstallDir)\..\Prof-UIS\Include
                 .../Prof-UIS/Src     или $(VCInstallDir)\..\Prof-UIS\Src
Source files     .../Prof-UIS/Include или $(VCInstallDir)\..\Prof-UIS\Include
                 .../Prof-UIS/Src     или $(VCInstallDir)\..\Prof-UIS\Src
Library files    .../Prof-UIS/Bin_710 или $(VCInstallDir)\..\Prof-UIS\Bin_710

- скачать Prof-UIS Application Wizard отсюда - http://www.prof-uis.com/download/wizards/ProfUisAppWizard_2003.ZIP (страница закачки http://www.prof-uis.com/FAQView.aspx?CID=101 )

- распаковать в папку c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\VCWizards\ папку ProfUISAppWizard из архива ProfUisAppWizard_2003.ZIP

- файлы ProfUISAppWizard.ico, ProfUISAppWizard.vsdir и ProfUISAppWizard.vsz из архива ProfUisAppWizard_2003.ZIP положить в папку c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\vcprojects\

- теперь сразу, без перегрузки Visual Studio будет доступен новый визард по адресу File\New\Project...\Visual C++ Projects\ProfUISAppWizard

- нужно откомпилировать все версии библиотеки ProfUIS264*.lib, для этого откройте c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Workspace\ProfUIS_710.sln, выберите проект ProfUISLIB и откомпилируйте его со всеми возможными конфигурациями:

ANSI Debug, Static ANSI Debug
ANSI Debug RDE, Static ANSI Debug RDE
ANSI Debug RDE with MFC DLL, Static ANSI Debug RDE with MFC DLL
ANSI Debug with MFC DLL, Static ANSI Debug with MFC DLL
ANSI Release, Static ANSI Release
ANSI Release RDE, Static ANSI Release RDE
ANSI Release RDE with MFC DLL, Static ANSI Release RDE with MFC DLL
ANSI Release with MFC DLL, Static ANSI Release with MFC DLL
MBCS Debug, Static MBCS Debug
MBCS Debug RDE, Static MBCS Debug RDE
MBCS Debug RDE with MFC DLL, Static MBCS Debug RDE with MFC DLL
MBCS Debug with MFC DLL, Static MBCS Debug with MFC DLL
MBCS Release, Static MBCS Release
MBCS Release RDE, Static MBCS Release RDE
MBCS Release RDE with MFC DLL, Static MBCS Release RDE with MFC DLL
MBCS Release with MFC DLL, Static MBCS Release with MFC DLL
Unicode Debug, Static Unicode Debug
Unicode Debug RDE, Static Unicode Debug RDE
Unicode Debug RDE with MFC DLL, Static Unicode Debug RDE with MFC DLL
Unicode Debug with MFC DLL, Static Unicode Debug with MFC DLL
Unicode Release, Static Unicode Release
Unicode Release RDE, Static Unicode Release RDE
Unicode Release RDE with MFC DLL, Static Unicode Release RDE with MFC DLL
Unicode Release with MFC DLL, Static Unicode Release with MFC DLL

Для этого идем в Build\Batch Build..., ставим галки на конфигурациях ProfUISLIB, жмем Build. После того, как все выбранные конфигурации откомпилируются (это надолго, можно пойти попить кофейку), в каталоге c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Bin_710\ появится куча файлов ProfUIS264*.lib.

- для того, чтобы отлаживаемые программы запускались, нужно добавить в переменную Path системы путь до библиотек c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Bin_710\, для этого открываем Control Panel\System\Advanced\Environment Variables...\System variables, выбираем переменную Path и нажимаем кнопку Edit..., в строке Variable Value: в конец добавляем ;c:\Program Files\Microsoft Visual Studio .NET 2003\Prof-UIS\Bin_710\

Перелогиниваться при этом не нужно, просто надо перезапустить приложение (Visual Studio), из которого запускаем программу. Другой способ - скопировать нужную dll в каталог отлаживаемой программы, или задать в свойствах проекта линковать библиотеки статически - в свойствах проекта программы, в разделе Configuration Properties\General указать Use of MFC в Use MFC in a Static Library.

090606 - 6 июня 2009

Пример:

log.obj : error LNK2001: unresolved external symbol "class ATL::CStringT -> LogFileName" (?LogFileName@@3V?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@A) .\Debug/UsbHidDemoCode.exe : fatal error LNK1120: 1 unresolved externals

Такая ошибка у меня часто возникает, когда я беру модуль *.cpp (из другого старого проекта) без хедера и пытаюсь вставить в новый проект. На переменные вставляемого модуля, объявленные с директивой extern (в этом примере так была объявлена переменная LogFileName) будет в этом случае будет происходить ошибка LNK2001 и следующая за ней общая фатальная ошибка LNK1120. Проблема устраняется объявлением CString LogFileName в любом другом модуле.

110721 - 21 июля 2011

Иногда это нужно сделать, если файл кода используется совместно с embedded-проектами для микроконтроллеров. Правой кнопкой щелкаем на нужном C-файле в дереве проекта, выбираем Properties -> Configuration Properties -> C/C++ -> Advanced -> Compile As -> Compile as C++ Code (/TP).

120705 - 5 июля 2012

Получить путь до запускаемого файла приложения вместе с именем *.exe файла можно вызовом ExecutablePath, а полный путь до запускаемого файла приложения без самого имени *.exe можно вызовом StartupPath. Пример:

[STAThreadAttribute]int main(array<System::String ^> ^args){
   // Включение визуальных эффектов Windows XP до создания
   // каких-либо элементов управления
   Application::EnableVisualStyles();
   Application::SetCompatibleTextRenderingDefault(false); 
 
   // создание файла лога
   applog = new Log("myapp.log");
   // Создание главного окна и его запуск
   applog->Write("[START]");
   applog->Write(Application::ExecutablePath); //полный путь до *.exe вместе с именем *.exe
   applog->Write(Application::StartupPath);    //полный путь до *.exe без с имени *.exe
   Application::Run(gcnew Form1());
   applog->Write("[EXIT]");
   delete applog;
   return 0;}

Простое преобразование из char* в System::String может быть выполнено с помощью конструктора. Пример:

char srcstr [] = "ASCIIZ char string";
String^ dststr1 = gcnew System::String(srcstr);
String^ dststr2 = gcnew String (srcstr);

Обратное преобразование из System::String в char* может быть выполнено либо с помощью функции sprintf_s, либо с помощью объекта Marshal. Метод с помощью sprintf_s:

String^ srcstr = gcnew String ("System::String example");char dststr [512];
sprintf_s(dststr, sizeof(dststr), "%s", srcstr);

Метод с помощью объекта Marshal:

char* STRING2CHAR (System::String ^strval){
   IntPtr ptr = Marshal::StringToHGlobalAnsi(strval);
   char* char_str = (char*)ptr.ToPointer();
   return char_str;}
 
String^ srcstr = gcnew String ("System::String example");char dststr [512];
strcpy(dststr, STRING2CHAR(srcstr));

120709 - 9 июля 2012

Предположим, что у нас есть кнопка MyBtn, и есть обработчик события щелчка на ней MyBtn_Click:

   private: System::Void MyBtn_Click(System::Object^ sender, System::EventArgs^ e) 
      {
         //далее код действий по обработке клика по кнопке
         ...
      }

Тогда программно вызвать обработчик события MyBtn_Click можно следующим образом:

         MyBtn_Click(MyBtn, gcnew System::EventArgs);

120828 - 28 августа 2012

Иногда нужно переименовать папку проекта, чтобы сделать его работоспособную копию. Однако, если после этого открыть проект в переименованной папке, то не отображается в конструкторе форма проекта (конструктор выдает ошибку). Очистка и компиляция работают, но это не решает проблему. Чтобы полностью починить переименованный проект, нужно выйти из Visual Studio, удалить файл *.sdf в корневой папке проекта, и снова открыть проект в Visual Studio. Файл *.sdf создастся заново, конструктор форм нормально заработает и покажет форму, доступную для редактирования.

150114 - 14 января 2015

Ранее проект нормально собирался под Windows XP SP3 32-бита в среде разработки Microsoft Visial C++ 2010 со встроенной библиотекой VS 2010 NET 4.0. После перехода на Windows 7 в той же среде Microsoft Visial C++ 2010, но со встроенной библиотекой VS 2010 NET 4.5 начала выскакивать ошибка:

LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt. 

(LINK : fatal error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден). Ошибка исчезла после дополнительной установки пакета обновления SP1 для Microsoft Visual Studio 2010 (KB983509).

Пример кода, который дает такую ошибку:

public class SerialGate
{public:
   enum IN_LINES_NAME {CTS, DSR, RING, RLSD};
   enum OUT_LINES_NAME {DTR, RTS};
   SerialGate();
   ~SerialGate();    
   bool Open(int port, int baud);
   int Send(char* buff, int szBuff);
   int Recv(char* buff, int szBuff);
   void SetLine(OUT_LINES_NAME ln, bool state);
   bool GetLine(IN_LINES_NAME ln);
   void GetPortsInfo(PortInfo* pi);
   void Close();
   void Clean();private:
   HANDLE m_hFile;
   bool state;
   SerialPort^ _serialPort;   //здесь ошибка C3265
};

Ошибку можно устранить, если подключить vcclr.h, объявить _serialPort с использованием шаблона gcroot:

#include < vcclr.h >
 
...
 
   //SerialPort^ _serialPort;
   gcroot< SerialPort^ > _serialPort;    //ошибка C3265 исчезла

Создавать экземпляр _serialPort надо теперь с помощью gcnew:

bool SerialGate::Open(int port, int baud)
{
   bool opened = false;
   _serialPort = gcnew SerialPort();
   ...
   return opened;
}

Можно использовать функцию Format, у которой синтаксис аналогичен синтаксису printf:

int port = 1;
_serialPort->PortName  = "COM" + String::Format("%i", port);

150115 - 15 января 2015

error C2664: CreateFileW: cannot convert parameter 1 from 'char[]' to 'LPCWSTR'
error C2664: CreateFileW: невозможно преобразовать параметр 1 из "char []" в "LPCWSTR"

Пример кода, который порождает ошибку:

char comportpath[20];
sprintf(comportpath,"\\\\.\\COM%d", port);
m_hFile = CreateFile(comportpath, GENERIC_READ|GENERIC_WRITE, 0, NULL,
                     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);  // Ошибка C2664

Как исправить:

   wchar_t comportpath[20];
   swprintf(comportpath, L"\\\\.\\COM%d", port);
   m_hFile = CreateFile(comportpath, GENERIC_READ|GENERIC_WRITE, 0, NULL,
                     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);

Строковый литерал: это обычная строка текста, которая напрямую указывается в коде программы:

char myText [] = "Hello, World!";

Здесь был приведен пример обычной ASCIIZ строки, где каждый символ представлен 1 байтом. Некоторые функции Win API требуют на входе UNICODE-строк типа wchar_t. Для представления строковых литералов как строк из символов wchar_t используют макросы _T(), TEXT() и модификатор L. Примеры:

wchar_t myText1 [] = L"строка 1";wchar_t myText2 [] = _T("строка 2");wchar_t myText3 [] = TEXT("строка 3");

После загрузки данных (методами Row -> Add()) таблица DataGridView начинает себя вести непредсказуемо. Во-первых, полоса прокрутки справа от таблицы не соответствует количеству данных в таблице. Во-вторых, при прокрутке таблицы вниз клавишей со стрелкой "вниз" выскакивает исключение ArgumentOutOfRangeException. Пример полного сообщения об ошибке:

Необработанное исключение типа "System.ArgumentOutOfRangeException" произошло
в System.Windows.Forms.dll". Дополнительные сведения: Значение '154' недопустимо
для 'Value'. 'Value' должно лежать в диапазоне от 'minimum' до 'maximum'.

Решение проблемы: после заполнения таблицы данными нужно вызвать метод PerformLayout(). Пример:

void Form1::dgvCalibrScenarioFill (char* xmlfilename)
{
   IrrXMLReader* xml = createIrrXMLReader(xmlfilename);
   while(xml && xml->read())
   {
      switch(xml->getNodeType())
      {
      case EXN_TEXT:
         break;
      case EXN_ELEMENT:
         Node = xml->getNodeName();
         if (!strcmp("OBJECT", Node))
         {
            if ( (dgvCalibrScenario->RowCount - 1) < i )
            {
               dgvCalibrScenario->Rows->Add();
               for (int clmn=0; clmnColumnCount; clmn++)
                  dgvCalibrScenario->Rows[i]->Cells[clmn]->Value = spaceStr;
            }
            memset(Str,0,sizeof(Str));
            Str1 = xml->getAttributeValue("K");
            if(Str1 != NULL)
            {
               dgvCalibrScenario->Rows[i]->Cells[1]->Value       = gcnew String(Str1);
               dgvCalibrScenario->Rows[i]->Cells[1]->ToolTipText = gcnew String("K");
            }
            fFild = xml->getAttributeValueAsFloat("D");
            if(fFild>0)
            {
               sprintf_s(Str, sizeof(Str), "%1.1f", fFild);
               dgvCalibrScenario->Rows[i]->Cells[2]->Value = gcnew String(Str);
               dgvCalibrScenario->Rows[i]->Cells[1]->ToolTipText = gcnew String("D");
            }
            ++i;
         }//if (!strcmp("OBJECT", Node))
         break;
      }//switch(xml->getNodeType())
   }//while(xml && xml->read())
   delete xml;
   dgvCalibrScenario->PerformLayout();
   if (xmlfilereaded)
      strcpy_s(CFG.F_Calibr, sizeof(CFG.F_Calibr), xmlfilename);
}

В файле sdf сохраняется информация для работы подсистемы Visual Studio Intellisense (подсветка синтаксиса, рекомендации об устранении ошибок, предупреждения и т. п.). Этот файл занимает много места на диске (от 20 мегабайт и больше). Можно ли как-то безопасно удалить sdf-файл, чтобы при повторном открытии решения он создавался заново (или можно ли как-то вообще избавиться от этого файла)?

Опытным путем выяснил, что удаление файла на работоспособность проекта и Visual Studio не влияет. Файл *.sdf будет заново создан, когда Вы откроете проект.

[Каталог для размещения SDF-файла]

Можно также настроить место для сохранения этого файла (например в папке C:\TEMP), что может быть полезным, когда Вы не хотите замусоривать Ваши каталоги с проектами лишними временными файлами. Перейдите в меню Tools -> Options -> Text Editor -> C/C++ -> Advanced, в разделе Fallback Location установите "Always Use Fallback Location" в True и "Do Not Warn If Fallback Location Used" в True. В разделе Fallback Location Вы можете ввести путь наподобие C:\Temp, либо оставить путь пустым, тогда Visual Studio в качестве каталога будет использовать директорию временных файлов в папке AppData.

[Как запретить создание SDF-файла]

Можно совсем отключить базу данных Intellisense: меню Tools -> Options -> Text Editor -> C/C++ -> Advanced -> Disable Database выставьте в true.

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\atlmfc\include\afx.h(24): fatal error C1189:
 #error :  Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version.
 Please #define _AFXDLL or do not use /MD[d]

Как исправить: открыть свойства конфигурации, опцию Общие -> Использование MFC -> задать в "Использовать MFC в общей DLL".

VisualStudio fix stdafx

По умолчанию все поля в структурах обычно имеют выравнивание на 4 байта (32-битное слово). Но как быть, если надо получить упакованную структуру, без пустот?

[GCC]

struct TLdrBlock
{
   u32 ADDRESS;
   u32 COUNT;
   u16 FLAG;
   char FLASHCRC16 [10];
   u32 sizeldrfile;
   u16 crc16;
}__attribute__((packed));

[Visual Studio C++]

#pragma pack(push)
#pragma pack(1)
struct TLdrBlock
{
   u32 ADDRESS;
   u32 COUNT;
   u16 FLAG;
   char FLASHCRC16 [10];
   u32 sizeldrfile;
   u16 crc16;
};
#pragma pack(pop)

[CString -> char*]

Получение массива символов char* из CString с помощью функции CStringA и метода CString::GetString:

static void Usage (void)
{
   CString csExe;
   csExe = AfxGetAppName();
   printf("Utility for add CRC (CRC16 CCITT) to Blackfin LDR-file. Usage:\n");
   printf("%s srcfile.ldr [firmware.bin]\n", CStringA(csExe.GetString()));
}

[char* -> CString]

Получение CString из массива символов char* еще проще - все делается вызовом конструктора:

char src [] = "HelloWorld";CString dst = CString(src);

void WLog (CString S)
{
   CStdioFile file;
   WORD Year, Month, Day, HH, MM, SS;
   CString asYear, asMonth, asDay, asHH, asMM, asSS;
 
   //Получить текущее время.
   CTime t = CTime::GetCurrentTime();
   Year  = t.GetYear();
   Month = t.GetMonth();
   Day   = t.GetDay();
   HH    = t.GetHour();
   MM    = t.GetMinute();
   SS    = t.GetSecond();
 
   asYear  = t.Format( "%Y" );
   asMonth = t.Format( "%m" );
   asDay   = t.Format( "%d" );
   asHH    = t.Format( "%H" );
   asMM    = t.Format( "%M" );
   asSS    = t.Format( "%S" );
 
   //Открыть файл лога на добавление и записать строку.
   if(file.Open(csExeName + CString(".log"),
                CFile::modeCreate
               |CFile::modeNoTruncate
               |CFile::modeWrite|CFile::typeText))
   {
      file.Seek(0L, CFile::end);
      file.WriteString(asYear + asMonth + asDay
                     + CString(" ") 
                     + asHH + CString(":") + asMM + CString(":")+ asSS + CString(", ") 
                     + S
                     + CString("\n"));
   }
}

[Получение размера файла]

static u64 filesize (CString filename)
{
   u64 result = 0;
 
   try
   {
      CStdioFile file( filename,
         CFile::modeRead|CFile::typeBinary );
      result = file.GetLength();
      file.Close();
   }
   catch(CFileException* pe)
   {
      TRACE(_T("File could not be opened, cause = %d\n"),
         pe->m_cause);
      pe->Delete();
   }
   return result;
}

[Чтение файла в двоичном режиме]

static UINT fileread (u8* data, CString filename, UINT size)
{
   UINT result = 0;
 
   try
   {
      CStdioFile file( filename,
         CFile::modeRead|CFile::typeBinary );
      result = file.Read(data, size);
      file.Close();
   }
   catch(CFileException* pe)
   {
      TRACE(_T("File could not be opened, cause = %d\n"),
         pe->m_cause);
      pe->Delete();
   }
   return result;
}

[Запись файла в двоичном режиме]

Если файл filename не существует, то он будет создан, если существует, то он будет уничтожен и перезаписан.

static bool filewrite (u8* data, CString filename, UINT size)
{
   bool result = true;
   try
   {
      CStdioFile file( filename,
         CFile::modeCreate|CFile::modeWrite|CFile::typeBinary );
      file.Write(data, size);
      file.Close();
   }
   catch(CFileException* pe)
   {
      TRACE(_T("File could not be opened, cause = %d\n"),
         pe->m_cause);
      pe->Delete();
      result = false;
   }
   return result;
}

[Запись файла в текстовом режиме на добавление]

В этом примере файл записывается как текстовый лог. Если файл csExeName.log не существует, то он будет создан, если существует, то содержимое строки S будет дописано в конец файла csExeName.log с добавлением метки текущего времени.

void WLog (CString S)
{
   CStdioFile file;
   WORD Year, Month, Day, HH, MM, SS;
   CString asYear, asMonth, asDay, asHH, asMM, asSS;
 
   //Получить текущее время.
   CTime t = CTime::GetCurrentTime();
   Year  = t.GetYear();
   Month = t.GetMonth();
   Day   = t.GetDay();
   HH    = t.GetHour();
   MM    = t.GetMinute();
   SS    = t.GetSecond();
 
   asYear  = t.Format( "%Y" );
   asMonth = t.Format( "%m" );
   asDay   = t.Format( "%d" );
   asHH    = t.Format( "%H" );
   asMM    = t.Format( "%M" );
   asSS    = t.Format( "%S" );
 
   //Открыть файл лога на добавление и записать строку.
   if(file.Open(csExeName + CString(".log"),
                CFile::modeCreate
               |CFile::modeNoTruncate
               |CFile::modeWrite|CFile::typeText))
   {
      file.Seek(0L, CFile::end);
      file.WriteString(asYear + asMonth + asDay
                     + CString(" ") 
                     + asHH + CString(":") + asMM + CString(":")+ asSS + CString(", ") 
                     + S
                     + CString("\n"));
   }
}

Обе директивы подключают заголовочные файлы (с расширением *.h), где находятся определения типов, констант, макросов, внешних переменных. Но заголовочные файлы бывают разные - некоторые поставляются вместе с системой программирования (такой как Visual Studio или Qt) и предназначены для подключения кода "стандартных" библиотек, а некоторые создает сам пользователь.

Готовые загловочные файлы называются predefined, т. е. "уже определенные", и они указываются в угловых скобках (иногда без расширения). Например:

#include < iostream>

Заголовочные файлы, созданные пользователем, указываются в двойных кавычках:

#include "myheader.h"

Чтобы вызвать функцию, написанную на языке C, из кода на языке C++, нужно использовать ключевое слово extern "C", когда декларируется функция C. Тогда можно вызывать эту функцию точно так же, как вызов любой другой функции. Пример:

/* Это код C++ для декларации функции foo,
   которая где-то определена в коде C: */
extern "C" void foo( );

После этой декларации функция foo может быть вызвана в коде C++, примерно так:

extern "C" void foo();
 
void main()
{
   // Вызов функции:
   foo( );
}

А что если нам нужно декларировать сразу несколько функций в коде C++? Для этого лучше всего использовать для них группу, вот так:

/* Это код C++, где декларируется доступ к нескольким
   функциям C: */
extern "C" {
   int foo( );
   double foobar();
};

Когда событие исключения выброшено, но не обработано, то это означает, что не определен подходящий обработчик для вида выброшенного исключения. Это означает также, что вообще не найден никакой замещающий обработчик исключения (ellipsis catch handler, так называемый обработчик последнего шанса).

Итак, что произойдет в таком сценариии? На C++ есть заранее определенная функция terminate, которая будет вызвана, когда для исключение нет подходящего обработчика. Функция terminate затем вызовет другую функцию с именем abort. Функция abort остановит выполнение программы.

Однако также стоит отметить, что функция terminate может быть переопределена для вызова некоторой другой функции – которая не вызовет abort – перед выходом из приложения. Для этого просто используйте функцию set_terminate, и передайте ей имя функции, которую хотите вызвать вместо terminate. Для того, чтобы переданная функция работала стандартным образом, лучше всего в ней вызвать функцию exit для завершения работы приложения.

[Что такое ellipsis catch handler]

На C++ ellipsis catch handler очень прост. Все, что он делает - перехватывает любые не обработанные исключения. Поэтому ellipsis catch handler работает как обработчик последнего шанса, когда нет никакого другого подходящего обработчика. Вот пример того, как может выглядеть ellipsis catch handler:

try
{
   throw "Что-то там произошло";
}
// Это ellipsis catch handler:
catch(...)
{
   cout << "Для этого сценария нет подходящего обработчика";
}

Откуда пошло название ellipsis catch handler для такого обработчика? Причина тривиальна - многоточие "...", которое также называется ellipsis, и обычно оно используется в смысле "et cetera" (и так далее).

error C2146: синтаксическая ошибка: отсутствие ";" перед идентификатором "ulong64"

В данном случае неправильно задан оператор typedef для типа ulong64. Само собой, такая ошибка может возникнуть и при неправильном определени любого типа. Пример ошибочного определения типа:

typedef unsigned _uint64 ulong64;

В этом примере ошибка C2146 произошла из-за отсутствия определения типа _uint64. Для Visual Studio правильным определением 64-битного типа без знака может быть замена _uint64 на __int64, ошибка C2146 пропадет:

typedef unsigned __int64 ulong64;

error C2059: синтаксическая ошибка: неправильный суффикс для числа

Пример кода, который дает такую ошибку:

static const unsigned __int64 textkeys[256] = {
   15498727785010036736LLU,
   7275080914684608512LLU,
   ...

Причина в том, что Visual Studio (в зависимости от версии) может не полно поддерживать современные стандарты C++. Языки C++ и C оба определяют суффикся "ULL" и "LLU" как допустимые для целочисленных литералов. Однако VS2010 эти суффиксы не поддерживает, а VS2012 поддерживает только суффикс ULL.

Устранить ошибку C2059 для данного случая можно заменой суффикса LLU на LU:

static const unsigned __int64 textkeys[256] = {
   15498727785010036736LU,
   7275080914684608512LU,
   ...

error C2589: (: недопустимая лексема справа от "::"

Такая ошибка возникала в следующем коде:

double m = std::min(lof,hif);

Устранить ошибку можно, заключив std::min в круглые скобки:

double m = (std::min)(lof,hif);

В системе программирования MSVC используйте ключевое слово __inline или _inline вместо inline. Ключевое слово inline определено стандартом c99, и этот стандарт (пока) не полностью поддерживается в MSVC.

Цитата из источника [1]: "Ключевое слово inline доступно только в C++. Ключевые слова __inline и __forceinline доступны в обоих языках C и C++. Для совместимости с предыдущими версиями _inline является синонимом __inline".

На платформе MSVC (Visual Studio C++) замените __func__ на __FUNCTION__:

#if defined(_WIN32) || defined(_WIN64)
  #define __func__ __FUNCTION__
#endif

Здесь __FUNCTION__ это так называемый предопределенный макрос, который в Visual Studio C++ разворачивается в строковый литерал, который содержит имя функции, где встретился макрос __FUNCTION__ (подробнее см. [2]).

Следующие ошибки:

error C2143: синтаксическая ошибка: отсутствие ";" перед "тип
error C2275: LINEAR_DATA: недопустимое использование этого типа в качестве выражения

происходят из-за того, что переменная была определена в неправильном месте.

[Пример 1]

Переменная была определена в инициализации переменной цикла for, и код компилировался как код C (не C++), пример:

// Следующая строка давала ошибку компиляции C2143:
for (int i = 0; i < 8; i++)
{
   //Тут тело цикла:
   ...

Исправить ошибку можно, если компилировать код как C++. Или если вынести определение переменной из инициализации цикла:

int i;
for (i = 0; i < 8; i++)
{
   //Тут тело цикла:
   ...

[Пример 2]

static int linear_copy (SRC_PRIVATE *from, SRC_PRIVATE *to)
{
   if (from->private_data == NULL)
      return SRC_ERR_NO_PRIVATE;
 
   LINEAR_DATA *to_priv = NULL; // ошибка C2275
   ...

Здесь ошибка C2275 происходит потому, что переменная to_priv определена не в начале функции. Устранение ошибки:

static int linear_copy (SRC_PRIVATE *from, SRC_PRIVATE *to)
{
   LINEAR_DATA *to_priv;
 
   if (from->private_data == NULL)
      return SRC_ERR_NO_PRIVATE;
   to_priv = NULL;
   ...

IDE Visual Studio не может найти файл заголовка, который находится в корневой папке проекта. Откройте свойства проекта, зайдите в раздел Свойства конфигурации -> Каталоги VC++ и в раздел "Каталоги включения" добавьте $(ProjectDir). Иногда $(ProjectDir) надо также добавить в раздел "Справочные каталоги".

[Ссылки]

1. Inline Functions (C++) site:docs.microsoft.com.
2. Predefined Macros site:docs.microsoft.com.

 

Комментарии  

 
0 #2 Ghost 16.02.2010 03:20
1 совет - тот-же эффект:
#pragma comment(lib, "winmm.lib")
17 совет - если в диалоговом окне несколько итемов с одинаковым ID=IDC_STATIC (а в VS это идентификатор по умолчанию для StaticText и пр.), то результат непредсказуем :sigh:
Цитировать
 
 
0 #1 Ravager 26.11.2008 18:35
Да, для работы с файлами можно пользоваться стандартными API. Функцией CreateFile открываем файл на чтение/запись, потом читаем/пишем с помощью ReadFile/WriteFile или маппим.
Цитировать
 

Добавить комментарий


Защитный код
Обновить

Top of Page