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) Устранил ошибку следующим образом: 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... - тогда будут видны все варианты описаний прототипов функции. 051014 - 14 октября 2005
rand()%n; Изменение поведения (кода) методов класса - открыть исходный файл *.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 Здесь "$(ProjectDir)$(OutDir)\$(TargetFileName)" означает полный путь к упаковываемому экзешнику (путь этот составлен из специальных макроопределений Visual Studio), а /Q /B- просто опции для AsPack, которые указывают без вопросов всё сделать и не делать бэкапа. - для отладки макроопределений удобно в командную строку Build Event подставить команду echo макроопределение(я) >testfile.txt. В результате в файле testfile.txt можно прочитать, что подставляется вместо макроопределения. 060203 - 3 февраля 2006
Глобальные переменные в C++ можно определять как внутри основного тела программы _tmain, так и до нее - сразу после одной или нескольких (если есть) строк include. Инициализировать переменные, объявленные до _tmain, можно только вместе с объявлением. Уже существующие (написанные ранее) процедуры из файлов *.cpp удобно использовать следующим образом: - наш *.cpp файл с нужными функциями переписываем в корневую папку проекта; 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) Само собой, 0xFFFFFFE0 никогда не равняется 0x0000000E0, и код не работает. В заключение привожу правильный код варианта 2: switch (ch0) Другой метод побороть проблему - применить тип переменной unsigned. 060302 - 2 марта 2006
Перехват Ctrl-Break в консольном приложении можно установить с помощью функции SetConsoleCtrlHandler - позволяет, кроме того, реагировать на Ctrl-C и события закрытия консоли. Пример: 1. Описываем обработчик консоли: BOOL CtrlHandler( DWORD fdwCtrlType )/* Обработчик исключительных ситуаций консоли, типа нажатия на Ctrl-Break 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 (обычно он используется для редактирования однострочного текста). По шагам: - перетаскиваем его на конструируемое окно. 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). 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. #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, то вводить ничего нельзя, можно только выбирать из списка. Здесь показано, как выполнить действия по завершению программы. Для этого в момент инициализации (для приложения - диалогового окна это будет, например, тело 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 забыл вставить строчку #include "stdafx.h". [Через имя класса приложения] Нужно добраться до свойств объекта CWinApp, а там обилие возможностей - например, у него есть переменная m_pszExeName, которая как раз и содержит имя исполняемого файла без расширения. Если, например, у вас приложение типа MFC-диалог, то у него обязательно есть класс приложения, обычно называемый Cимя_программыApp (реализация класса в имя_программы.cpp, заголовочный файл имя_программы.h). Этот класс Cимя_программыApp является производным от класса CWinApp (задается в имя_программы.h). Экземпляр класса объявлен в имя_программы.cpp, и обычно носит имя theApp. Что нужно сделать по шагам: - узнаем название класса приложения (виден в корне дерева Class View, название класса заканчивается на App). Например, название класса Cping5bApp. 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); Теперь фон текста будет прозрачный. Текст рисуется поверх всех элементов управления на окне, как будто их и не было. Если текст попадает при выводе за пределы окна, он обрезается или не прорисовывается. 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 При работе со строками важно учитывать следующее отличие 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() Пример на 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") 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 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... varOutputList.SetWindowText(csFN + ", ["+ csIP +"]"); UpdateWindow(); Если Вы создали приложение с помощью визарда как диалоговое окно, и добавили обработчики сообщений для диалогового окна 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 в класс вашего диалога. Практическая реализация по шагам (на примере простейшего приложения на основе стандартного диалога): 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);} Постоянно забываю эти простые специальные символы. 070426 - 26 апреля 2007
- Resource View\папка Bitmap\правая кнопка Add Resource...\выбираем Bitmap\нажимаем кнопку New 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: - в свойствах проекта, в раздел 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)): Причина - переменная *HidDevices имела тип PHID_DEVICE, а calloc возвращает тип void *. Дело в том, что в проекте CPP компилятор более строго проверяет соответствие типов, чем в проекте на чистом C. Вариантов решения 2 - либо поменять тип проекта (с CPP на чистый C), либо применить явное преобразование типа, например так: Решение проблемы было найдено на 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): - скачать 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 Для этого идем в 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' Пример кода, который порождает ошибку: 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; clmn В файле 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". По умолчанию все поля в структурах обычно имеют выравнивание на 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. |