Все технические форумы на одном сайте Удобный поиск информации с популярных форумов в одном месте
Вопрос: Как не надо писать программы

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

Итак:

Когда вы пишете графическую программу, вы не должны ...

... рассчитывать на то, что размер экрана постоянен, и равен 640 пикселей по горизонтали на 480 по вертикали. Простейший код - заставка: приветствие пользователя. В центре экрана должна появиться надпись 'Hello'. Что делают новички?
Код Pascal
1
2
{ инициализация графики }
OutTextXY(320, 240, 'Hello');
Так делать не надо. Потому что это сейчас программа запускается в режиме VgaHi (640x480). Позже вам может понадобиться несколько видеостраниц, а в этом режиме их нет. Придется инициализировать или VgaMed (640x350), или VgaLo (640x200). В случае VGALo вы вообще не увидите "заставки", просто потому что она будет вне экрана.
Написав код правильно:
Код Pascal
1
2
{ инициализация графики }
OutTextXY(GetMaxX div 2, GetMaxY div 2, 'Hello');
, вы избегаете таких проблем, это будет корректно работать в любом режиме, хоть 320*200, хоть 640*480 (и даже в более высоких разрешениях).

... использовать "магические числа", полагаясь на то, что вы всегда будете использовать для вывода текста один и тот же шрифт, и работать в одном и том же разрешении.

Этот пункт является по сути продолжением предыдущего пункта. Итак, была поставлена задача вывести несколько строк, и обвести их прямоугольником (что-то подобное всплывающему меню в Windows-приложениях). Вот так эта задача была решена:
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uses graph;
var gd, gm, i: integer;
const
  s: array[1 .. 5] of string = (
    'one', 'two', 'three', 'four', 'five'
  );
begin
  initgraph(gd, gm, '');
  for i := 1 to 5 do
  begin
    outtextxy(10, 10 + 15 * (i - 1), s[ i ]);
  end;
  rectangle(5, 5, 60, 10+15*5);
  readln;
  closegraph;
end.
При запуске программы получается достаточно приемлемый результат. И тогда программист берет и выносит этот код в отдельную процедуру, справедливо полагая, что это поможет ему выводить разные "менюшки" в разных местах экрана:
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
uses graph;
var
  gd, gm, i: integer;
  i: integer;
 
procedure Print(px, py: integer;
                const s: array of string; n: integer);
var i: integer;
begin
  for i := 1 to n do
  begin
    outtextxy(px, py + 15 * (i - 1), s[i - 1]);
  end;
  rectangle(px - 5, py - 5, px + 50, py+15*n);
end;
 
const
  s: array[1 .. 5] of string = (
    'one', 'two', 'three', 'four', 'five'
  );
begin
  initgraph(gd, gm, '');
  print(10, 10, s, 5);
 
  readln;
  closegraph;
end.
Этот код работает абсолютно так же, как и предыдущий:

Как не надо писать программы

после чего программист, считая, что здесь прокола не будет, использует эту процедуру в своей программе "по полной". И в какой-то момент сталкивается с проблемой:

Как не надо писать программы

Шрифт был изменен с дефолтного на SmallFont, отображение уже не такое, как прежде, и придется искать неправильную константу и исправлять ее (скорее всего - методом подбора). А этого опять же можно было избежать, сразу ориентируясь на работу с разными шрифтами:
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure Print(px, py: integer;
                const s: array of string; n: integer);
var i, curr_x, curr_y, max_x: integer;
begin
  max_x := 0; curr_y := 0;
  for i := 1 to n do
  begin
    curr_x := textwidth(s[i - 1]);
    if curr_x > max_x then max_x := curr_x;
 
    outtextxy(px, py + curr_y, s[i - 1]);
    inc(curr_y, textheight(s[i - 1]) + 5);
  end;
  rectangle(px - 5, py - 5, px + max_x + 5, py + curr_y);
end;
Никаких "магических чисел", ширина и высота текста вычисляются библиотечными функциями. Такая процедура будет работать с любыми выбранными шрифтами, и картинка будет одинаково правильной:
(smallfont, 4)

Как не надо писать программы


(gothicfont, 5)

Как не надо писать программы

... использовать числовые значения вместо именованных констант.
В общем-то, этого правила стоило бы придерживаться не только при программировании графики, но и вообще при любом программировании, особенно начинающему программисту. Однако, как показывают вопросы, задаваемые на форумах, этого правила новички как раз и не придерживаются. А зря. Мне, например, гораздо сложнее понять, что такое:
Код Pascal
1
2
3
4
5
SetFillStyle(1, 12);
fillEllipse(i, j, r, r);
delay(1000);
SetFillStyle(1, 0);
fillEllipse(i, j, r, r);
или
Код Pascal
1
2
3
4
SetTextJustify(1,2);
OutTextXY(ygmin-5, xgmin-10, 'fi');
SetTextJustify(2,1);
OutTextXY(ygmax+170, xgmax-160, 't');
, чем разобраться вот в таких кодах:
Код Pascal
1
2
3
4
5
SetFillStyle(SolidFill, LightRed);
fillEllipse(i, j, r, r);
delay(1000);
SetFillStyle(SolidFill, Black);
fillEllipse(i, j, r, r);
и
Код Pascal
1
2
3
4
SetTextJustify(CenterText, TopText);
OutTextXY(ygmin-5, xgmin-10, 'fi');
SetTextJustify(RightText, CenterText);
OutTextXY(ygmax+170, xgmax-160, 't');
Ведь компилятор все равно подставит те же самые значения (выигрыша от того, что задаются числа не будет никакого), но программисту будет гораздо проще понять код, и, следовательно, сопровождать и модифицировать его.

Ну, и еще одно замечание. Не перемешивайте логику и интерфейс. Я уже устал говорить об этом, но, похоже, меня не слышат. Если вы делаете графическое меню, то функция отображения должна только отображать его, ни в коем случае не выполняя действия, "подключенные" к этому пункту меню. Потому, что если вам потом захочется сделать другое меню (или текстовое, или тоже графическое, но, скажем, трехмерное), то при разделенных логике/интерфейсе достаточно будет написать новую функцию, отображающую менюшки, а все остальное останется без изменения. В большинстве же проектов, которые пишут студенты начальных курсов ВУЗов, проще переписать всю программу заново, чем чуть-чуть изменить ее внешний вид (потому что все переплетено настолько вычурно, что только потяни за одну ниточку, все запутается окончательно).
Ответ: Решил дополнить список того, как делать не надо.
То, о чем будет сказано ниже - относится в основном к новому компилятору - FPC...


Не используйте в качестве параметров функционального (процедурного) типа функции с неправильной сигнатурой.


Объясню на примере. Вы все, конечно, слышали, что для передачи в подпрограмму своей функции, надо предварить ее имя операцией взятия адреса? Далеко ходить не буду, открываем один из примеров, идущих с FPC, называется winhello.pp, находится он в папке \FPC\{версия}\demo\win32, и создается в этом примере простейшее Win32 приложение. Вот одна из процедур этой программы:
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{ Register the Window Class }
function WinRegister: Boolean;
var
  WindowClass: WndClass;
begin
  WindowClass.Style := cs_hRedraw or cs_vRedraw;
  WindowClass.lpfnWndProc := WndProc(@WindowProc); { <--- !!! }
  WindowClass.cbClsExtra := 0;
  WindowClass.cbWndExtra := 0;
  WindowClass.hInstance := system.MainInstance;
  WindowClass.hIcon := LoadIcon(0, idi_Application);
  WindowClass.hCursor := LoadCursor(0, idc_Arrow);
  WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH);
  WindowClass.lpszMenuName := nil;
  WindowClass.lpszClassName := AppName;
 
  Result := RegisterClass(WindowClass) <> 0;
end;
Обратите внимание на значок "@", присутствующий в отмеченной мной строке...

Еще примеры? Пожалуйста, открываем DRKB, смотрим пример использования функции EnumWindows. Я его немного упростил, вот в таком виде программа компилируется FPC:
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{$mode delphi}
uses windows;
 
function AddWinInfo(WinHandle: HWnd): Boolean; stdcall;
var
  WinCaption,WinClass: array[0..255] of Char;
begin
  Result:=True;
  GetClassName(WinHandle,WinClass,SizeOf(WinClass));
  GetWindowText(WinHandle,WinCaption,SizeOf(WinCaption));
  WriteLn(WinClass + ' - ' + WinCaption);
end;
 
procedure Test;
begin
  EnumWindows(@AddWinInfo, LParam(0));
end;
 
begin
  Test;
end.
Что видим при вызове EnumWindows? Опять тот же значок "@"? Попробуйте убрать его - программа перестает компилироваться. Ужас, правда?
Кто-нибудь задумывался, почему, собственно, надо добавлять этот вездесущий "@"? Посмотрите в любой книге по Турбо-Паскалю тему "Процедурные типы". Вы там хоть один такой символ при передаче функции как параметра в другую функцию видели? Что же произошло, что теперь компиляторы требуют везде брать адрес, интересно?
А ничего не произошло. Все дело - в том, что этот значок никому не нужен. Вы этим самым оказываете и себе и компилятору медвежью услугу - вместо того, чтобы описать Callback функцию правильно, вы берете адрес этой функции, который совместим с любым другим указателем.
Вспомните, что
Код Pascal
1
2
3
4
5
6
7
8
9
10
type
  pInt = ^Integer;
  pDouble = ^Double;
var
  p_i: PInt;
  p_d: PDouble;
begin
  // ...
  p_i := p_d; // <--- Ошибка - указатели несовместимы !!!
end.
Но в то же время:
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
type
  pInt = ^Integer;
  pDouble = ^Double;
var
  p_i: PInt;
  p_d: PDouble;
  p: Pointer;
begin
  // ...
  p := p_d;  // Вот она, та же самая медвежья услуга
  p_i := p;  // Теперь p_i = p_d, обошли ограничение
end.
Вот нечто подобное происходит в примере, описанном выше. Заставляем компилятор закрыть глаза на то, что функция вызывается не так, как должна вызываться. Возможно, это удобнее, но искать возникающие при таком подходе ошибки на порядок сложнее.

Что же надо сделать, чтобы исправить приведенные выше примеры? Очень немного.
1. Оконная функция. Смотрим в MSDN (если не помним наизусть) сигнатуру оконной функции:
Код C++
1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
/* __in */ HWND hwnd,
/* __in */ UINT uMsg,
/* __in */ WPARAM wParam,
/* __in */ LPARAM lParam
);
, убеждаемся, что сигнатура описанной в примере функции полностью совпадает с правильной, и просто убираем "взятие адреса":
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{ Register the Window Class }
function WinRegister: Boolean;
var
  WindowClass: WndClass;
begin
  WindowClass.Style := cs_hRedraw or cs_vRedraw;
  WindowClass.lpfnWndProc := WindowProc;
  // Ни @ ни приведение типа не нужны !!!
 
  WindowClass.cbClsExtra := 0;
  WindowClass.cbWndExtra := 0;
  WindowClass.hInstance := system.MainInstance;
  WindowClass.hIcon := LoadIcon(0, idi_Application);
  WindowClass.hCursor := LoadCursor(0, idc_Arrow);
  WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH);
  WindowClass.lpszMenuName := nil;
  WindowClass.lpszClassName := AppName;
 
  Result := RegisterClass(WindowClass) <> 0;
end;
Перекомпилируем программу (ошибок нет, странно, правда?), и запускаем ее. Программа работает точно так же, как и раньше. Значит, не надо приказывать компилятору делать глупости?

2. Функция перечисления окон. Аналогично: смотрим описание EnumWindows, чтобы узнать, какая функция ожидается первым параметром. А вот такая:
Код C++
1
2
3
4
BOOL CALLBACK EnumWindowsProc(
/* __in */ HWND hwnd,
/* __in */ LPARAM lParam
);
Теперь понимаете, почему требовалось "замазать глаза" компилятору? Не та сигнатура!!! Переделываем на правильную:
Код Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{$mode delphi}
uses windows;
 
// Мало того, что не хватало второго параметра,
// так еще типы Boolean и BOOL - это не одно и то же
function AddWinInfo(WinHandle: HWnd; param: LParam): BOOL; stdcall;
var
  WinCaption,WinClass: array[0..255] of Char;
begin
  Result:=True;
  GetClassName(WinHandle,WinClass,SizeOf(WinClass));
  GetWindowText(WinHandle,WinCaption,SizeOf(WinCaption));
  WriteLn(WinClass + ' - ' + WinCaption);
end;
 
procedure Test;
begin
  EnumWindows(AddWinInfo, LParam(0)); // Адрес не берем !!!
end;
 
 
begin
  Test;
end.
, и все прекрасно работает!

То же самое касается и излюбленных хаков: запуска функций через RunDll32. Очень много в интернете советов подобного рода:

rundll32.exe user32.dll, LockWorkStation
или
rundll32 user32.dll LockWorkStation
(блокировка компьютера)

Windows Vista:
rundll32 user32.dll, SwapMouseButton
(поменять местами кнопки мыши... Обратная смена невозможна !!!)
Нельзя этого делать! Почему? Да все по той же причине. Сначала давайте посмотрим, что ожидается RunDll32 в качестве параметра:
Интерфейс программ Windows Rundll и Rundll32

Вот что:
Код C++
1
2
3
4
5
void CALLBACK EntryPoint(HWND hwnd, HINSTANCE hinst,
  LPCSTR lpszCmdLine, int nCmdShow);
// или
void CALLBACK EntryPointW(HWND hwnd, HINSTANCE hinst,
  LPWSTR lpszCmdLine, int nCmdShow);
А какой прототип имеют функции LockWorkStation или SwapMouseButton?

И после этого люди еще удивляются, что "обратная смена невозможна"? Удивляться-то надо не этому, а тому, что оно вообще хоть как-то работает. Опять же, отсюда и эти самые "работает только на Win98, или только на Win95". Запуск функции с подходящими сигнатурами поддерживается на всех версиях Windows. А вот поддерживать то, что изначально не предполагалось - извините, никто не обязан.

P.S. Я говорю об этом каждый раз: Паскаль - очень мощный язык, но используйте его правильно, не пытайтесь "объегорить" ни компилятор ни ОС. Выйдет себе дороже.

Я надеюсь, хоть кто-то задумается над тем, что я тут написал, и перестанет бездумно добавлять операторы в программу для того, чтобы ее откомпилировать с наименьшими затратами усилий... Лучше потерять чуть больше времени, но разобраться в причинах, по которым компилятор отказывается выполнять свою работу, и исправить именно сами причины, а не закрывать глаза на следствия.

Желающие скачать этот топик отдельным PDF-файлом могут это сделать, в аттаче точная копия топика.
Вопрос: Программа, запускающая первую программу в качестве вновь созданного процесса

Задание:
Разработать две программы. Первая находит площадь кольца, внутренний радиус которого равен R1, а внешний радиус равен R2 (R1<R2). В качестве значения Pi использовать 3.14.Радиусы R1 и R2 задаются пользователем.
Вторая программа запускает первую в качестве вновь созданного процесса.

Листинг программы, являющуюся дополнительным процессом. Программа "Нахождение площади кольца":
Код C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <vcl.h>
#include <iostream.h>
#include <conio.h>
#include <math.h>
int main()
{
float r1,r2,S;
cout << " Enter the first r: " ; cin >> r1 ;
cout << " Enter the second r: " ;cin >> r2;
double pi = 3.14;
{
if (r1<r2)
          S=pi*r2*r2-pi*r1*r1;
          else
          cout << " error, because r1>r2 " ;
}
          cout << " S= " << S;
 getch();
}
Листинг программы, реализующая полученное задание (запуск процессов):
Код C++
1
2
3
4
5
6
7
8
9
10
11
12
#include <windows.h>
 
int main (){
STARTUPINFO si;
 
PROCESS_INFORMATION pi;
GetStartupInfo(&si);
system("Project1.exe");
 
  return 0;
 
}
При компиляции второй, загружается первая программа но ничего не выдает. Помогите разобраться. Обе программы в одной папке.
Ответ:
Код C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cout << "Press R to run programm or press any other key to exit" << endl; //Выдаем подсказку, что надо делать
char n = getch(); //Считываем один символ с клавиатуры
if(n == 'R' || n == 'r') //Если нажата клавиша R
{
  SHELLEXECUTEINFO SEI = {0}; //Объявляем структуру типа SHELLEXECUTEINFO (прочитать о всех параметрах этой структуры можно в Help-е
  SEI.cbSize = sizeof(SHELLEXECUTEINFO);
  SEI.fMask = SEE_MASK_NOCLOSEPROCESS;
  SEI.hwnd = NULL;
  SEI.lpVerb = "open"; 
  SEI.lpFile = "S_Ring.exe"; //Программа, которую будем запускать
  SEI.lpParameters = NULL; //Параметры запуска программы
  SEI.lpDirectory = NULL; //Начальная директория
  SEI.nShow = SW_SHOW; //Вид окна
  SEI.hInstApp = NULL;
  ShellExecuteEx(&SEI); //Запускаем программу на основании параметров, заданых в структуре SEI
  WaitForSingleObject(SEI.hProcess, INFINITE); //Ждем завершения порожденного процесса
  return 0; //Выход с кодом 0
}
cout << "Programm by closed"; //Сообщаем о том, что программа будет закрыта
getch(); //Ждем нажатия любой клавиши
return 1; //Выход с кодом 1
Вопрос: Считывание данных, отправляемых программой на COM-порт

Здравствуйте!

Суть проблемы вот в чём: программа А общается с внешним устройством через COM-порт. Из программы Б нужно получить данные, которые программа А отправляет на COM-порт. Понимаю, что для этого есть готовые приложения, но интересует именно возможность самостоятельной реализации этой задачи.
В идеале хотелось бы, чтобы это хозяйство было кроссплатформенным на Qt, но интересуют и варианты решения для конкретных платформ (Windows в приоритете), чтобы потом впихнуть это в Qt'овскую программу и применять в зависимости от ОС, под которой запускается программа.
Я пока вижу два пути - либо мониторить процесс программы А на исходящие данные, либо мониторить сам COM-порт. Но вот с чем-то конкретным туго, поэтому буду весьма благодарен, если развернёте носом в нужном напрвлении
Ответ:
Сообщение от alexey_rage
то этот порт будет всегда занят и невозможно будет к нему подключить еще одну программу!
Чтобы программа "В" могла "слушать" программу "А", для обмена между ними нужно открывать еще один порт!
Поправьте меня, если я не прав!
Проблематично, но возможно, судя по тому, что готовые программы для этого существуют. Но в том и суть моей проблемы, чтобы "научить" программу Б саму перехватывать трафик, идущий от программы А на COM-порт. Мне самому приходил в голову вариант создавать для программы А виртуальный порт, соединённый с реальным, и мониторить трафик между ними, но желательно ограничится работой с уже имеющимся портом.
Вопрос: Защита программы от повторного использования

Всем привет, пожалуйста не ругайтесь за дублирование темы, я видел несколько созданных тем про подключение формы vb.net к удаленной базе данных. Но я так и не понял решение проблемы. И что, если нельзя подключиться к удаленной бд из формы, как можно защитить свою программу?

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

Но вот беда.. локально красиво все работает, но на хостинге нет возможности из vb.net соединяться к базе. т.е. соединяться то можно но только статическому ip, т.е. нескольким юзерам уже не подключиться никак, да и в принципе невозможно это, т.к. на хостинге необходимо добавить ip для которого хотим получить доступ для удаленного подключения.. я естественно никаких ip не знаю...
Может есть какие то мысли на этот счет? я что-то никак не могу понять как другие защищают свои программы, неужели нет возможности сделать регистрацию и авторизацию в программе на vb.net в удаленную базу данных... это просто в уме не укладывается...

Или есть какие то другие принципы с работой с удаленной бд? Пожалуйста, если не трудно расскажите... я столько времени убил на этот механизм... неужели придется все переделывать(((
Ответ: GSXL, SSL это как вариант усложнить крякеру работу минут на 5, но не вариант 100% защиты.
Объясню. Даже SSL не защитит, если у сервера нет добротного сертификата (обычно 98% пользователей его не делают, т.к. душит жаба его покупать). Если сертификата нет, то в противном случае я так же без особых усилий смогу вклиниться сниффером между сервером и программой, и снять траффик, несмотря даже на то, что " соединение защищенное".

Сообщение от vadimnev
Eще думал по мак адресу привязывать как-то программу к компу, но думаю по отношению к юзерам это не красиво будет, скорее всего у пользователя несколько устройств на котором пользователь будет использовать программу..
Если и надумаете, то привязка к маку не самый лучший вариант. Мак без особых усилий можно изменить, что даст еще одну дыру в защите Вашей программы.
Вязаться нужно к материнской плате + желательно захватить процессор.
В идеале привязываться к: Папка - Материнка - Проц. - МАК.
Но даже такая привязка оставит дырки для крякера.
Опять же не буду вдаваться в подробности, чтобы не облегчать им жизнь. Кто по настоящему хочет защитить свою программу думаю дойдет до всего сам, методом проб и ошибок.

Так же не стоит забывать про кейгены. Почаще используйте Using. Не храните строковые переменные в открытом виде. Всегда по возможности очищайте глобальные переменные. Делайте проверки важных переменных. Почаще разбивайте код на процедуры. Это частично затруднит взлом.
Вопрос: Завершение текущей программы из запускаемого процесса

Доброго времени суток. У меня имеется такая ситуация (пока все работает под винду):

есть некая программа, запускаемая с .exe-шника. Пусть будет называться main. В процессе выполнения этой программы с помощью QProcess запускается вспомогательная программа, которая должна прекратить выполнение main в корректной форме (с отработкой деструктора. Что-то наподобие QProcess::terminate, если правильно понимаю).

Пока из идей воспользоваться методом QThread::currentThread для получения указателя на текущий поток, и передавать его во вспомогательный процесс.

Вопросы в следующем - возможно ли с помощью указателя на QThread корректно завершить работу программы (и вообще правильно ли так делать с идеологической точки зрения).
И можно ли этот самый указатель передать во вспомогательный процесс? Например, создать экземпляр QProcess, запустить с его помощью программу и передать ему с помощью QProcess::writeData указатель (только как тогда эти данные считать "изнутри" вспомогательной программы)?


ps необязательный вопрос не совсем по теме - есть ли возможность Qt'шными средствами узнать, запущена ли программа (например, если известно ее имя)? Пока видел такое только средствами winapi. Видимо я пока плохо дружу с поиском - не смог найти такого обсуждения на форуме.
Ответ:
Сообщение от atForce
так я и хотел какую-нибудь обертку.
Обвертку над чем?

Добавлено через 1 минуту
Сообщение от atForce
Если мы с помощью QProcess запускаем программу, то после прекращения работы этой программы с помощью того же статуса можем узнать, завершилось ли выполнение.
Да по тому что в QProcess хранится хендл этой программы.

Добавлено через 1 минуту
Сообщение от atForce
Из известных мне вариантов пока что - это искать программу среди запущенных процессов, и если не нашли ее - то значит она умерла. Только это странное решение для простенького апдейтера.
Так даже лучше, вдруг пользователю взбредет запустить несколько раз программу, хотя конечно можно просто запретить запуска нескольких экземпляров программы.
Вопрос только в том как искать, т.е по имени процесса или его пути- с этой стороны не очень хорошее решение.

Добавлено через 2 минуты
Сообщение от atForce
Из известных мне вариантов пока что
Я же вам подробно описал как можно сделать. Единственное это проканает только под винду, под другие платформы - нужно смотреть ...
Вопрос: Ошибка с путями при запуске другой программы

Есть программа_1 написанная на c#. Обычным двойным щелчком программа_1 запускается без ошибок.
Я хочу запустить программу_1 из другой программы (программы_2).

Код программы_2 - выполняется в фоновом потоке first_laught_thread
C#
1
2
3
Process.Start("program\\TelerikWinFormsApp1.exe"); //Запускаем программу_1
//first_laught_thread.Abort(); //Завершение потока не помогает. Да и поток должен сам завершиться при Application.Exit() - он же фоновый
Application.Exit();
В результате запускается программа_1. Пока все хорошо.
Но далее запущенная программа 1 выдает текст ошибки-исключения. Исключение возникает если не найден путь к файлу.
Но все файлы есть и все пути верные. Проверял многократно.
Код программы_1
C#
1
2
3
4
5
6
7
try
{
xmlDoc.Load("handbook_files\\SB\\SB.xml");
......//Тут часть кода
}
catch (System.IO.DirectoryNotFoundException) //Вот это срабатывает при вызове из программы 1.
{ this.Invoke(new Action(() => { show_error_message("Ошибка. Не найден путь к справочнику SB.xml."); }));}
Почему при обычном вызове не возникает никаких ошибок, а при вызове из другой программы возникает исключение?
Ответ:
Сообщение от OwenGlendower
Нам же нужна директория приложения
Зачем? Она меняется в зависимости от вызова программы. Что я и увидел, на своем примере.
Директория приложения это ведь Рабочий каталог? При создании нового процесса, он наследует рабочий каталог родительского процесса.
Когда я вызывал программу двойным кликом - мой рабочий каталог был каталог этой программы. До вызова программы не было процесса. Все работало.
Когда я вызывал программу из другой программы - эта другая программа (процесс) "передала свой рабочий каталог" вызываемой программе.
И если бы обе программы находились в одном каталоге - все было бы хорошо, но программы находились в разных. Что и вызвало ошибку относительных путей.
Я так это понимаю.

А вот путь к любым объектам относительно корневой директории не меняется в зависимости от вызова программы.

Сообщение от OwenGlendower
нужно всегда явно формировать полный путь чтобы не было сюрпризов
Вы хотели сказать - нужно всегда формировать относительный путь, относительно корневого каталога системы?
Вопрос: Программа прерывания

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

Если такое возможно пожалуйста помогите! Пример программы можно взять любое, например выполнение двух уравнений и т.д.
Ответ:
Сообщение от Abzhanovk
т.е. выполняется одна программа и мы сделали прерывание и дальше выполняется вторая программа.
О чём ты? Всеми/описанными тобой действиями занимается любое прерывание. Или ты никогда не пользовался ими? Почитай про определение:
Р.Марек. глава 2.5
Событие прерывания состоит в том, что процессор прекращает выполнять инструкции программы в нормальной последовательности, а в место этого начинает выполнять другую программу, предназначенную для обработки этого события. После окончания обработки прерывания, процессор продолжит выполнение прерванной программы.
Вопрос: Программа должна открываться только на одном компьютере

Здравствуйте! Пишу программу, которая должна открываться только на одном компьютере. Было две мысли:
1. Указать в программе команду работы с папкой имени пользователя (оно известно) через блок try и в случае ошибки сообщить о том, что программа не должна запускаться на этом компьютере.
2. Сделать две программы. Первая создает в недрах компьютера файл. Вторая программа удаляет первую и проверяет наличие папки. Если папка есть, все нормально. Иначе выдает ошибку.
Пока конкретные коды сюда вставить не могу. По возможности напишу позже.

А какие способы можете предложить вы?

P. S.: просьба продублировать ответ в Сообщении: бывают ошибки с чтением ответов внутри темы.
Ответ: kolay_ne, ну как вам ответить, прочтите вот это:
Кликните здесь для просмотра всего текста
19 апреля 2012 в 17:01
Привязка программы к железу + онлайн верификация
Чулан
Данную статью я представляю вниманию новичков для обеспечения защиты своего софта. Защита реализуется путем привязки к железу компа + онлайн проверка.

Наша защита будет состоять из нескольких частей:

Генератор серийного номера по системным характеристикам.
Генератор ключа авторизации по серийнику
Защищаемая программа


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

Мы сделаем это через WIN Api функции:

GetUserName — Имя текущего пользователя.
GetComputerName — Имя компутора.
GetVolumeInformation — Получение информации о носителе.
GlobalMemoryStatus — Информация о используемой системой памяти.

Что бы облегчить вам жизнь, я приведу готовые функции:

function UserName: string;
var
u: pchar;
i: dword;
begin
i := 1024;
u := StrAlloc(Succ(i));
if GetUserName(u, i) then Result := StrPas(u) else Result := '?';
end;

function ComputerName: string;
var
buffer: array[0..255] of char;
size: dword;
begin
size := 256;
if GetComputerName(buffer, size) then
Result := buffer
else
Result := ''
end;

function GetHard: String;
var
VolumeName, FileSystemName: array [0..MAX_PATH-1] of Char;
VolumeSerialNo: DWord;
MaxComponentLength,FileSystemFlags: Cardinal;

begin

GetVolumeInformation('C:\',VolumeName,MAX_PATH,@VolumeSerialNo,
MaxComponentLength,FileSystemFlags, FileSystemName,MAX_PATH);
Result := IntToHex(VolumeSerialNo,8);

end;

function GetMem: String;
var
MyMem: TMemoryStatus;

begin

MyMem.dwLength:=SizeOf(MyMem);
GlobalMemoryStatus(MyMem);
with MyMem do begin
Result:= IntToStr(dwTotalPhys);
end;
end;

Итак, мы получили всю интересующую нас информацию. Теперь мы склеим эти данные в hex строку, что бы конечный пользователь не знал, какие параметры мы используем.
Берем функцию преобразования в 16ричный вид.

function StringToHex(str1,Separator:String):String;
var
buf:String;
i:Integer;
begin
buf:='';
for i:=1 to Length(str1) do begin
buf:=buf+IntToHex(Byte(str1[i]),2)+Separator;
end;
Result:=buf;
end;

Склеим все параметры —

function getSerial:string;
begin
Result := StringToHex((UserName + ComputerName + GetHard + GetMem));
end;

Вывод полученной строки в TEdit

procedure TForm2.Button1Click(Sender: TObject);
begin
Edit1.Text := getSerial;
end;

Мои поздравления, готов модуль получения serial кода.

Генератор регистрационного ключа

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

function getKey(Serial: string):string;
begin
Result := MD5DigestToStr(MD5String(Serial+'123'));

end;

Теперь кидаем на форму два Tedit и кнопку.
на онклик ставим
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit2.Text := getKey(Edit1.Text); / в первый Edit вставляем серийник, во втором будет зашифрованый вариант(очевидно)
end;

Кейген готов.

Пишем основную программу.

1) Кидаем все функции сбора инфы о компе и генерации серийника из первого модуля.
2) Кидаем функцию шифрации серийника из генератора рег ключа.
Приступим к регистрации программы и онлайн привязки. Кидаем Tedit(для ввода рег ключа) + 2 кнопки
(1 — проверка на валидность рег ключа 2 — коннект к серверу и проверка на наличия записи там)
procedure TForm2.Button1Click(Sender: TObject);
begin
if Edit1.Text = getKey(GetSerial) then ShowMessage('RegOk') else ShowMessage('NoFuckinWay');
end;

При валидности рег ключа — RegOk, если нет, то посылает.
В качестве онлайн проверки будем использовать простейший вариант — txt файл.
Алгоритм — на хосте лежит файл серийник.txt, а в нем же рег ключ. Программа сравнивает содержимое текстовика и ключ, сгенереный программой.
procedure TForm2.Button2Click(Sender: TObject);
begin
if getKey(GetSerial)= IdHTTP1.Get('http://zzzzzz.com/reg/'+GetSerial+'.txt') then ShowMessage('RegOk') else ShowMessage('NoFuckinWay');
end;

Вот и все Осталось вам придумать, куда прописывать то, что программа зарегистрирована.
Я лишь описал наглядный пример, как строятся такие методы защиты… вы можете использовать свои способы привязки, совершенствуя код + верификация через txt самый ненадежный способ. используйте php+mysql и т.д.

Будут вопросы — пишите, но я тут разжевал все до процедур онклика.

Всем удачи в начинаниях.

p.s. Напоминаю, что статья для новичков и является пищей для размышления.
Вопрос: Делаем регистрацию для своей программы

Доброе время суток!!
Наверняка, если ты напишешь супер программу ты захочешь на ней бабло заработать. Вот только может хрень такая получиться, ты продашь челу программу, а он начнет ее распространять(сайты всякие делать, на болванки скидывать(и продавать)). Так чтоб этого избежать, т.е. защитить свою программу от пиратов и злых дядек, надо сделать регистрацию программы. Т.е. программой сможет пользоваться только тот кто заплатил за нее деньги, и зарегистрировал. Надо сделать так, что б на каждом компе был свой серийный номер и ключ, т.к. если у всех будут одинаковые ключи и с/н то ты продашь ее одному челу, а он начнет всем говорить коды.

Ну, тянуть не буду, скажу идею:

На форме 2 текст. поля, первое текст. поле это с/н, при первом запуске программы в заныканый файл(или реестр) записывается случайный с/н(RND). Второе текстовое поле, это ключ. Он высчитывается в зависимости от с/н, по определенной формуле(например ключ = с/н * 3/555+1). Так вот, чел присылает тебе свой с/н(а он на каждом компе свой) + бабло, а ты ему ключ. Идею понял?? Ну тогда начнем`с:
Как я уже и говорил, на форму кинь кнопку(Caption = "Регистрация"), 2 текст. поля(в ряд), 2 метки(у первой Caption = "Серийный номер", она находится напротив первого текст. поля, у второй: Caption = "Ключ", напротив второго текст. поля). Вот что у тебя должно получиться:
собственно интересует следующие вопросы:
1) у кого какие идеи (если можно поделитесь кодом)
2) вот нашел такой код правда на vb 6 помогите под vb2010 переделать не соображу плиз
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Dim SNPath As String ' В этой переменной будет храниться путь к заныканому файлу с с/н
Dim GetReestr As String ' В эту переменную будет читаться данные из реестра
Dim Serial As String ' А эта переменная, будет загружать в текст. поле 1 с/н из файла
 
Function Start() ' Эта функция будет выполняться если программа зарегистрирована
End ' Я написал End, а ты можешь сюда что - нбудь по умнее вставить
End Function
 
Private Sub Form_Load() Randomize ' Этот оператор нужен для того, чтоб всегда генерировались случайные числа(RND(ты что забыл??))
On Error Resume Next ' Если происходит ошибка, то игнорируем ее
GetReestr = GetSetting("proga", "serial", "serial") ' Читаем из реестра значение параметра serial(если serial = 0, значит программа не зарегистрированая, а если 1 то зарег-на)
 
SNPath = Environ("windir") & "key.sn" ' В переменную SNPath - записываем путь к заныканому в папке windows файлу key.sn
f = FreeFile
 
If Val(GetReestr) <> 1 Then ' Если GetReestr не равен 1, то продолжаем регистрацию, если же = 1, то программа уже зарегистрирована
If Dir(SNPath) = "" Then ' Если файла с с/н не существует, то:
Text1.Text = Fix(Rnd * 8000000000#) ' Генерируем случайное число(в любом пределе, можно вместо 8 с деветью нулями написать любое число), и округляем его(Fix())
Serial = Text1.Text ' Это число записываем в перем. Serial
Open SNPath For Output As f ' Открываем заныканный файл
Print #f, Text1.Text ' Записываем в него с/н из текст. поля 1
Close #f ' Закрываем
SaveSetting "proga", "serial", "serial", 0 ' Записываем в реестр параметр Serial со значением 0(ноль означает что программа не зарегистрирована)
Else ' Если же файл существует, то читаем из него с/н
Open (SNPath) For Input As f
Serial = Val(Input(LOF(f), f))
Text1.Text = Serial ' В текс. поле помещаем с/н
Close #f
End If
Else ' Если же в реестре параметр Serial = 1(программа зарег-на), то запускаем функцию Start
Start
End If
End Sub
 
Private Sub Command1_Click() ' При нажатии на кнопку:
Text1.Text = Serial ' Копируем в текст. поле значение переменной serial(на случай, если чел изменил текст в текст. поле 1)
If Text2.Text = Fix(Val(Serial) * 3 + 333 / 2) Then ' Вот самое интересное :-), если текст. поле 2(ключ) равен с/н умноженному на 3 + 333 / 2
SaveSetting "proga", "serial", "serial", 1 
MsgBox "Программа зарегистрирована" 
Start ' Запускаем функцию Start
Else
MsgBox "Введен не правильный ключ!!! Для регистрации отправь автору программы с/н и $, а он вышлет тебе ключ" End If
End Sub
если нужен вам пример keygen выложу
вот код дя Keygen: соотведственно 2 texbox и кнопку
Visual Basic
1
2
3
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        TextBox2.Text = Fix(Val(TextBox1.Text) * 3 + 333 / 2) ' По С/Н узнаем ключ 
    End Sub
Ответ: Или декомпилируешь программу, выпиливаешь проверку и компилируешь обратно. Это же .Net. Поэтому простые методы, как у Faraon, бесполезны.

Или, техподдержка закрылась вместе с компанией-разработчиком. Тогда и долбить некого, только своей головой об стену. Так как законно приобретённое более не работает, техпроцессы стоят, а спросить не с кого.
Вопрос: Не понимаю как работает программа

Всем привет! Не так давно занялся изучением асма... Занимаюсь в основном по Калашникову. Сейчас я занимаюсь с разделом работа с файлами, и мне в нем очень многое не ясно. Очень на хоть какие-то разъяснения... Итак вот код
ASM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
CSEG segment
org 100h
 
GO:
mov ax,3d00h
mov dx, offset File_name
int 21h
jc Error_File
 
mov Handle, ax
mov bx,ax
mov ah,3Fh
mov cx,0fde8h
mov dx,offset Buffer
int 21h
 
mov ah,3eh
mov bx,Handle
int 21h
 
mov dx,offset Mess_ok
 
Out_prog:
mov ah,9
int 21h
int 20h
 
Error_File:
mov dx, offset Mess_error
jmp Out_prog
 
Handle dw 0
Mess_ok db 'File v pamyati!$'
Mess_error db 'Ne udalos(!$'
 
File_name db 'c:\msdos.sys',0,'!$'
 
Buffer equ $
 
CSEG ends
end GO
Программа прекрасно выполняется но я не понимаю :
1) почему метка Buffer должна располагаться в конце программы? ( дело в том, что следующая программа в этом разделе открывает сама себя, и в ней в dx заносится не смещение конца программы, но смещение ее начала) Я попробовал занести в dx начальное смещение (100h), и программа все равно выполняется! Почему?
2)если мы читаем файл, то где можно посмотреть его содержимое? (программа, о которой я уже упоминал выше, должна, как я понял выводить содержимое файла на экран, но она лишь пропускает строчку после сообщения об успешном/неуспешном открытии файла).
3)зачем вообще нам нужна переменная Handle, если номер файла мы заносим в bx из регистра ax, и после этого bx не меняется, а Handle не используется?
Вторая программа:
ASM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
CSEG segment
org 100h
 
Go:
 
    ;Начало
mov dx,offset File_name
call Open
jc Eror
 
   ;Открыли файл
mov bx,ax
mov ah,3fh
mov cx,offset Finish-100h 
mov dx,offset Go
int 21h
 
;Прочитали!
call Close
 
;Сообщение
 
mov ah,9
mov dx,offset Mess1
int 21h 
 
ret
 
;Не удалось
Eror:
mov ah,2
mov dl,7
int 21h
 
ret 
 
;Процедуры
 
;Процедура открытия файла
Open proc
cmp Handle,0FFFFh
jne Quit_open
mov ax,3d00h
int 21h
mov Handle,ax
ret
 
Quit_open:
stc
ret
 
Handle dw 0FFFFh
Open endp
 
;Процедура закрытия файла
Close proc
mov ah,3Eh
mov bx,Handle
int 21h
ret
Close endp
 
;Данные
File_name db 'Progg09.com',0 
Mess1 db 'OK!',0Ah,0Dh,'$'
 
Finish equ $
  
CSEG ends
end Go
Для наглядности я привел так же код 2й программы. Она, как я уже говорил, почему - то не выдает содержимое читаемого файла на экран... Вопросы по ней:
1а)зачем сравнивать переменную Handle с 0FFFFh, если это значение было ей присвоено при создании? Получается, что переход на метку Quit_open никак не произойдет, а значит она вовсе бесполезна. По идее это сравнение каким-то образом должно выявлять, открыт ли файл и если нет - открывать его, но ведь мы открываем его всего лишь раз! Зачем проверять: открыт он или нет?
2а)маленький вопрос про ret: если после него в процедуре идут какие-то инструкции(до Example_proc endp) будут ли они выполняться? Как я понял - нет.
3а)ну и самый незначительный, но неприятный момент: почему при неисправной работе программы (если указать несуществующее имя файла) не происходит звукового сигнала, который должен сообщать об ошибке?

И еще очень хотелось бы знать на будущее: стоит ли разделять такие большие вопросы на несколько, или я выбрал правильный формат?
Ответ: NEWd,
1)Вы читаете из файла данные и там может быть что угодно. Сам процессор считает, что по адресу CS:IP находится код и будет безразлично его выполнять. Кода Вы ставите начальное значение 0х100, то просто перезаписываете исполняемый код на аналоггичный, но всеравно это будет уже другой код. Чтобы в этом убедится нужно в начале программы записать какое либо значение в любую переменную, которое отличается от сгенерированного компилятором, после чтения из файла данные востановятся.
2)Программа ничего не должна, всё что написано кодером она выполнила(открыла, прочитала в память, закрыла и отрапортовала). Хотите выводить на экран пишите нужный код. Это ассемблер.
3)Handle используется при закрытии. Не факт, что после выполнения прерывания 0х21 ВХ останется прежним.
1а)Нормально написанная программа должна проверять все, а не просто падать без звука и напрягать мозги кодеру, что случилось.
2а)нет не будут.
3а)У Вас наверное ноут, там спикера нет.