Эта статья впервые была напечатана в ClarionOnline ( http://www.clariononline.com ) том 2 выпуск 10 (май 1999)
Работа с email через Simple MAPI
Компьютеры и интернет становятся все более распространенными в обществе, люди все более привычно используют email, веб и т.п. Одна из вещей, которую вы должны сделать как разработчик - это добавить поддержку email в ваши программы.
Как и в большинстве вещей в жизни, имеется много средств для достижения одной и той же цели. Для случае поддержки email, самый простой способ это использовать уже имеющиеся инструменты третьих фирм. Однако, я предпочитаю использовать мой собственный интерфейс - это может занять больше времени чем хотелось бы, но я должен иметь полный контроль за всем и полностью понять всю поддерживающую философию.
К сожалению, как и в большинстве вещей, здесь есть что изучать. Когда вы поймете базовые принципы, то все становится легко.
Какой интерфейс?
Есть несколько разных интерфейсов, которые может использовать разработчик для добавления функций email в свое приложение. К сожалению, не все из этих возможностей доступны Clarion-разработчику. Вот что вы можете использовать и почему:
MAPI (Mail API)
Это базовая почтовая система, на которой базируются другие три. Она предоставляет очень большой контроль за всеми аспектами почтовой системы, но это полностью объектно-ориентированная и COM-совместимая реализация. Для нас как для разработчиков это означает (имея в виду что Clarion в настоящее время не совместим с COM) что в основном мы ее не можем использовать. Если имеются функции, которые вы хотите чтобы другие возможности не могли адресовать, то вероятно есть VBX или OCX, которые это могут.
CMC (Common Messaging Calls) и Simple MAPI (Simple Mail API)
CMC и SMAPI предоставляют простой набор из дюжины высокоуровневых вызовов API, которые могут использовать Clarion разработчики. Функции предоставляемые SMAPI и CMC практически идентичны, но CMC немного компактнее, он объединяет много возможности в одной функции. Хотя реализация Microsoft основана на MAPI, сам CMC API был разработан в объединении с ассоциацией стандартов X.400 API, и поддерживается в платформах Windows, MS-DOS, OS/2, Macintosh, и UNIX.
AML (Active Message Library)
AML является программным интерфейсом OLE automation, который является компонентом интерфейса MAPI. AML предоставляет программируемые объекты, подобные объектам Microsoft Excel и объектам Microsoft Access, что делает доступными свойства и методы, которые могут управляться при помощи программ на Visual Basic (VB) и Visual Basic for Applications (VBA). И опять, из-за того что Clarion не поддерживает COM и OLE (I’m being PC here), это означает что мы не можем их использовать.
В результате нам остается выбор из CMC и SMAPI. Как уже сказано выше, они практически одинаковы, исключая то что CMC является кросс-платформеным а SMAPI нет.
Имея это в виду, я собираюсь описать основы использования Simple MAPI (SMAPI) в программах на Clarion. Имеется 12 функций Simple MAPI - я собираюсь использовать 5 из них для отсылки и приема почты. Они все практически одинаковы, поэтому если вы хотите использовать одну из тех, что я не описал, то они получают параметры подобные приведенным ниже
Функция Simple MAPI Описание MAPIAddress Адресовать сообщение MAPIDeleteMail Удалить сообщение MAPIDetails Показать диалоговое окно recipient-details MAPIFindNext Возвращает идентификатор первого или следующего сообщения указанного типа MAPIFreeBuffer Освобождает память, выделенную системой сообщений MAPILogoff Завершает сессию системы сообщений MAPILogon Устанавливает систему сообщений MAPIReadMail Прочитать сообщение MAPIResolveName Выводит диалоговое окно разрешения неоднозначного имени получателя MAPISaveMail Сохраняет сообщение MAPISendDocuments Посылает стандартное сообщение с использованием диалогового окна MAPISendMail Посылает сообщение, предоставляя большую гибкость в формировании сообщения чем MAPISendDocuments() Установлен ли клиентский интерфейс?
Хотя мы только что определились с нашим почтовым интерфейсом, не все компьютеры могут иметь уже установленным этот интерфейс, поэтому мы должны определить имеется ли он и запретить соответствующие пункты меню если его нет. Мы можем проверить наличие каждого их 4-х почтовых интерфейсов используя процедуру GETINI для файла WIN.INI. Соответствующая секция ini-файла будет похожа на следующую. Если интерфейс установлен, то ее опция будет установлена в 1, иначе она будет равна 0.
[Mail] ;MAPI MAPIX=0 or 1 ;CMC CMC=0 or 1 ;Simple MAPI MAPI=0 or 1 ;AML OLEMessaging=0 or 1
Ваша программа, когда обнаружит присутствие нужного интерфейса, должна вызвать функции LoadLibrary() и GetProcAddress() для соответствующих DLL. После этого мы можем вызывать нужные нам процедуры. С другой стороны, гораздо проще прилинковать к программе библиотеку импорта MAPI статически. Конечно, если мы так сделали а MAPI не установлена, то приложение не сможет запуститься. Итак, что же мы делаем? Правильнее не прилинковывать MAPI.DLL статически (для случая если он не установлен).
Моя теория такова. Во первых, каждый новый компьютер приходит с предустановленной Windows, и поддержка email виртуально гарантирована. Во вторых, если вы делаете поддержку email по запросу клиента, то он уже должен иметь какую-либо систему email уже установленную.
Как вы уже вероятно поняли, я линкую свою программу а не импортирую библиотеку. Не ищите ее на своем компьютере, так как вероятно вы ее не найдете. Она не поставляется с Clarion, так как Clarion не декларирует поддержку SMAPI , и ее нет в вашем каталоге \Windows, так как Microsoft не имеет привычки поставлять библиотеки импорта для своих DLL. Для получения нужного LIB-файла вы должны воспользоваться LibMaker-ом. Это удобная утилита поставляется со всеми последними версиями Clarion в той или другой форме. Она просто создает LIB-файлы, которые вы можете ликовать вместо оригинальной DLL. Запустите LibMaker и найдите MAPI32.DLL (или MAPI.DLL если вы делаете 16-разрядную программу). Вероятно, она находится в \WINDOWS\SYSTEM (или в \WINNT\SYSTEM под Windows
NT).
Сессии
Взаимодействие между вашей клиентской программой и подсистемой SMAPI основано на концепции сессий. Перед тем как ваша программа сможет вызывать низкоуровневую систему сообщений, она должна установить сессию, или соединение, с подсистемой MAPI. Сессия инициализируется когда пользователь "логинится", убеждается что существует доступ к правильному профилю, подтверждает полномочия системы сообщений и службы сообщений, и страхуется против того что все профили службы сообщений правильно сконфигурированы.
Клиенты могут указать один из двух типов сессий для установки при вызове logon: индивидуальную сессию или разделяемая (shared). Индивидуальные сессии являются приватными соединениями; они используют соединение "один к одному" между клиентской программой и сессией. Как следствие, клиентские программы разделяющие сессию также разделяют профиль. Разделяемые сессии устанавливаются однажды, но могут "использоваться" другими клиентскими программами, которые нуждаются в них.
Logging on и создание новой MAPI сессии
Для создания новой сессии нужно использовать функцию MAPILogon(). Ее API прототип:
ULONG FAR PASCAL MAPILogon( ULONG ulUIParam, LPTSTR lpszProfileName, LPTSTR lpszPassword, FLAGS flFlags, ULONG ulReserved, LPLHANDLE lplhSession )
Clarion прототип:
MAPILogon(ULONG,ULONG,ULONG,ULONG,ULONG,*ULONG),ULONG,RAW,PASCAL
Параметры имеют следующие объяснение:
MAPI_FORCE_DOWNLOAD ытаться загрузить все пользовательские сообщения перед возвратом. Если признак MAPI_FORCE_DOWNLOAD не установлен, то сообщения могут быть загружены в фоновом режиме после того как произойдет возврат из функции. MAPI_NEW_SESSION Пытаться создать новую сессию до того как получена среда (environment's) разделяемой сессии. Если признак MAPI_NEW_SESSION не установлен, то MAPILogon использует существующую разделяемую сессию. MAPI_LOGON_UI Должен быть выведен logon dialog box для ввода пользователем logon information. Если пользователь должен сообщить пароль и имя профайла, то признак MAPI_LOGON_UI должен быть включен. MAPI_PASSWORD_UI MAPILogon должен только ввести пароль и не давать пользователю изменить имя профайла. Признак MAPI_PASSWORD_UI или MAPI_LOGON_UI не должен быть установлен, так как нужно выбрать для logon один из двух разных диалоговых окон.
lplhSession - содержит хендл сессии при успешном logon-е, и он вам нужен будет (обычно) для следующих функций MAPI.
Для использования этой процедуры, я нашел что проще написать для нее wrapper некоторого рода, обычно довольно похожий на следующий:
LogOn FUNCTION (Prm:UserName,Prm:Password)
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
ulMapiSession ULong
End
CODE
Loc:ulMapiStatus = MAPILogon(0,Address(Prm:Username),Address(Prm:Password),MAPI_LOGON_UI + MAPI_NEW_SESSION,0,Loc:ulMapiSession)
IF Loc:ulMapiStatus = SUCCESS_SUCCESS Then
Return(Loc:ulMapiSession)
Else
Return(0)
End
Он имеет эффект "логина" в MAPI. Он всегда создает новую сессию и всегда выводит пользователю диалоговое окно. Если logon успешен, то он возвращает хендл сессии вызывающей процедуре.
Если вы всегда знаете имя пользователя и пароль, то можно изменить параметр flFlags чтобы не выводилось окно, а просто создавалась бы новая сессия. Но по моему опыту всегда легче (как минимум во время разработки) просто показать диалоговое окно и позволить пользователю его заполнить. В этом случае вы не должны беспокоится о правильности имени пользователя и пароле в параметрах программы.
Отсылка email
Для отсылки email используется процедура MAPISendMail(). Ее API прототип:
ULONG FAR PASCAL MAPISendMail( LHANDLE lhSession ULONG ulUIParam lpMapiMessage lpMessage FLAGS flFlags ULONG ulReserved )
Прототип Clarion:
MAPISendMail(ULONG,ULONG,*GROUP,ULONG,ULONG),ULONG,RAW,PASCAL
Опять, параметры нуждаются в некотором объяснении:
Важные параметры здесь lpMessage и flFlags. Они определяют как создается emai, что видит пользователь.
lpMessge это группа следующей структуры:
MapiMessageType GROUP,TYPE
ulReserved ULONG ! Зарезервирован, должен быть 0
lpszSubject ULONG ! Указатель на subject, CSTRING максимум 256
lpszNoteText ULONG ! Указатель на text, CSTRING, CR как параграфы
lpszMessageType ULONG ! Указатель на message type, null по умолчанию
lpszDateReceived ULONG ! Указатель на строку в формате YY/MM/DD
lpszConversationID ULONG ! Игнорируется
flFlags ULONG ! 1 - MAPI_UNREAD
! 2 - MAPI_RECEIPT_REQUESTED
! 3 - MAPI_SENT
lpOriginator ULONG ! Указатель на группу MapiRecipDesc
nRecipCount ULONG ! Счетчик групп указываемых lpRecips
lpRecips ULONG ! Указатель на массив групп recipient descriptor
nFileCount ULONG ! Счетчик групп file attachment указываемых lpFiles
lpFiles ULONG ! Указатель на массив групп file attachment
END
При отсылке email, вы должны сначала определить будет ли ваша программа ответственна за приглашения пользователю о subject, destination address и mail text , или же MAPI должно выводить свое собственное окно. Если первое, то вы должны заполнить соответствующие поля группы и вызвать MAPISendMail() не устанавливая флаг MAPI_DIALOG. Если же вы хотите чтобы MAPI делала за вас всю эту работу (а почему бы и нет?), то просто оставьте группу пустой и установите признак MAPI_DIALOG. Это приведет к появлению стандартного окна почты Microsoft, и освободит вас от разработки другого окна.
Если вы не хотите передать MAPI всю эту работу, то это скорее нудно чем быстро. Destination addresses работает по принципу счетчика (число людей которым посылается почта), и указателя на массив из групп (одна для каждого человека). Вы должны заполнить поля lpszSubject и lpszNoteText адресом (ADDRESS()) соответствующей CSTRING. nRecipCount надо установить в 1, и установить lpRecips адресом (ADDRESS()) структуры содержащей адреса первого человека, которому вы посылаете сообщения. Эта структура выглядит наподобие:
MapiRecipDescType GROUP,TYPE ! УКАЗАТЕЛЬ ПОЛУЧАТЕЛЯ
ulReserved ULONG ! Зарезервирован, должен быть 0
ulRecipClass ULONG ! Роль получателя в сообщении ! 0 - MAPI_ORIG
! 1 - MAPI_TO
! 2 - MAPI_CC
! 3 - MAPI_BCC
lpszName ULONG ! Указатель на выводимое имя получателя
lpszAddress ULONG ! Указатель на почтовый адрес получателя
ulEIDSize ULONG ! Размер (в байтах) двоичных данных в lpEntryID.
lpEntryID ULONG ! Указатель на двоичные данные представляющие получателя
END
Практически, я нашел, что в этой структуре нужно только установить поля ulRecipClass (для MAPI_TO и lpszName как ADDRESS() CSTRING содержащей его имя). Если вам также нудны CC или BCC где либо, то то нужно увеличить счетчик nRecipCount оригинальной группы, и установить ulRecipClass в MAPI_CC или MAPI_BCC в N-ой копии группы. все остальное можно оставить равным 0 или NULL.
Если нужно послать приложения, это также нудно. Приложения описываются таким же самым образом что и адреса назначения - имеется счетчик (число приложений), и указатель на массив групп (по одной на приложение). Группы приложений следующие:
MapiFileDescType GROUP,TYPE ! FILE DESCRIPTOR
ulReserved ULONG(0) ! Reserved, must be 0.
flFlags ULONG(0) ! Ignored.
nPosition ULONG ! where in the message text to place the file
lpszPathName ULONG ! Pointer to full pathname of attached file.
lpszFileName ULONG ! Pointer to file name seen by user.
lpFileType ULONG(0) ! Reserved, must be NULL.
END
Как вы видите, виртуально можно оставить все поля пустыми, кроме lpszPathName, nPosition и lpszFileName. Я нашел что хорошая идея не передавать в lpszPathName полные имена и путями, так как будут проблемы на приемном конце если путь не существует. Вместо этого, я пытаюсь использовать PATH() для сохранения текущего пути, делать SETPATH() на путь где лежит файл, и затем делать SETPATH() назад перед вызовом MAPISendMail().
Другая проблема с приложениями - поле nPosition. Это поле определяет где размещен файл приложения, поэтому если вы установите его равным 1, то приложение перекроет первый символ текста. Под конец я добавляю один дополнительный символ в конец текста и затем устанавливаю поле nPosition в позицию этого символа.
Проверка и получение почты
Один из недостатков Simple MAPI это недостаток функций (это потому что называется
Simple!)
Этот недостаток функций проявляет себя в том что вы можете проверить наличие почты только в главном приемном каталоге почты (Inbox). Если вы опытный пользователь и используете Inbox Assistant для перенаправления сообщений в другие каталоги, то Simple MAPI не сможет прочитать почту в этих каталогах, только в главном каталоге.
Однако, если это все что вы хотите сделать, то вот как это делается. Но теперь вам потребуется уже три функции:
ULONG FAR PASCAL MAPIFindNext(
LHANDLE lhSession,
ULONG ulUIParam,
LPTSTR lpszMessageType,
LPTSTR lpszSeedMessageID,
FLAGS flFlags,
ULONG ulReserved,
LPTSTR lpszMessageID )
ULONG FAR PASCAL MAPIReadMail(
LHANDLE lhSession,
ULONG ulUIParam,
LPTSTR lpszMessageID,
FLAGS flFlags,
ULONG ulReserved,
lpMapiMessage FAR * lppMessage )
ULONG FAR PASCAL MAPIFreeBuffer( LPVOID pv )
Соответствующие прототипы Clarion:
MAPIFindNext(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG,ULONG),ULONG,RAW,PASCAL MAPIReadMail(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG),ULONG,RAW,PASCAL MAPIFreeBuffer(ULONG),ULONG,RAW,PASCAL
MAPIFindNext() ищет следующий непрочитанный email, и возвращает идентификатор сообщения. MAPIReadMail() принимает этот идентификатор и действительно получает сообщение (отмечая его как прочитанное). MAPIFreeBuffer() освобождает буфер, который был выделен для почты. Не освобождение буфера приводит к "утескам памяти", которые через некоторое время могут поставить машину на колени. Никогда не забывае освобождать буфер!
Мы начинаем обработку вызовом MAPIFindNext():
- MAPI_GUARANTEE_FIFO
Возвращаемые идентификаторы сообщений должны извлекаться в порядке времени. Вызовы MAPIFindNext могут быть дольше если установлен этот флаг. Некоторые реализации не принимают этот запрос и возвращают значение MAPI_E_NO_SUPPORT. MAPI_LONG_MSGID Возвращаемый идентификатор сообщения может быть длиной 512 символов. Если этот признак установлен, то параметр lpszMessageID должен быть достаточным для приема до 512 символов. Старые версии MAPI поддерживают малые идентификаторы сообщений (64 байта) и не включают этот флаг. MAPIFindNext успешно завершится без установки этого флага если lpszMessageID достаточно велик для того чтобы вместить идентификатор сообщения. Если lpszMessageID не может вместить идентификатор сообщения, то MAPIFindNext завершиться неудачно.
MAPI_UNREAD_ONLY Должны быть перечислены только непрочитанные сообщения указанного типа. Если этот флаг не установлен, то MAPIFindNext может возвратить любое сообщение указанного типа.
LpszMessageID - возвращается, и является идентификатором первого непрочитанного сообщения.
Придерживаясь идеи написания wrapper-ов вокруг всего, процедура для поиска следующего непрочитанного сообщения может быть примерно такой:
FindNextUnread FUNCTION
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
MessageType CString(10)
MessageID CString(515)
End
CODE
Loc:MessageType = ''
Loc:ulMapiStatus = MapiFindNext(SMAPI:Session,|
0,|
0,|
0,|
MAPI_UNREAD_ONLY+MAPI_LONG_MSGID,|
0,|
Address(Loc:MessageID))
If Loc:ulMapiStatus <> SUCCESS_SUCCESS Then
Clear(Loc:MessageID)
End
Return(Clip(Loc:MessageID))
Когда мы нашли следующее новое сообщение, следующее что нужно сделать - это действительно прочитать сообщение. Параметры для MAPIReadMail():
MAPI_BODY_AS_FILE MAPIReadMail должна записывать текст сообщения во временный файл и добавить его как первое приложение (attachment) в списке приложений. MAPI_ENVELOPE_ONLY MAPIReadMail должна читать только заголовок сообщения. Файлы приложений не копируются во временные файлы, и никакие имена временных файлов и тексты сообщения не записывается. Установка этого флага увеличивает быстродействие. MAPI_PEEK MAPIReadMail не отмечать сообщение как прочитанное. Отметка сообщения как прочитанного действует на его видимость в интерфейсе пользователя и генерирует подтверждение получения. Если система сообщений не поддерживает этот флаг, то MAPIReadMail всегда отмечает сообщение как прочитанное. Если MAPIReadMail встретит ошибку, то она оставляет сообщение напрочитанным. MAPI_SUPPRESS_ATTACH MAPIReadMail не должна копировать файловые приложения (attachments) но должна записывать текст сообщения в структуру MapiMessage. MAPIReadMail игнорирует этот флаг если вызывающая программа установила флаг MAPI_ENVELOPE_ONLY. Установка признака MAPI_SUPPRESS_ATTACH увеличивает быстродействие.
lppMessage это указатель на структуру сообщения (это та же самая структура что используется для отправки сообщений). Структура может быть освобождена одним вызовом функции MAPIFreeBuffer(). Если флаги MAPI_ENVELOPE_ONLY и MAPI_SUPPRESS_ATTACH не установлены, то приложения (attachments) записываются во временные файлы, указываемые параметром lpFiles структуры MapiMessage. Ответственность за удаление этих файлов лежит на вызывающей программе если они больше не нужны.
Код будет выглядеть наподобие вот этого:
ReadMail FUNCTION (Prm:MessageID)
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
MessageType CString(10)
MessageID CString(100)
MailTitle CString(255)
MailSender CString(255)
MailText CString(8192)
MessagePtrPtr ULong
MessagePtr ULong
End
RxMessage Like(MapiMessageType) ! message info
RxSender Like(MapiRecipDescType) ! sender info
RxFile Like(MapiFileDescType) ! attached file info
CODE
If SMAPI:LoggedOn Then
Loc:MessageID = Clip(Prm:MessageID) & Chr(0)
Clear(RxMessage)
Loc:MessagePtrPtr = Address(RxMessage)
Loc:ulMapiStatus = MAPIReadMail( SMAPI:Session,|
0,|
Address(Loc:MessageID),|
MAPI_SUPPRESS_ATTACH,|
0,|
Loc:MessagePtrPtr)
If Loc:ulMapiStatus = SUCCESS_SUCCESS Then
MemCpy( Address(Loc:MessagePtr) , Loc:MessagePtrPtr , 4)
MemCpy( Address(RxMessage) , Loc:MessagePtr , Size(RxMessage) )
If RxMessage:lpszSubject <> 0 Then
MemCpy( Address(Loc:MailTitle) , RxMessage:lpszSubject , 100 )
Else
Loc:MailTitle = 'Untitled'
End
If RxMessage:lpszNoteText <> 0 Then
memcpy( Address(Loc:MailText) , RxMessage:lpszNoteText , 100 )
Else
Clear(Loc:MailText)
End
If RxMessage:lpOriginator <> 0 Then
memcpy( Address(RxSender), RxMessage:lpOriginator, Size(RxSender) )
If RxSender:lpszAddress <> 0 Then
memcpy( Address(Loc:MailSender), RxSender:lpszAddress, 100 )
End
Else
Clear(Loc:MailSender)
End
SMAPI:Rx:MailTitle = Clip(Loc:MailTitle)
SMAPI:Rx:MailText = Clip(Loc:MailText)
SMAPI:Rx:MailSender = Clip(Loc:MailSender)
Loc:ulMapiStatus = MAPIFreeBuffer(Loc:MessagePtr)
If Loc:ulMapiStatus <> SUCCESS_SUCCESS Then
SMAPI:ErrorString = MapiError(Loc:ulMapiStatus)
End
End
End
Return(Loc:ulMapiStatus)
Если вы внимательно посмотрите этот код, то увидите что мы продвигаемся от указателя к указателю структуры сообщения. Когда мы знаем что сообщение успешно прочитано, мы вызываем MemCpy для копирования адресов структуры в первый указатель. Мы копируем 4 байта потому что их размер long, что обычно для указателей в Clarion. Как только у нас есть указатель на структуру сообщения, мы de-reference его опять и действительно копируем данные из памяти в структуру сообщения, потому что так проще работать. После того как сообщение прочитано (и "отвечено" если нужно), нам нужно освободить память структуры.
Отключение (Logging off) от MAPI
Следующее. что нам нужно сделать - отключиться (logoff) и закрыть сессию. Прототип API:
ULONG FAR PASCAL MAPILogoff ( LHANDLE lhSession, ULONG ulUIParam, FLAGS flFlags, ULONG ulReserved )
Прототип Clarion:
MAPILogoff(ULONG,ULONG,ULONG,ULONG),ULONG,PASCAL,RAW
По сравнению с тем то мы уже сделали с MAPI, реализация этого кода очень легка:
LogOff PROCEDURE
LocalVars Group,Pre(Loc)
ulMapiStatus ULong
End
CODE
Loc:ulMapiStatus = MAPILogoff( SMAPI:Session,0,0,0)
IF Loc:ulMapiStatus = SUCCESS_SUCCESS then
SMAPI:LoggedOn = False
End
Выводы
Когда понятны примененные принципы, использование Simple MAPI добавляет поддержку электронной почты в ваши программы довольно легко. Осталось еще довольно много, что я не смог рассказать, просто потому что вопрос довольно сложен, но это должно дать вам возможность начать.
Конечно, по своей природе Simple MAPI имеет
ограниченные функции, но даже это дает вам
некоторые идеи по ее применению. Если вы не
думаете что Simple MAPI достаточно для вашей
задачи, то попытайтесь использовать
шаблоны третьих фирм или контролы VBX/OCX - я
уверен что они имеют нужные вам функции.
В следующий раз
Использование Windows API для связи через RS232
Загрузить исходный текст примера
На домашнюю страницу. Или послать мне письмо paula@attglobal.net
(c) 1999 Paul Attryde