-
Muxa/Ibl4 (14.07.09 22:12) [0]Уважаемые коллеги, прошу помочь в реализации.
Что_имеем: 2 программы(или более) на одном компе, каждая имеет некую DLL(по сути это одна библиотечка просто для портативности для каждой проги нужна копия DLL).
Что_нуна: Реализовать в DLL механизм чтения/записи данных в MMF (можно последовательный - но не обязательно).
Что_получилось: получилось реализовать 2 приложения работающие с DLL, и саму DLL. Но проблема в том что если 1приложение работает со совей копией DLL то 2 уже не может работать со своей.
Как быть? Если кто то швырнет в меня примером библиотечки (желательно с возможность последовательного доступа) - то я буду очень счастливым человеком. -
Rouse_ © (14.07.09 22:14) [1]
> если 1приложение работает со совей копией DLL то 2 уже не
> может работать со своей.
SHARE_ права выставилил при открытии? -
Loginov Dmitry © (14.07.09 22:29) [2]Отсутствует наиболее важное:
Как пытался получить -
Юрий Зотов © (14.07.09 22:31) [3]Не понял насчет копий DLL. Почему каждый EXE не может загрузить одну и ту же DLL и с ней работать?
-
Muxa/Ibl4 (14.07.09 22:36) [4]Долго объяснять но так требуют великие боги.
-
Muxa/Ibl4 (14.07.09 22:36) [5]Вот подобие моего говнокодаlibrary DllMmfDate;
uses
ShareMem,
SysUtils,
windows,
md5 in 'md5.pas';
type
User = record
sName : string;
iId : integer;
end;
aUser = array of User;
Command = record
sTitle : string;
iId: integer;
end;
aCommand = array of Command;
info = record
ListUser : aUser;
ListCommand: aCommand;
end;
tinfo = ^info;
var
SendMMF: THandle;
SendData: PChar;
const
NameAreaForWwork = 'trulala';
function writeMMF ( param : info ) : boolean;
var
rw : tinfo;
begin
if (MapViewOfFile(SendMMF, FILE_MAP_WRITE, 0, 0, 0) = nil ) then Begin
SendMMF := CreateFileMapping( $FFFFFFFF, Nil, PAGE_READWRITE, 0, SizeOf(DWORD), NameAreaForWwork);
end;
rw := MapViewOfFile(SendMMF, FILE_MAP_WRITE, 0, 0, 0);
rw^ := param;
Result :=true;
end;
function readMMF() : tinfo;
begin
if ( MapViewOfFile(SendMMF, FILE_MAP_READ, 0, 0, 0) = nil ) then Begin
SendMMF := CreateFileMapping( $FFFFFFFF, Nil, PAGE_READWRITE, 0, SizeOf(DWORD), NameAreaForWwork);
end else begin
SendMMF := OpenFileMapping(FILE_MAP_READ, False, NameAreaForWwork);
end;
Result := MapViewOfFile(SendMMF, FILE_MAP_READ, 0, 0, 0);
end;
function addUser( sUserName, iIdUser : PChar ): PChar; stdcall;
var
r : info;
rp : tinfo;
aUsers : aUser;
begin
rp := readMMF();
r := rp^;
aUsers := r.aUser;
setLength(termW, Length(aUsers)+1);
termW[Length(aUsers)-1].sName := sUserName;
termW[Length(aUsers)-1].terminalName := iIdUser;
r.aUser := aUsers;
writeMMF( r );
//.........
Result := 'тут текстовый результ';
end;
function getUserAll(): aUser; stdcall;
var
r : info;
rp : tinfo;
begin
rp := readMMF();
r := rp^;
Result := r.aUser;
end;
exports addUser, getUserAll;
begin
end. -
Игорь Шевченко © (14.07.09 22:42) [6]program files\borland\bds\x.0\demos\DelphiWin32\vclwin32\IPCDemos\*.*
-
Loginov Dmitry © (14.07.09 22:44) [7]Это именно говнокод. Все верно.
Здесь
http://pda.delphimaster.net/?n=6&id=1247596906
можно в целях изучения MMF посмотреть, что когда-то предлагали Тейксера и Пачека. -
Muxa/Ibl4 (14.07.09 23:00) [8]спасибо за ссылки, но у меня вопросов пару
подскажите
при такой реализации (я про DLL) - я же не могу 1 раз создать mmf и хранить его Handle т.к. в dll это не получиться и мне необходимо каждый раз как то проверять есть ли уже созданный файл или нет. Я прав или неверно мыслю? -
Muxa/Ibl4 (14.07.09 23:02) [9]после записи или чтения в mmf я тоже не могу "закрыть" его. Т.к. при следующем вызове функции мне тогда придется создавать его заново, т.е. потеря данных?
-
Игорь Шевченко © (14.07.09 23:04) [10]
> при такой реализации (я про DLL) - я же не могу 1 раз создать
> mmf и хранить его Handle т.к. в dll это не получиться и
> мне необходимо каждый раз как то проверять есть ли уже созданный
> файл или нет. Я прав или неверно мыслю?
на время загрузки DLL вполне получится. в инициализации DLL создать MMF, дальше пользоваться, при выгрузке освободить -
Игорь Шевченко © (14.07.09 23:04) [11]Muxa/Ibl4 (14.07.09 23:02) [9]
MMF (и данные в нем) будут жить, пока не закроется последний его Handle -
Muxa/Ibl4 (14.07.09 23:08) [12]но еще один момент, как я и сказал для каждой программы должна быть своя "личная" копия dll, как быть в этом случае?
-
Игорь Шевченко © (14.07.09 23:11) [13]Muxa/Ibl4 (14.07.09 23:08) [12]
А какая разница - у объекта FileMapping есть имя - пусть каждая DLL открывает его, кто не открыл по OpenFileMapping, пусть создает по CreateFileMapping.
Я ссылку на Demos не просто так дал, а со смыслом. -
Muxa/Ibl4 (14.07.09 23:22) [14]блин, вроде и понятно, но что то я запутался(
-
Loginov Dmitry © (15.07.09 00:36) [15]> блин, вроде и понятно, но что то я запутался
То, что в [5] долго еще будешь распутывать. А ведь
обмен между процессамы задумывается, верно?
Я лишь предложу перечень замечаний по [5], но, думаю, они
помогут разобраться в ошибках и уйти от них. Нет так нет.
1. По допустимым типам данных. Переменная rw : tinfo это по сути
указатель на разделяемую между приложениями область памяти. И что
мы хотим хранить в этой области памяти? Переменные ListUser : aUser и
ListCommand: aCommand. НО! Обе переменные - это массивы. Динамические.
(Их размер можно менять). По сути обе переменные - это указатели на области
памяти, в которых хранятся массивы. НО! Области памяти, в которых хранятся
массивы мы не расшариваем! Таким образом если какой-то обмен и будет - то
только ссылками на неизвестно что, взятое из чужого адресного пространства.
Выход: использовать статические массивы и короткие строки.
2. По использованию функции CreateFileMapping(). Что мы получим при таком вызове:
SendMMF := CreateFileMapping( $FFFFFFFF, Nil, PAGE_READWRITE, 0, SizeOf(DWORD), NameAreaForWwork);
будет создан соответствующий объект Windows, про который известно, что он не будет хранится в каком-либо
файле на диске, и его размер = 4 байта. В итоге получим хэндл этого объекта. И что дальше? А дальше идет
rw := MapViewOfFile(SendMMF, FILE_MAP_WRITE, 0, 0, 0);
что в данном случае приведет либо к выделению области памяти в 4 байта для записи, причем эта память будет
доступна для любого приложения, либо же функция вернет указатель на область памяти, которая ранее уже была
выделена для другого приложения. Причем функция может вернуть и вовсе NIL, если ей неправильно воспользовались.
(это - пояснение, а не замечание).
3. По реализации readMMF. Здесь все просто. Можно использовать одну лишь функцию CreateFileMapping(). Функцию
OpenFileMapping() используют немного в других ситуациях: когда требуется получить хэндл файлового отображения,
которое ранее уже было создано (а если оно не создано, то выполняется соответствующая обработка, а в каких-то
ситуациях может и CreateFileMapping). Если же просто нужен хэндл на файловое отображение не зависимо от того,
было ли одно до этого, или нет, то достаточно функции CreateFileMapping(). Она либо создаст объект, либо вернет
хэндл для существующего объекта.
4. По реализации addUser. Нельзя возвращать результат как PChar, предварительно не выделив под него память. В
данном случае ошибки может не возникнуть, поскольку есть явное присвоениеResult := 'тут текстовый результ'
. Строка является константной, она всегда находится в памяти в
одном и том же месте. А вот если вместо константной строки передать динамическую строку, то скорее всего будет
капут, т.к. память, выделенная под динамическую строку, освободится сразу же, как только завершится функция, и
в итоге функция вернет указатель на ничто. Вместо PChar нужно использовать string, тем более что подключен модуль
ShareMem.
5. Совсем мелкие замечания:
- имена типов в Delphi обычно начинаются с буквы T (например, TInfo)
- указатели на типизированные структуры начинаются с буквы P (например PInfo)
- stdcall определяет порядок вызова фукнции, при этом для передачи параметров используется стэк вместо
высокоскоростных регистров процессора. В данном случае оно ни в EXE ни в DLL не нужно.
- функцию writeMMF можно сделать процедурой. Возвращаемый ею результат все-равно бесполезен.
Итого - весь код с нуля нужно переделывать, учесть все замечания всех ответивших в этой ветке. -
Юрий Зотов © (15.07.09 09:36) [16]> Muxa/Ibl4 (14.07.09 22:36) [4]
Богам, возможно, виднее, но даже и простым смертным известно, что каждая запущенная в Windows программа грузит библиотеку Kernel32.dll. Запустили 100 программ - библиотека 100 раз загружена.
Одна и та же. И никаких копий. И все замечательно работает. И так со всеми DLL. И непонятно, на кой ляд богам понадобилось принимать такие (skipped) решения.
PS
По коду даже не говорю ничего. Потому что слишком много говорить придется. Документацию на используемые функции надо читать внимательнее. -
Muxa/Ibl4 (21.07.09 22:33) [17]Все сдаюсь, прошу если есть возможность дать пример _dllки_ где есть метод чтения и метод записи MMF
-
Сергей М. © (21.07.09 22:39) [18]
> 2 уже не может работать со своей
Что, вот прямо так и кричит "не могу" ? -
Muxa/Ibl4 (21.07.09 22:52) [19]я уже согласен на такую реализацию, там конечно есть трудности но на это я забил.
-
Сергей М. © (21.07.09 23:04) [20]Михалыч, ты на вопрос в [18] соизволишь ответить ?)
-
antonn © (22.07.09 00:29) [21]вместо $FFFFFFFF правильнее использовать INVALID_HANDLE_VALUE
-
Игорь Шевченко © (22.07.09 01:11) [22]antonn © (22.07.09 00:29) [21]
> вместо $FFFFFFFF правильнее использовать INVALID_HANDLE_VALUE
const
INVALID_HANDLE_VALUE = DWORD(-1);
почему правильнее ? -
antonn © (22.07.09 01:22) [23]потому что на 64-битной системе будет не -1 :)
-
Игорь Шевченко © (22.07.09 02:05) [24]antonn © (22.07.09 01:22) [23]
До Delphi 64 еще дожить надо. Хотя специально посмотрел в своих промышленных проектах - таки INVALID_HANDLE_VALUE использую.
Значит, ты прав :) -
Юрий Зотов © (22.07.09 02:06) [25]> Muxa/Ibl4 (21.07.09 22:33) [17]
Лови, я сегодня добрый. И давно на Delphi не писал, соскучился даже - вот и захотелось размяться слегка. Писал в Delphi 7.
Только не копируй бездумно - разберись в каждой строчке и в каждой букве - что она делает, зачем она это делает, как она это делает и почему она это делает именно тут, а не в другом месте. Справка и MSDN тебе в помощь, там все описано. А что останется неясным - спрашивай здесь.
1. Полный код общего юнита (используется и в DLL, и в тестовой программе)
unit DataType;
interface
type
TData = packed record
Int: integer;
Str: ShortString
end;
PData = ^TData;
TWriteData = procedure(Source: PData); stdcall;
TReadData = function: PData; stdcall;
const
DLL_NAME = 'MMF_DLL';
ReadDataProcName = 'ReadData';
WriteDataProcName = 'WriteData';
implementation
end.
2. Полный код DLL
library MMF_DLL;
uses
Windows,
SysUtils,
DataType in 'DataType.pas';
const
MMF_NAME = 'MySharedData';
var
HData: THandle = 0;
DataPtr: PData = nil;
procedure EntryProc;
begin
HData := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(TData), MMF_NAME);
Win32Check(HData <> 0);
DataPtr := MapViewOfFile(HData, FILE_MAP_ALL_ACCESS, 0, 0, 0);
Win32Check(DataPtr <> nil)
end;
procedure ExitProc;
begin
UnmapViewOfFile(DataPtr);
CloseHandle(HData)
end;
procedure MyDllProc(Reason: integer);
begin
case Reason of
DLL_PROCESS_ATTACH: EntryProc;
DLL_PROCESS_DETACH: ExitProc
end
end;
procedure WriteData(Source: PData); stdcall;
begin
DataPtr^ := Source^
end;
function ReadData: PData; stdcall;
begin
Result := DataPtr
end;
exports
ReadData name ReadDataProcName,
WriteData name WriteDataProcName;
begin
if @DllProc = nil then
DllProc := MyDllProc;
MyDllProc(DLL_PROCESS_ATTACH)
end.
3. Код главной формы тестовой программы (на форму бросить 2 кнопки и назначить им OnClick)
uses
DataType;
var
Data: TData;
HLib: THandle = 0;
ReadData: TReadData = nil;
WriteData: TWriteData = nil;
procedure TForm1.FormCreate(Sender: TObject);
begin
HLib := LoadLibrary(DLL_NAME);
Win32Check(HLib <> 0);
@ReadData := GetProcAddress(HLib, ReadDataProcName);
Win32Check(@ReadData <> nil);
@WriteData := GetProcAddress(HLib, WriteDataProcName);
Win32Check(@WriteData <> nil);
Randomize
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeLibrary(HLib)
end;
procedure TForm1.ReadButtonClick(Sender: TObject);
begin
Data := ReadData^;
Caption := Format('Прочитано: < %d %s >', [Data.Int, Data.Str])
end;
procedure TForm1.WriteButtonClick(Sender: TObject);
begin
Data.Int := Random(10);
if Data.Int > 4 then
Data.Str := 'Привет Богам!'
else
Data.Str := 'А DLL-то одна!';
WriteData(@Data);
Caption := Format('Записано: < %d %s >', [Data.Int, Data.Str])
end;
4. Что с этим делать.
Вначале все компилируем в один каталог (любой). Потом запускаем 2 копии тестовой программы и в обоих жмем кнопки Write и Read. Убеждаемся, что каждая программа читает из MMF точно то, что перед этим в MMF записала либо она сама же, либо другая программа.
PS
И богам своим покажи. Решение они приняли, панимашь... обхохотаться можно над такими решениями. -
Германн © (22.07.09 02:48) [26]
> Юрий Зотов © (22.07.09 02:06) [25]
>
> > Muxa/Ibl4 (21.07.09 22:33) [17]
>
> Лови, я сегодня добрый. И давно на Delphi не писал
Я точно не буду таким добрым. :) -
Юрий Зотов © (22.07.09 02:49) [27]Небольшой комментарий. По сути, процедура WriteData не нужна. После загрузки DLL тестовая программа может вызвать функцию ReadData, от нее получить указатель на общую область памяти и запомнить этот указатель - таким образом, она получает прямой доступ к этой общей области и может писать в нее (и читать из нее) что угодно, без всяких посредников.
В таком варианте функцию ReadData правильнее было бы обозвать, например, GetDataAddress, а в тестовой программе вместо переменной Data:TData ввести переменную DataAddress:PData.