• Как открыть в мобильном браузере сайт на localhost?

    @Mercury13
    Программист на «си с крестами» и не только
    Если мобильное устройство не использует прокси-браузер (Opera Mini и прочие) и висит на той же радиоточке, что и стационарник — 1) настраиваем радиоточку так, чтобы с WiFi-клиентов был доступ в локальную сеть; 2) поднимаем на стационарном компьютере веб-сервер. Доступ — http://192.168.1.2:3000/site, внутренний IP-адрес компьютера можно узнать программой ipconfig; порт (3000) определяется настройками веб-сервера.

    Если либо используется прокси-браузер, либо смартфон в интернете через ОпСоСа, надо поднять веб-сервер и сделать, чтобы он был виден из интернета. Либо залить на хостинг, либо пробросить порт на маршрутизаторе. Доступ — http://12.34.56.78:3000/site, внешний IP-адрес можно узнать через 2ip.ru. Порт (3000) зависит от настройки перенаправления портов, и для безопасности его крайне нежелательно делать стандартными 80, 3128 и 8080.

    Как пробросить порт на маршрутизаторе — гуглите уж сами; состоит из двух вещей: как зафиксировать IP и как вбить в NAT новое правило перенаправления.
    Ответ написан
    Комментировать
  • Как заполняются многомерные массивы?

    @Mercury13
    Программист на «си с крестами» и не только
    New(Psh[4, 1]);
    Переводим на C#. Правда, тут есть одна загвоздка. Массивы в c# начинаются только с 0, и надо смотреть: либо заводить холостой 0-й элемент, либо корректировать индексы. Так что перед нами два варианта.
    Psh[4][1] = new short[393];
    Psh[3][0] = new short[392];
    (для примера работаю со вторым)

    function OpenA(FileName: ShortString; Mode: LongWord): integer;
    Stream OpenA(string FileName, FileAccess access);


    Как перевести ReadA? Это не так просто. Дело в том, что языки со стандартным промежуточным кодом (к ним относятся в первую очередь языки семейства Java и .NET) обычно пишут в порядке байтов Motorola. На Delphi файл писался в порядке Intel. Так что мы считываем 392 word’а, а затем смотрим, что делать: либо обернуть байты, либо сказать: теперь у нас новый стандарт — Motorola!

    if (!ReadA(Df[3][0], Psh[3][0], 392) {
      CloseA(Df[3, 0]);
      Psh[3][0] = null;
      return;
    }


    И последний рывок.
    Pkts[4, 1]:= @Psh[4, 1]^[9];
    Из-за отсутствия указателей (точнее, есть они, но мы же не хотим unsafe-кода) придётся Pkts разбивать на две части: ссылку на массив и смещение в массиве.
    Pkts[3][0].array = Psh[3][0];
    Pkts[3][0].offset = 8;
    Ответ написан
  • Как работает quicksort?

    @Mercury13
    Программист на «си с крестами» и не только
    Первое.
    Ваш код явно неортодоксальный, и, по-моему, он делает лишние сортировки.
    UPD. Алгоритмическая сложность такого кода порядка O(n² log n), более точную границу подобрать не могу, да и имеет ли смысл?

    Второе.
    Пусть массив отсортирован по убыванию, слева — одно число, справа — сто, делящий 100. Скажем, 101, [100], 99…0.
    Тогда i = 0, j = 101, и имеем массив 0, 100, 99…1, 101.
    Теперь i = 1, j = 100; arr[i]<pivot в пролёте, arr[j]>pivot в пролёте, i<j, и проводим обмен 0, 1, 99…2, 100, 101.
    На третьем шаге выходит 0, 1, 2 98…3, 99, 100, 101. И это при том, что делящий 100 и он давно встал на место.
    Когда мы прокрутим итерацию до конца, массив будет полностью отсортирован по возрастанию.
    Вывод: делящий элемент не обязательно будет точно делить массив, хоть многое что крутится вокруг него. И если будет сильный дисбаланс, часть элементов перейдёт из большего массива в меньший.
    Ответ написан
    Комментировать
  • Как посчитать сумму чисел в файле?

    @Mercury13
    Программист на «си с крестами» и не только
    Пока вижу вот что.
    1. a += buff[i]; Вы суммируете коды символов, а не их числовые значения. Правильно a += buff[i] - '0';

    2. Эти строки дублируют друг друга.
    fin >> buff;
      fin.getline(buff, 11);

    Сначала получаем 0123456789, потом файл кончился и на место 0 записываем нулевой символ (NUL).

    3. for (int i = 0; i < 11; i++) — неуниверсальная конструкция. Плюс цифр в строчке всего 10, поэтому надо i < 10.

    4. Так сумму чисел или сумму цифр?
    Ответ написан
  • Как реализовывается функцию(main), которая будет производить проверку?

    @Mercury13
    Программист на «си с крестами» и не только
    Мы пишем модульный тест собственными силами, без фреймворка. Это значит, что нам надо каким-то образом сымитировать работу фреймворка, но собственными силами и минимумом строчек.

    Для удобства нам потребуется одна функция — и немного препроцессорной магии.
    #include <iostream>
    #include <cstring>
    
    void doAssert(bool condition, int line)
    {
        if (condition) {
            std::cout << "Line " << line << " ok" << std::endl;
        } else {
            std::cout << "Line " << line << " FAILED" << std::endl;
        }
    }
    
    #define ASSERT(x) doAssert(x, __LINE__)
    #define ASSERT_STREQ(x, y) doAssert((std::strcmp(x, y) == 0), __LINE__)


    А теперь что-нибудь протестируем. Например, создание строки. Для простоты я проверю не вашу строку, а std::string.
    void testStringCreation()
    {
        std::string s;
        ASSERT(s.length() == 0);
        ASSERT(s.empty());
        ASSERT_STREQ("", s.c_str());
    }

    Проверим ещё операцию +=.
    void testPlusEq()
    {
        std::string s1 = "Alpha";
        std::string s2 = "Bravo";
        s1 += s2;
        ASSERT_STREQ("AlphaBravo", s1.c_str());
    }
    
    int main()
    {
        testStringCreation();
        testPlusEq();
        return 0;
    }

    Нужны ещё тесты — создавай новую функцию и вызывай её из main.
    Если какой-то тест не проходит — уничтожаем из main все вызовы, кроме несработавшего, и начинаем отладку.

    А теперь немного о том, какие должны быть модульные тесты.
    1. Изолированные. Если уничтожить часть тестов или запустить их в другом порядке, ничего не должно меняться — при условии, конечно, что никто не обращается к чужой памяти.
    2. Каждый модульный тест проверяет одну концепцию, которая указана в его названии. Например: строка создаётся, работает операция +=, и т.д. Будет очень много дублей кода, просто смиритесь с этим.
    3. Но нет ничего зазорного, что какой-то тест полагается на концепции, испытанные в других тестах. Если мы испытываем +=, то полагаем, что конструктор копирования работает, и не будем испытывать это.
    4. Модульным тестам иногда приходится иметь дело с внутренним поведением объекта. В таком случае объект должен выдавать наружу некоторую информацию — например, соответствующие функции обычно private, но по какой-то препроцессорной команде они становятся public.
    5. Модульные тесты обычно имеют дело с простейшим поведением объекта. Это не нагрузочные тесты, надо с этим просто смириться.
    6. Принцип работы теста таков. Создать исходные объекты, убедиться в том, что их ВНУТРЕННЕЕ (неспецифицированное, зависящее от реализации) состояние верное, провести некое действие, убедиться, что действие проведено правильно. Например, пусть строка выделяет память с запасом, и надо проверить, как она расширяется — тогда при присваивании "Alpha" мы убеждаемся, что выделено менее 10 символов, затем проводим s1 += "Bravo", затем убеждаемся, что выделено, например, 16 символов.
    7. Но заранее не нужно проверять, верно ли ВНЕШНЕЕ (заспецифицированное) состояние. Если мы пишем s1 = "Alpha", значит, строка равна "Alpha", и точка. Все случаи, возможные в операции string = char*, разумеется, покрыты другими тестами.
    8. Если вдруг в «боевом» коде обнаружится ошибка, надо сделать юнит-тест, её повторяющий, затем исправить ошибку и констатировать, что тест проходит и остальные тесты не «упали».
    9. То же самое, если ошибка случилась, когда проверяли другую концепцию. Проверяем +=, а глюк в конструкторе копирования — заводи новый тест, покрывающий эту ошибку.
    10. В проверках на равенство принят порядок ASSERT_EQ(что_должно_быть, что_реально_есть).
    Ответ написан
    Комментировать
  • В чем заключается проблема с кодировкой на хостинге?

    @Mercury13
    Программист на «си с крестами» и не только
    $resultData = mb_convert_encoding($resultData, 'Windows-1251', 'UTF-8');

    Думаю, автодетекция кодировки заглючила.
    Ответ написан
    2 комментария
  • На какой версии C++ Builder(максим. доступной) можно запустить проект C++ Builder 6?

    @Mercury13
    Программист на «си с крестами» и не только
    Переписывать придётся.

    1. Потому что в паскале — string и есть string, и раньше он был Ansi-, а теперь Unicode-. А на Си — именно что AnsiString/UnicodeString.
    2. То же самое касательно char. В Паскале-то можно объявить, что отныне char двухбайтовый, а на Си — есть стандарт, и по стандарту Си char — это один байт.
    3. Алгоритмы-то какие используются? Возможны какие-то алгоритмические уловки на манер алгоритма поиска подстроки Бойера-Мура — там тоже придётся переписывать.
    4. Если программа работает с файлами или сетью — надо указывать, как юникодные строки сериализовать. На Windows распространены UTF-16 Intel и UTF-8. На этот счёт AnsiString почти не используется в истинно юникодных программах из-за того, что хранит кодировку; если нужна цепочка байтов — есть RawByteString или std::string.
    5. Иногда старые программы работают с Юникодом через WideString (обёртку над BSTR). Сейчас этот WideString придётся массово менять на Unicode-.
    6. По-другому пишутся стандартные хедеры Delphi/VCL. Ранее #include <Forms.hpp>, сейчас — #include <Vcl.Forms.hpp>.

    Версия Builder — ну, любая, какую найдёшь. Не забудь только, что из C++11 есть enum class (не совсем стандартный, правда) и кое-что ещё. Я не проверял, как работает новый clang под XE10, но это только вопрос эффективности, «въедливости» и C++11 — миграцию на Юникод придётся проводить, как и раньше.

    Насчёт «въедливости». Я всегда считал, что G++ — самый «въедливый» компилятор. Но по статическому анализатору clang (встроен в Qt Creator, мой сейчас рабочий инструмент) кажется, что он переплюнул и этот рекорд. И сделать, чтобы один и тот же файл компилировался под древним Borland и суперсовременным clang — тяжеловато, по-моему. А именно это требовал Embarcadero от XE3 до XE8, если нужны версии и под x86, и под x64. Так что пусть BCC32 идёт туда, куда ему и дорога!

    Да, приходилось мигрировать на Юникод, когда появился Builder XE. Как я был рад!

    Знаю, что подхватит и сконвертирует XE2. Про более новые — не знаю ничего. Но даже если не сконвертирует — ничего не стоит построить новый проект с нуля и добавить туда все модули и формы. Но сконвертировать — это лишь половина вопроса; дальше придётся добиться компилируемости, а потом — и Юникода.
    Ответ написан
    Комментировать
  • Для чего используются "методы по умолчанию" на практике?

    @Mercury13
    Программист на «си с крестами» и не только
    Это приближает интерфейсы Java к примесям.

    1. Очень часто бывает, что у какой-то функции есть реализация, опирающаяся на другие функции — либо базовая неоптимальная, либо вообще единственно возможная. Чаще всего это избыточные функции-утилиты. Пишу на Си++
    class Stream {
    public:
      virtual void write(size_t length, const void* data) = 0;
    
      // пишет в поток word в машинном порядке байтов
      void writeW(uint16_t data) {
        write(2, &data);
      }
    };

    За что вообще так ненавидят множественное наследование? За дублирование данных! Вот у нас некая штука, без единого поля данных — почему в Java она класс, а не интерфейс?

    2. Какую-то функцию переопределяют настолько редко, что лучше сделать ей реализацию на месте. Опять Си++.
    class ErpConnector {
    public:
      // 80% модулей не могут экспортировать данные в систему управления
      //   предприятием — напишем базовую реализацию.
      virtual bool canExportData() const { return false; }
      virtual void exportData() const {}
    };


    3. И просто документирование, как оно должно себя вести :) Из реального проекта, снова Си++.
    void im::DateGrouper::toFirstDateOfPeriod(dt::Date& aDate, int aStart) const
    {
        aDate = toDate(toInt(aDate, aStart), aStart);
    }

    Класс служит для группировки дат в периоды (недели, месяцы, годы). aStart — это дополнительный int, позволяющий начать год с февраля или месяц с 15-го. Эта функция переводит дату «на месте» в первую дату периода. Она крайне неоптимальна (дату в число, затем число опять в дату) и потому переписана почти во всех в реализациях.
    void im::MonthGrouper::toFirstDateOfPeriod(dt::Date& aDate, int aStart) const
    {
        if (aDate.day < aStart)
            aDate.addMonthsMechanical(-1);  // механически вычесть 1 месяц; дата может стать неверной.
        aDate.day = aStart;
        aDate.fixupTo1();    // неверную дату привести к 1-му числу следующего месяца
    }


    Но по крайней мере понятно, как она должна работать.
    Ответ написан
    Комментировать
  • Как присвоить выражение boolean полю TObject?

    @Mercury13
    Программист на «си с крестами» и не только
    Попробую угадать задачу. Есть некое поле (назовём его UserData), но у нас-то пользовательские данные boolean!

    Решение 1. Прямое преобразование, невзирая на несовместимости типов.
    someObj.UserData := TObject(true);
    someBool := boolean(someObj.UserData);

    + Эффективнее всех.
    − Запрещено разыменовывать.

    Решение 2. Создать некий объект DummyObject. Обозначить nil=false, <>nil = true.
    someObj.UserData := dummyObject;
    someBool := (someObj.UserData <> nil);
    
    initialization
      dummyObject := TObject.Create;
    finalization
      dummyObject.Free;
    end.

    + Достаточно эффективно, можно разыменовывать.
    − Если в классе есть неотключаемое автоуничтожение UserData — облом!

    Решение 3. Создать класс TBoolObject.
    TBoolObject = class
    public
      Value : boolean;
      // конструкторы опущу, деструктор не нужен
    end.
    
    someObj.UserData := TBoolObject(true);
    someBool := (someObj.UserData as TBoolObject).Value;

    + Работает, когда в классе есть автоуничтожение UserData.
    − Иначе — овчина не стоит выделки, тогда нам эти объекты придётся уничтожать самим.

    Если же у вас тут экономия памяти и надо в одной памяти держать object или boolean, работает решение 4.
    TObjBool = record
    case integer of
    0 : ( asObj : TObject );
    1 : ( asBool : boolean );
    end;

    + Очень эффективно.
    − Программист сам должен следить за тем, что там: объект или буль.
    Ответ написан
    2 комментария
  • Что это означает?

    @Mercury13
    Программист на «си с крестами» и не только
    Это дело у вас на компьютере. L2TP — это самый простой вид туннелирования, некогда популярный в домосетях. Локальный трафик был бесплатный, интернет — дорогой, и надо сделать, чтобы интернет включался и выключался по желанию пользователя. Для этого у пользователя были два соединения: локальная сеть и VPN, и таблицей маршрутизации указывалось, какой трафик идёт в локальную, а какой — в VPN.
    Что наш L2TP делает сейчас, в 2016-м — я не знаю. Может, действительно кто-то на вашем компьютере скрывался от РКН…Правда, для этого ещё надо включить IPSec, 12 лет назад этого просто не было.
    Ответ написан
    Комментировать
  • Есть ли алгоритм для наиболее быстрого нахождения включает ли одна фигура другую?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Надо указывать не только то, что все точки первой фигуры внутри второй, но и то, что все точки второй фигуры снаружи первой.
    2. Даже примитивный алгоритм даёт скорость O(mn). Сколько у вас примерно точек?
    3. Можно воспользоваться эвристиками. Если охватывающий прямоугольник 1-й фигуры целиком во 2-й — ДА. Если вылезает из охватывающего прямоугольника 2-й — НЕТ.
    4. Можно поступить так. Отсортируем все точки той фигуры, которую проверяем на точки, по X-координате. Также отсортируем все отрезки той фигуры, которую проверяем на отрезки, по X-координате левой точки. Указатель отрезков ставим в начало перечня отрезков. Заводим список активных отрезков. Проходимся по всем точкам, поступая так…
    • Для всех отрезков в списке активных: если точка вышла из [X1, X2) — исключить из списка!
    • Смотрим на тот отрезок, что под указателем. Если X точки >= X1 — идём по фигуре дальше; если заодно X < X2 и Y выше отрезка (X1,X2 — (Y1,Y2) — включаем отрезок в список.
    При определённом устройстве списка (куча по X2) имеем O(n log n). И если меньшая фигура имеет сложность примерно как у большей (т.е. не логарифм) — имеем выигрыш.
    Ответ написан
  • Как решить проблему вывода из потока?

    @Mercury13
    Программист на «си с крестами» и не только
    #include <iostream>;
    Это не синтаксис Си, а директива препроцессора. Убери точку с запятой.

    #include <conio.h>;
    conio.h — это не стандартный хедер, а Windows-специфичный. Разумеется, вместо _getch() придётся использовать что-то другое.
    Ответ написан
    Комментировать
  • Движение персонажа под углом?

    @Mercury13
    Программист на «си с крестами» и не только
    Расскажу, как поступал я. Писал на Java ME, так что особо сложных идей не было.

    У каждой плитки была алгоритмически заданная поверхность (pavement). В более новых версиях движка приделали и потолок — впрочем, неважно.
    Для простоты введём несколько ограничений. Все они, разумеется, обходятся усложнённой логикой.
    • Ни на чём, кроме плиток, персонаж стоять не может. Тогда можно обойтись флагом: isSupported: true/false.
    • Поднимаясь по склону, персонаж не ударится головой в потолок. Кстати, у нас было два разных геймдиза и два разных подхода — когда один увидел этот баг, пошёл жаловаться, второй взял ограничение на карандаш и не делал таких уровней.
    • Неровности достаточно велики, чтобы можно было проверить ноги слева и ноги справа — и понятно, на какой высоте ему стоять.
    • Соотношение «площадь ног / размер хитбокса» достаточно велико, чтобы, пока персонаж падает, его гарантированно вытолкнуло из стены.
    В зависимости от скорости персонажа и крутизны склонов подберём epsilon — диапазон поиска поверхности.
    Предположим, наш персонаж стоит на чём-то и двигаем его вправо.
    1. Сдвинем его туда, где он должен быть.
    2. Попробуем подстроить высоту, взяв правую сторону ног и отыскав новую поверхность в диапазоне (y — epsilon, y + epsilon). То же самое с левой стороной ног. То, что выше — и есть наш новый y. Если не получилось — УПИРАЕТСЯ (см. шаг 3).
    3. Если на шаге 2 персонаж не упирается в стену, проверим на упор весь хитбокс. Если упирается — вытолкнем его из стены, снова подстроим высоту по принципу 2.
    Ответ написан
    2 комментария
  • Как округлить число до 2-3 знаков после запятой на "pascal.A.B.C" если выдается ошибка?

    @Mercury13
    Программист на «си с крестами» и не только
    Обычно в учебных работах (и даже не в учебных — именно так работает Excel) не округляют, а выводят с округлением.
    writeln(x1:0:3);
    writeln(x2:0:3);

    Каких-то продвинутых средств округления, которые округляют дробное число до i-го знака я не вижу.
    Кроме того, вы подсказали, с каким Паскалем вы имеете дело, я загрузил его и повторил прошлую ошибку — такая ошибка происходит именно что из-за округления NaN.
    Ответ написан
    Комментировать
  • Корректно ли такое сравнение вещественного числа с нулём?

    @Mercury13
    Программист на «си с крестами» и не только
    Так сравнивать безопасно. Единственный вопрос — может ли появиться ноль во время вычислений и не сломает ли это логику программы?
    Лучше, конечно, для этого использовать не 0.0, а 0.0f.
    Ответ написан
    2 комментария
  • В чем ошибка в операторе присвоения?

    @Mercury13
    Программист на «си с крестами» и не только
    Здесь нужны две операции: «присвоить» (возможно, автоматически сгенерированная) и конструктор копирования.
    class MP3_Format {
       MP3_Format(const char* c_string);
       MP3_Format& operator=(const MP3_Format&);   // велика вероятность, что компилятор её сгенерирует автоматически
    };


    Если пишете на C++11 — вместо операции «присвоить» нужна операция «присвоить из временного объекта». Опять-таки, годится автоматически сгенерированная.

    Семантика языка C++ в этой строчке такова. Мы создаём временный объект MP3_Format("Summertime"), а затем переносим его в наш mp3a. Скорее всего, оптимизатор уберёт этот перенос и инициализирует mp3a на месте, но такова уж семантика…
    Ответ написан
    1 комментарий
  • Ошибка времени выполнения: Значение было недопустимо малым или недопустимо большим для Int32, как это решить?

    @Mercury13
    Программист на «си с крестами» и не только
    1. У вас неправильная формула корней.
    2. Хотелось бы узнать, чему равны a, b, c.
    3. Зачем вы округляете?
    4. Собственно причина ошибки. Думаю, у вас отрицательное d, sqrt(d) = NaN (не-число), и ваша реализация Паскаля вот так поступает, когда нужно округлить NaN.
    Ответ написан
    Комментировать
  • Существуют ли роутеры способные раздавать интернет, беря его от http-прокси?

    @Mercury13
    Программист на «си с крестами» и не только
    «Раздавать интернет» — это маршрутизация и NAT, 3-й уровень стека протоколов.
    «Брать интернет от HTTP-прокси» — это более высокий уровень (6 или 7 — даже спец ногу сломит).

    Если вы хотите абсолютно прозрачную систему, не требующую от юзверей никаких настроек, нужен
    • либо хитрый шлюз прикладного уровня (ALG) — тогда облом;
    • либо «прозрачный» прокс в локальной сети, способный работать в режиме шлюза — тогда в нём этот ALG уже есть, и подойдёт абсолютно любой маршрутизатор, правда, его придётся слегка поднастроить (отключить NAT, включить брандмауэр, наладить маршрутизацию). Раз уж брандмауэр, чтобы юзвери куда попало не ходили — лучше серия ZyXEL Keenetic, там брандмауэр неплохой. Как проверить? В настройках TCP/IP сделай шлюзом маршрутизатор. Если всё заработает, как надо — бери Keenetic, он дороговат, но работать будет.
    • либо — говорит 15432 — наладить внешней сетью VPN к прокси. Конфигурация экзотическая, как работает, я не в курсе. Если проверишь и заработает — подойдёт любой маршрутизатор, способный на внешнюю сеть через такой вот VPN. Желательно процессор получше, обычно VPN’ы на порядки медленнее простого L3+NAT.

    Если вы можете позволить, чтобы юзвери настраивали прокс в своём браузере — тогда любой маршрутизатор с отключаемым NAT и хорошим брандмауэром.
    Ответ написан
    Комментировать
  • Почему появляется ошибка компилятора '?

    @Mercury13
    Программист на «си с крестами» и не только
    Ошибся тут не компилятор, а линкер. Этот вопрос идёт со времён Си и происходит из технологии компиляции: несколько единиц компиляции компилируются независимо, а потом собираются воедино компоновщиком (линкером).

    Статический член класса — это глобальная переменная, и если объявлять её в каждой единице компиляции, то какую потом линкеру брать? Вот мы и пришли к выводу: static int counter; в хедере — это тот же extern. Другими словами, мы не объявляем переменную, а говорим: не беспокойся, компилятор, в какой-то единице компиляции она будет.

    И решается этот вопрос так же, как для любого extern’а: в одной единице компиляции (CPP) объявляем
    int Skript::counter;
    Можно также статически инициализировать этот счётчик:
    int Skript::counter = 0;

    Ах да. А технология эта с единицами компиляции откуда? Из ассемблера. Если программа у нас почти на всю память в 16 килобайт, то ассемблерный текст намного, намного превышает память ПК. Вот и приходится ассемблировать по частям, а потом собирать куски полуготового машинного кода в полную программу. Наконец, чтобы язык заработал, КиР пришлось написать только компилятор Си, а линкер — имеющийся.
    Ответ написан
    Комментировать
  • Как указать путь создаваемого файла в c++?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Нам придётся работать с юникодными (wchar_t* / wstring) именами файлов. Использовать функции wfopen или CreateFile.
    2. Чтобы заполучить путь к рабочему столу, надо исполнить вот что.
    std::wstring desktopPath;
    wchar_t tmp[MAX_PATH + 1];
    if (SHGetSpecialFolderPathW(HWND_DESKTOP, tmp, CSIDL_DESKTOPDIRECTORY, FALSE))
        desktopPath = tmp;

    Не забудь подключить к программе соответствующие хедер и библиотеку, загугли уж сам, какие нужны.
    Ответ написан