Обмен сообщениями между программами
Интересует такой вот формат:
Программа1 -> SendMessage(<param>) -> Программа2
switch (<param>)
{ case <something>: ....}
Как лучше всего это сделать? То есть я бы хотел посылать какие-нибудь переменные из Программы1 в Программу2, в свою очередь Программа2 обработала операции, связанные с посланной переменной и отправила Программе1.
Получается логикак как у клиент-серверного приложения... :)
И что с того?
у меня тут есть две интересные мысли:
1. использовать BEGIN_MESSAGE_MAP. Вот я набросал код:
{
char Text1[100];
int Data1;
int Data2;
};
TMyData *MyData;
<...>
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_COPYDATA, TMessage, OnMyMessage)
END_MESSAGE_MAP(TForm)
<...>
void __fastcall TForm1::OnMyMessage(TMessage& Message)
{
COPYDATASTRUCT *cpDS;
cpDS = (COPYDATASTRUCT *)(void *)Message.LParam;
MyData = (TMyData *)(cpDS->lpData);
Edit1->Text = MyData->Text1;
Edit2->Text = IntToStr((int)MyData->Data1);
Edit3->Text = IntToStr((int)MyData->Data2);
};
И сразу проблема - у меня же не инициализируется в DLL класс формы Form1, а предложенный MESSAGE инициализируется в разделе public формы в заголовочном файле... что делать? Создать новую форму? или есть другой путь решения?
2. У каждого WinControl (и у TCustomForm) есть метод DefaultHandler(void *Message). Его можно подменить в производном классе (например, в форме Form1) приблизительно так:
{
TMessage *msg=(TMessage*)Message;
switch(msg->Msg)
{
case WM_USER: // твое сообщение
if (msg->WParam == ....)
{
// какая-то обработка
msg->Result = tue;
return;
}
}
TForm::::DefaultHandler(Message); // вызов базового метода
}
И опять проблема... как это встроить в DLL? ведь я не могу просто так взять и объявить функцию void __fastcall TForm1 :: DefaultHandler(void* Message)... у меня в DLL нет формы... как быть?
Прошу помочь разобраться во всем этом...
А почему?
2. вывод в файл
3. прочитал статью (http://www.codenet.ru/progr/bcb/pipes.php)
3.1 много кода писать приходится
3.2 используется (и практически везде) в работе с консолью
3.3. и т.д. в течение статью
4. некоторые другие нюансы
5. короче, pipe - это капец, и вряд ли мне лучше использовать его...
Я подумал - и решил, что в моем случае лучше - SendMessage() - проще, удобней и т.п.
2. Это вообще к чему? В какой файл?
3.1 Код примерно следующий:
CreatePipe
WriteFile
ReadFile
это сложно?
3.2 При чем тут консоль?
4. "некоторые др. нюансы" - это "надо разбираться, а влом" ?
5. pipe - это правильное решение, а SendMessage() базируется на оконных сообщениях, т.е. будут тормоза.
Впрочем, дело твое. Рациональное решение тебе подсказали, а воспользоваться им или делать через... дело твое.
как это можно сделать - можешь подкинуть простой примерчик?
1) сервер - http://msdn2.microsoft.com/en-us/library/aa365588.aspx
2) клиент - http://msdn2.microsoft.com/en-us/library/aa365592.aspx
Не пугайся объема кода, там просто все разжевано и куча проверок на всевозможные ошибки.
Сам же процесс сводится на стороне сервера
1) создание именованного pipe - CreateNamedPipe
2) ожидание подключения - ConnectNamedPipe
далее идет заточка на многопоточность, но можно без этого
3) читаем из pipe - ReadFile
4) пишем в pipe - WriteFile
на стороне клиента:
1) попытки подключиться к pipe - CreateFile, WaitNamedPipe
2) переключение pipe в "message-read mode" - SetNamedPipeHandleState
3) пишем в pipe - WriteFile
4) читаем из pipe пока возможно - ReadFile
я для себя это все обернул примерно в след. классы:
{
public:
void close();
bool send(void* buff, size_t size);
bool recv(void* buff, size_t& size);
protected:
PipeBase();
~PipeBase();
HANDLE hPipe;
};
class ServerPipe :public PipeBase
{
public:
bool create(const char* name);
bool connect();
};
class ClientPipe :public PipeBase
{
public:
bool open(const char* name);
};
// PipeBase
//
PipeBase::PipeBase() :hPipe(INVALID_HANDLE_VALUE)
{}
PipeBase::~PipeBase()
{
close();
}
void PipeBase::close()
{
if(INVALID_HANDLE_VALUE != hPipe) {
CloseHandle(hPipe);
hPipe = INVALID_HANDLE_VALUE;
}
}
bool PipeBase::send(void* buff, size_t size)
{
DWORD bytes;
return (WriteFile(hPipe, buff, (DWORD)size, &bytes, NULL) == TRUE);
}
bool PipeBase::recv(void* buff, size_t& size)
{
return (ReadFile(hPipe, buff, (DWORD)size, (PDWORD)&size, NULL) == TRUE);
}
//============================================================================
// ServerPipe
//
bool ServerPipe::create(const char* name)
{
std::string pipeName("\\\\.\\pipe\\");
pipeName += name;
hPipe = CreateNamedPipe(pipeName.c_str(), PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE,
NMPWAIT_USE_DEFAULT_WAIT, NULL);
return (INVALID_HANDLE_VALUE != hPipe);
}
bool ServerPipe::connect()
{
BOOL res = ConnectNamedPipe(hPipe, NULL);
return (res == TRUE ? true : GetLastError() == ERROR_PIPE_CONNECTED);
}
//============================================================================
// ClientPipe
//
bool ClientPipe::open(const char* name)
{
std::string pipeName("\\\\.\\pipe\\");
pipeName += name;
while(true) { // !!!
hPipe = CreateFile(pipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE != hPipe) {
break;
}
DWORD err = GetLastError();
if (GetLastError() != ERROR_PIPE_BUSY) {
return false;
}
if (!WaitNamedPipe(name, 200)) { // All pipe instances are busy, so wait for 200 ms.
return false;
}
}
DWORD dwMode = PIPE_READMODE_MESSAGE;
BOOL res = SetNamedPipeHandleState(hPipe, &dwMode, NULL, NULL);
return (res == TRUE);
}
Обычная тема для организации обмена сообщениями для "неоконного" компонента и т.п. - создание вспомогательного (невидимого) окна. В VCL-е для этого есть вспомогательная ф-и AllocateWnd/DeallocateWnd.
GIZMO, ты знаешь, что я тут делал и для чего мне нужна была DLL - посоветуй тоже - следует ли мне в моем случае использовать pipe или нет?
GIZMO, ты знаешь, что я тут делал и для чего мне нужна была DLL - посоветуй тоже - следует ли мне в моем случае использовать pipe или нет?
хз, пусть Green аргументирует ... он наверняка ветку, про доступ к VCL-объектам чужой программы читал
(кстати выше неправильно обозвал ф-ию - AllocateHWnd).
Я бы использовал сообщения + разделяемый мап-файл. SendMessage - обеспечит в этом случае и запрос и синхронизацию:
Из ЕХЕ через SendMessage шлем пользовательское сообщение сервисному окну в dll (например запрос какого-либо св-ва). Оконная процедура сервисного окна получив соотв. сообщение, пишет в мап-файл значение св-ва и возвращает управление в EXE. Как только отработала оконная процедура сервисного окна
идем дальше - читаем результат из мап-файла.
void GetEditText()
{
// просим, hWndInDll - создано в dll и записано в разделяемую память(мап-файл)
SendMessage(hWndInDll, WM_GETEDITTEXT_, 0, 0);
// читаем из мап-файла
}
можно просить вообще все (св-ва + значения) сразу это как тебе удобно.
создать на форме ПСР два Edit'а (или три :) ), в них через DLL добавлять значения, полученные из СтутусБара и Прогресса... Если значение изменилось, DLL делает SendMessage() мое программе, что, мол, обнови данные... Моя программа находит форму и получает значения из всех необходимых Edit'ов, в которые я занес данные... как такая реализаця - туповатая? :)
создать на форме ПСР два Edit'а (или три :) ), в них через DLL добавлять значения, полученные из СтутусБара и Прогресса... Если значение изменилось, DLL делает SendMessage() мое программе, что, мол, обнови данные... Моя программа находит форму и получает значения из всех необходимых Edit'ов, в которые я занес данные... как такая реализаця - туповатая? :)
Есть несколько нормальных способов это сделать:
1. Использование пайпов - как было сказано выше решение практически наиболее оптимальное.
2. Использование сокетов - тоже ничем не худшее и маштабируемое решение.
3. Использование почтовых слотов - знаю о таком но сам реально не использовал, потому ничего конкретного сказать не могу.
4. Использование COM - громоздко и не очень надежно - но вполне нормально.
5. Использование мап-файлов (разделяемой памяти и все т.п.) - годится как подручное решение в некоторых случаях может быть оптимальным.
Все остальные реализации может и могут быть использованы - но оно надо? кстати забыл еще один способ - но думаю 5 приведенных достаточно.
2. Использование сокетов - тоже ничем не худшее и маштабируемое решение.
На самом деле пайпы предпочтительней, ибо реально могут быть реализованны снизу как угодно, в том числе и парой приведенных тут способов. Но при этом довольно универсальны. Сокеты по сути предоставляют почти тот же интерфейс что и пайпы только чуть более громоздкий. Но всеж их предназначение другое. Как то некрасиво это. )
Их (в смысле сокетов) предназначение как раз и заключается в передаче данных и установке связи между приложениями :)
Но универсальной пердачи по сети. А пайпы - для простой связи приложений. А так интерфейс аналогичный почти. Темболее, что сокеты сами могут служить основой для них )
Это, наверное, самая важная часть твоей программы?
А это всё от обилия возможных решений и от нежелания эти решения принимать самостоятельно. А может, автор просто никуда не торопится.
Sanila_san,
Тороплюсь ;) я делаю другие части программы...
а насчет времени в неделю и получаса... это же форум, обсудение - хочется знать, что можно сделать теми или иными средствами, что лучше в разных ситуациях, да и вообще, какими методами можно реализовывать вещи... ;)
2. HANDLE hPipe; - Declaration missing ;
3. PipeBase::PipeBase():hPipe(INVALID_HANDLE_VALUE){} - 'hPipe' is not an unambiguosbase class of 'PipeBase'
4. if(INVALID_HANDLE_VALUE != hPipe) - Undefined symbol 'INVALID_HANDLE_VALUE'
5. if(INVALID_HANDLE_VALUE != hPipe) - Undefined symbol 'hPipe'
6. И т.д.
может я не так все объявил?
как должны выглядеть Unit1.cpp и Unit1.h? Я просто думал создать два файла - pipe.cpp и pipe.h, которые буду подключать к программе... как лучше всего это сделать?
А на счет разделяемой памяти, так стандартно pipe и базируется на ней.
проблема... как это встроить в DLL? ведь я не могу просто так взять и объявить функцию void __fastcall TForm1 :: DefaultHandler(void* Message)... у меня в DLL нет формы... как быть?
А зачем она нужна, форма?
Например, пределано наспех из одного класса и засунуто в dll ...
class TEditHook
{
private:
TEdit* FEdit;
HWND FWnd;
TKeyPressEvent OnKeyPress;
void __fastcall OnKeyPress2(TObject *Sender, char &Key);
public:
void __fastcall Attach(TEdit* AEdit);
void __fastcall Detach();
public:
__fastcall TEditHook(String WndName);
__fastcall ~TEditHook();
};
-------------------------------------------------------------
__fastcall TEditHook::TEditHook(String WndName)
{
FEdit = NULL;
OnKeyPress = NULL;
FWnd = FindWindow(NULL, WndName.c_str());
}
//---------------------------------------------------------------------------
__fastcall TEditHook::~TEditHook()
{
Detach();
}
//---------------------------------------------------------------------------
void __fastcall TEditHook::Attach(TEdit* AEdit)
{
//TODO: Add your source code here
if(FEdit) Detach();
if(AEdit)
{
FEdit = AEdit;
OnKeyPress = FEdit->OnKeyPress;
FEdit->OnKeyPress = OnKeyPress2;
}
}
//---------------------------------------------------------------------------
void __fastcall TEditHook::Detach()
{
//TODO: Add your source code here
if(FEdit)
{
FEdit->OnKeyPress = OnKeyPress;
FEdit = NULL;
}
}
//---------------------------------------------------------------------------
void __fastcall TEditHook::OnKeyPress2(TObject *Sender, char &Key)
{
if(OnKeyPress)
OnKeyPress(Sender, Key);
if(FWnd)
{
String data("Кто-то нажал - "); data += Key;
COPYDATASTRUCT CopyData = { 0, strlen(data.c_str()) + sizeof(char), data.c_str()};
::SendMessage( FWnd, WM_COPYDATA, NULL, reinterpret_cast<LPARAM>(&CopyData));
}
}
//----------------------------------------------------------
...
#define TESTDLL_EXPORTS
#include "testdll.h"
#include "hook_events.h"
extern HHOOK hHook;
HINSTANCE hDLL;
char Work[256];
String s;
Windows::HINST hModule=NULL;
TForm *fmMain=NULL;
TEditHook ehk("VCLViewer"); //<===========
...
TForm **pForm=(TForm**)::GetProcAddress(::GetModuleHandle(NULL), "_TargetForm");
if(pForm == NULL)
{
ShowMessage("ERROR: export not found");
}
else
{
fmMain=*pForm;
if(fmMain == NULL)
{
ShowMessage("ERROR: form is not accessible");
}
TEdit* ed = (TEdit*)fmMain->FindComponent("Edit1");
if(!ed)ShowMessage("Edit1 not found");
else
{ehk.Attach(ed); }
}
...
// in injector
// hpp
class TfmMain : public TForm
{
...
protected:
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_COPYDATA, TMessage, OnWMCopyData)
END_MESSAGE_MAP(TForm)
};
// cpp
void __fastcall TfmMain::OnWMCopyData(TMessage& Message)
{
DWORD CountData;
PCOPYDATASTRUCT PassedCopyDataStruct;
char* data;
PassedCopyDataStruct = reinterpret_cast<COPYDATASTRUCT*>(Message.LParam);
CountData = PassedCopyDataStruct->cbData;
if (CountData > 0) {
data = new char[CountData];
if (PassedCopyDataStruct->lpData != NULL)
memmove(data, PassedCopyDataStruct->lpData, CountData);
}
Win32Check( ReplyMessage(true) );
if(data)
Memo1->Lines->Add(data);
delete [] data;
}
Запускаю тестовую прогу, запукаю инжектор, ставлю хук, набираю текст в Edit1 тестовой программы тоже добавляется в Memo1 инжектора.
я же тебе на это уже раза три намекал:)