Vc |
ToolTip(всплывающей подсказки) |
Первоначально необходимо создать окно класса TOOLTIPS_CLASS, потом заполнить структуру TOOLINFO:
typedef struct tagTOOLINFO{ UINT cbSize; UINT uFlags; HWND hwnd; UINT_PTR uId; RECT rect; HINSTANCE hinst; LPTSTR lpszText; #if (_WIN32_IE > = 0x0300) LPARAM lParam; #endif } TOOLINFO, NEAR *PTOOLINFO, FAR *LPTOOLINFO; Определяем два параметра в этой структуре, которые имеют для нас значение uFlags и lpszText. uFlags выбираем равным TTF_TRACK, что означает возможность использования посылки сообщений выбирающих позицию для ToolTip-a и видимость. lpszText - задание текста, который мы хотим выводить. Теперь мы посылаем сообщение в систему, о желании создать всплывающую подсказку, куда передаём ссылку на нашу структуру SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti). Затем посылаем сообщение TTM_TRACKPOSITION, которая задаёт координаты всплывающей подсказки SendMessage(hwndTT, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD) MAKELONG(m_x, m_y)), где m_x и m_y координаты x и y на экране. И в последнюю очередь посылаем сообщение о активизации всплывающей подсказки S endMessage(hwndTT, TTM_TRACKACTIVATE, true, (LPARAM)(LPTOOLINFO) &ti), где true параметр указывающий на отображение подсказки, при выборе false, подсказка будет скрыта. |
Просмотр графических файлов(bmp,pcx,tiff,gif,jpeg) |
Функции стандартного ввода/вывода (printf, scanf) |
Работа с ADO |
Установка ловушек |
Создание драйверов |
Драйвер с нуля |
Ссылки: Первое с чем сталкивается программист, решивший заняться драйверами это почти полное отсутствие русскоязычной литературы по данной теме. За год поисков мне удалось найти всего три книги на русском, в которых хотя бы немного говориться о драйверах: ----------------------- 1. Свен Шрайбер «Недокументированные возможности Windows 2000». Издательство «Питер» 2002 год. Здесь очень хорошо описан механизм динамической загрузки драйвера. ------------------ 2. П. И. Рудаков, К. Г. Финогенов «Язык ассемблера: уроки программирования» Диалог МИФИ 2001 год. Очень полезная книга для того, что бы понять, как писать драйвера без всякий Wizard-ов. ----------------------------- 3. Светлана Сорокина, Андрей Тихонов, Андрей Щербаков «Программирование драйверов и систем безопасности». Издательство «БХВ-Петербург» 2002 год. Здесь хорошо описывается многоуровневая модель драйверов. Для начала надо установить на компьютер Visul C++ 6.0, MSDN и NTDDK установку проводить желательно именно в этом порядке. Лично я пользуюсь редактором UltraEdit для работы с текстами драйверов, но в принципе исходный код драйвера можно набирать в любом текстовом редакторе, хоть в NotePad. Создадим папку, в которой мы будем работать с драйвером (пусть это будет C:\myDriver). В этой папке создадим 5 файлов: 1. myDrv.c 2. myDrv.h 3. myDrv.rc 4. MAKEFILE 5. SOURCES Начнем с последнего файла: В SOURCES скопируйте следующее: TARGETNAME=myDrv TARGETPATH=obj TARGETTYPE=DRIVER SOURCES=myDrv.c MyDrv.rc В MAKEFILE скопируйте следующее: !INCLUDE $(NTMAKEENV)\makefile.def В myDrv.rc скопируйте следующее: #include <windows.h> #include <ntverp.h> #define VER_FILETYPE VFT_DRV #define VER_FILESUBTYPE VFT2_DRV_SYSTEM #define VER_FILEDESCRIPTION_STR "Generic Port I/O" #define VER_INTERNALNAME_STR "myDrv.sys" #include "common.ver" А вот так должен выглядеть myDrv.h: #define FIRST_IOCTL_INDEX 0x800 #define FILE_DEVICE_myDRV 0x00008000 #define TEST_SMTH CTL_CODE(FILE_DEVICE_myDRV, \ FIRST_IOCTL_INDEX + 101, \ METHOD_BUFFERED, \ FILE_ANY_ACCESS) Теперь обратимся к тексту самого драйвера myDrv.c: #include "ntddk.h" #include "myDrv.h" #include "parallel.h" #define NT_DEVICE_NAME L"\\Device\\myDrv" #define DOS_DEVICE_NAME L"\\DosDevices\\myDrv" //структура расширения устройства typedef struct _DEVICE_EXTENSION { PDRIVER_OBJECT DriverObject; PDEVICE_OBJECT DeviceObject; PFILE_OBJECT FileObject; HANDLE Handle; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; //прототипы функций NTSTATUS DriverDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); VOID DriverUnload(IN PDRIVER_OBJECT DriverObject); NTSTATUS DriverOpen(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); NTSTATUS DriverClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////реализация функций NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { PDEVICE_OBJECT deviceObject; UNICODE_STRING deviceNameUnicodeString; UNICODE_STRING deviceLinkUnicodeString; PDEVICE_EXTENSION extension; NTSTATUS ntStatus; RtlInitUnicodeString(&deviceNameUnicodeString, NT_DEVICE_NAME); ntStatus = IoCreateDevice(DriverObject, sizeof (DEVICE_EXTENSION), &deviceNameUnicodeString, FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject); if (!NT_SUCCESS(ntStatus)) return ntStatus; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl; DriverObject->DriverUnload = DriverUnload; DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverOpen; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverClose; extension = (PDEVICE_EXTENSION) deviceObject->DeviceExtension; extension->DeviceObject = deviceObject; extension->DriverObject = DriverObject; // Create counted string version of our Win32 device name. RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME); // Create a link from our device name to a name in the Win32 namespace. ntStatus = IoCreateSymbolicLink(&deviceLinkUnicodeString, &deviceNameUnicodeString); if (!NT_SUCCESS(ntStatus)) { IoDeleteDevice(deviceObject); return ntStatus; } return STATUS_SUCCESS; } //------------------------------------------------------------------------------------------------------------------- VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { UNICODE_STRING deviceLinkUnicodeString; PDEVICE_EXTENSION extension; PIRP pNewIrp = NULL; ULONG m_size; NTSTATUS ntStatus; extension = DriverObject->DeviceObject->DeviceExtension; // Create counted string version of our Win32 device name. RtlInitUnicodeString(&deviceLinkUnicodeString, DOS_DEVICE_NAME); // Delete the link from our device name to a name in the Win32 namespace. IoDeleteSymbolicLink(&deviceLinkUnicodeString); // Finally delete our device object IoDeleteDevice(DriverObject->DeviceObject); } //------------------------------------------------------------------------------------------------------------------- NTSTATUS DriverOpen(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } //------------------------------------------------------------------------------------------------------------------- NTSTATUS DriverClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } //------------------------------------------------------------------------------------------------------------------- NTSTATUS DriverDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS ntStatus; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION extension; PULONG ioBuffer; ULONG ioControlCode; ULONG port = 0; Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; irpStack = IoGetCurrentIrpStackLocation(Irp); extension = DeviceObject->DeviceExtension; ioBuffer = Irp->AssociatedIrp.SystemBuffer; ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode; switch (ioControlCode) { case TEST_SMTH: ioBuffer[0] =(ULONG)DriverEntry;//В буфер обмена адрес функции DriverEntry Irp->IoStatus.Information = 4; break; default: Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; break; } ntStatus = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return ntStatus; } Немного поясню содержимое файла myDrv.c. Итак, по порядку: #define NT_DEVICE_NAME L"\\Device\\myDrv" #define DOS_DEVICE_NAME L"\\DosDevices\\myDrv"- Эти строки служат для задания символических имен текстовыми строками с именами объекта устройства, который будет создан нашим драйвером. Далее приведены прототипы используемых функций. Этим функциям можно дать произвольные имена, НО их сигнатура (состав параметров с типом возвращаемого значения) жестко заданы системой. [2]:Программная часть драйвера начинается с обязательной функции с именем DriverEntry(), которая автоматически вызывается системой на этапе загрузки драйвера. Эта функция должна содержать все действия по его инициализации. В качестве первого параметра наша функция получает указатель на объект драйвера типа PDRIVER_OBJECT. Небольшое лирическое отступление: При загрузке драйвера системы создает объект драйвера (driver object), олицетворяющий образ драйвера в памяти. С другой стороны, объект драйвера представляет собой структуру, содержащую необходимые для функционирования драйвера данные и адреса функций. В процессе инициализации драйвера (в DriverEntry()) создаются один или несколько объектов устройств (device object), олицетворяющие те устройства с которыми будет работать драйвер. Этот самый device object, необходим для правильного функционирования драйвера и создается (как и в нашем случае) даже тогда, когда драйвер не имеет отношение к какому-либо реальному устройству. Далее, в первых строках DriverEntry мы определяем используемые в ней данные, в т.ч. указатель на device object и две символьные строки UNICODE_STRING с именами устройств. Системные программы взаимодействуют с объектом устройства, созданным драйвером, посредством указателя на него. Однако для прикладных программ объект устройства представляется одним из файловых объектов и обращение к нему осуществляется по имени (в приложении мы будем использовать функцию CreateFikle()). Надо иметь в виду, что объект устройства должен иметь два имени, одно-в пространстве имен NT, другое-в пространстве имен Win32. Эти имена должны представлять собой структуры UNICODE_STRING. Имена объектов устройств составляются по определенным правилам. NT-имя предваряется префиксом \Device\, а Win32-имя -префиксом \??\ ( или \DosDevice\). При указании имен в Си-программе знак обратной косой черты удваивается. Для того чтобы указанное в программе драйвера имя можно было использовать в приложении для открытия устройства, следует создать символическую связь между обоими заданными именами устройств. Для этого используем функцию IoCreateSymbolicLink(). Следующая обязательная операция-создание объекта устройства-осуществляется вызовом функции IoCreateDevice(). Первый параметр этой функции это указатель на объект драйвера, поступающий в DriverEntry(). Второй параметр определяет размер расширения устройства-структуры, которая служит для передачи данных между функциями драйвера (состав этой структуры произволен, и полностью определяется разработчиком). Третий параметр-созданное ранее NT-имя устройства. Далее идут: тип устройства (FILE_DEVICE_UNKNOWN), специальные характеристики (0), FALSE означает, что у нас однопоточное устройство. Наконец, последний параметр является выходным-через него функция возвращает указатель на созданный объект устройства. Далее необходимо занести в объект драйвера адреса основных функций, включенных программистом в текст драйвера. Массив MajorFunction является одним из элементов структурной переменной. В этот массив мы и заносим адреса основных функций (т.е. функций которые вызываются системой автоматически в ответ на определенные действия приложения или устройства). Завершается функция оператором return с указанием кода успешного завершения. Функция DriverUnload() вызывается при выгрузке драйвера. Здесь мы должны выполнить действия по удалению объекта устройства, созданного в DriverEntry(). Функции DriverOpen и DriverClose в нашем случае ничего не делают и возвращают просто STATUSS_SUCCESS. Кстати, все эти функции тоже могут иметь произвольные имена, но передаваемые им параметры строго фиксированы. [2]:Вот мы и добрались до «самой содержательной» с точки зрения прикладного программирования функции DriverDeviceControl(). Эта функция вызывается каждый раз, когда драйверу приходит IRP-пакет с каким либо IOCTL_… кодом. Грубо говоря, IRP-пакет-это структура, передавая указатель на которую, приложение может «общаться» с драйвером (как впрочем, и драйвер может «общаться» с другим драйвером). Более подробное описание того, что такое IRP-пакет можно найти здесь http://www.lcard.ru/~gorinov/texts/irp99.html . IRP-пакет содержит так называемый системный буфер, служащий для обмена информацией (переменная SystemBuffer). Таким образом, нам надо получить доступ к IRP-пакету, а через него к SystemBuffer. Для этого объявляем переменную irpStack типа указателя на стековую область ввода-вывода PIO_STACK_LOCATION, и, кроме того, переменная ioBuffer, в которую будет помещен адрес системного буфера обмена. В нашем случае тип этой переменной-PULONG, в действительности тип передаваемых данных может быть каким угодно. С помощью функции IoGetCurrentIrpStackLocation() в переменную irpStack помещается адрес стековой области ввода-вывода, а в переменную ioBuffer заноситься адрес системного буфера из структуры IRP. Системный буфер входит в объединение (union) с именем AssociatedIrp, поэтому мы используем конструкцию Irp->AssociatedIrp.SystemBuffer. Конструкция switch-case анализирует содержимое ячейки IoControlCode и в зависимости от значения кода выполняет те или иные действия. В нашей программе только один код действия TEST_SMTH. Засылка в буфер обмена адреса функции DriverEntry() осуществляется через указатель на этот буфер. В переменную Irp->IoStatus.Information заносим количество (4) пересылаемых байт. Для завершения IRP-пакета вызываем IoCompleteRequest(). Итак, драйвер мы написали. Теперь надо его скомпилировать. Т.к. процесс компиляции идет из командной строки, то для этой цели гораздо удобнее пользоваться bat-файлом. Создадим небольшой bat-файл с именем, допустим, Crt.bat и со следующим содержанием: %SystemRoot%\system32\cmd.exe /c "cd C:\NTDDK\bin&&setenv.bat C:\NTDDK&&cd c:\myDriver \&&build -ceZ" pause Замечание: NTDDK у меня установлено в корень С:\, если у Вас по другому- то вместо C:\NTDDK\bin и C:\NTDDK пропишите полные пути к соответствующим папкам. Итак, теперь запустим наш Crt.bat. После окончания компиляции в папке C:\myDriver\objfre\i386 находим готовый драйвер myDrv.sys. Наш драйвер пока умеет только, лишь загружаться/выгружаться и по специальному запросу-посылает приложению адрес одной из своих процедур. Теперь займемся написанием приложения работающего с нашим драйвером. Еще раз напоминаю, что мы работаем под Win2000. Эта ОС позволяем реализовать динамическую загрузку/выгрузку драйвера. Примечание: Точнее динамическую загрузку/выгрузку служб (service), но т.к. в Win2000 в качестве службы можно рассматривать и драйвер, я буду использовать оба эти термина, подразумевая, в данной статье, наш драйвер. Для загрузки и выгрузки драйверов используется диспетчер управления службами SC Manager (Service Control Manager). Прежде чем вы сможете работать с интерфейсом SC, вы должны получить дескриптор диспетчера служб. Для этого необходимо обратиться к функции OpenSCManager(). Дескриптор диспетчера служб необходимо использовать при обращении к функциям CreateServise() и OpenService(). Дескрипторы, возвращаемые этими функциями необходимо использовать при обращении к вызовам, имеющим отношение к конкретной службе. К подобным вызовам относятся функции ControlService(), DeleteService() и StartService(). Для освобождения дескрипторов обоих типов используется вызов CloseServiceHandle(). Загрузка и запуск службы подразумевает выполнение следующих действий: 1. Обращение к функции OpenSCManager() для получения дескриптора диспетчера 2. Обращение к CreateServise() для того, чтобы добавить службу в систему. Если такой сервис уже существует, то CreateServise() выдаст ошибку с кодом 1073 (код ошибки можно прочитать GetLastError()) данная ошибка означает, что сервис уже существует и надо вместо CreateServise() использовать OpenService(). 3. Обращение к StartService() для того, чтобы перевести службу в состояние функционирования. 4. Если служба запустилась успешно, то можно вызвать CreateFile(), для получения дескриптора, который мы будем использовать уже непосредственно при обращении к драйверу. 5. И по окончании работы не забудьте дважды обратиться к CloseServiceHandle() для того чтобы освободить дескрипторы диспетчера и службы. Если на каком-то шаге этой последовательности возникла ошибка, нужно выполнить действия обратные тем, которые вы успели выполнить до ошибки. Надо помнить о том, что при обращении к функциям подобным CreateServise() необходимо указывать полное имя исполняемого файла службы (в нашем случае полный путь и имя myDrv.sys). Посмотрим на исходный текст простого консольного приложения, написанного на Visual C++ 6.0: #include <conio.h> #include "LoadDRV.h" #include <string.h> #include <stdio.h> void main() { LPTSTR m_name = new char[20]; strcpy(m_name, "myDrv.sys"); if (drvLoad(m_name)) TestSmth(); drvUnLoad(m_name); delete m_name; } //----------------------------------------------------------------------------- //создаем и посылаем драйверу IRP-запрос int TestSmth(void)//0x800 + 101 { int test = 0; DWORD ReturetLength = 0; DeviceIoControl(hDevice, IOCTL_TEST_SMTH, NULL, 0, &test, 4, &ReturetLength, NULL); printf("TestSmth= %i\n",test); return test; } ///**************Функции динамической загрузки************************ bool drvLoad(char* name) { printf (name); hSCManager=NULL; hService=NULL; bool status; status=FALSE; if(OpenManager()) { if(drvCreateService(name)) { if(drvStartService(name)) { status=TRUE; printf("\n Driver is now load...\n"); } } } hDevice = CreateFile ("//./myDrv", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); return status; } //--------------------------------------------------------------------- bool OpenManager() { bool status; status=FALSE; if(hSCManager!=NULL) { CloseServiceHandle (hSCManager); hSCManager=NULL; } hSCManager=OpenSCManager (NULL,NULL,SC_MANAGER_ALL_ACCESS); if (hSCManager == NULL) { error(_OpenSCManager); } else status=TRUE; return status; } //--------------------------------------------------------------------- bool drvCreateService(PCHAR pDrvName) { LPTSTR lpBuffer; lpBuffer = new char[256]; bool status = FALSE; LPTSTR awPath; // путь к драйверу с именем pDrvName // формируем путь к pDrvName, драйвер должен лежать рядом с exe-шником GetCurrentDirectory(256, lpBuffer); strcat(lpBuffer,"\\"); strcat(lpBuffer,pDrvName); awPath = lpBuffer; hService = CreateService(hSCManager,pDrvName,pDrvName,SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,SERVICE_ERROR_NORMAL,awPath,NULL,NULL,NULL,NULL,NULL); if(!hService) { error(_CreateService); status = drvOpenService(pDrvName);//Пытаемся открыть службу } else status=TRUE; delete lpBuffer; return status; } //-------------------------------------------------------------------- bool drvOpenService(PCHAR name) { bool status; status=FALSE; if(hService!=NULL) CloseService(); hService=OpenService(hSCManager,name,SERVICE_ALL_ACCESS); if (!hService) error(_OpenService); else status=TRUE; return status; } //--------------------------------------------------------------------- bool drvStartService(PCHAR name) { bool status; status=FALSE; if(!StartService(hService,0,NULL)) { error(_StartService); printf("Deleting service..."); drvDeleteService(name) } else status=TRUE; return status; } //--------------------------------------------------------------------- bool drvDeleteService(PCHAR name) { bool status; status=FALSE; CloseService(); if(!DeleteService(hService)) error(_DeleteService); else status=TRUE; return status; } //------------------------------------------------------------------- void CloseService() { CloseServiceHandle (hService); hService=NULL; } //------------------------------------------------------------------- int drvUnLoad(PCHAR name) { int status; status=FALSE; if (hDevice!=INVALID_HANDLE_VALUE) { if(!CloseHandle(hDevice)) error(_CloseHandle); hDevice=INVALID_HANDLE_VALUE; } if (hService) { status = ControlService(hService,SERVICE_CONTROL_STOP,&ServiceStatus); if(!status) error(_SERVICE_CONTROL_STOP); status = DeleteService(hService); if(!status) error(_DeleteService); status = CloseServiceHandle(hService); if(!status) error(_CloseServiceHandle); } if(!CloseServiceHandle(hSCManager)) error(_CloseServiceHandle); if (status) printf("Driver Unload... SUCCESS\n"); return status; } //--------------------------------------------------------------------- void error(error_index erIndex) { DWORD err; err=GetLastError(); switch(erIndex) { case _OpenSCManager: printf("OpenSCManager failed with Error=%i\n",err); break; case _GetFullPathName: printf("GetFullPathName failed with Error=%i\n",err); break; case _CreateService: switch (err) { case 1073: printf("The specified service already exists.\n"); printf("opening this service..."); break; default: printf("CreateService failed with Error=%i\n",err); } break; case _OpenService: printf("OpenService failed with Error=%i\n",err); break; case _StartService: printf("StartService failed with Error=%i\n",err); break; case _DeleteService: printf("DeleteService failed with Error=%i\n",err); break; case _SERVICE_CONTROL_STOP: printf("SERVICE_CONTROL_STOP failed with Error=%i\n",err); break; case _CreateFile: printf("CreateFile failed with Error=%i\n",err); break; case _CloseHandle: printf("CloseHandle failed with Error=%i\n",err); break; case _CloseServiceHandle: printf("CloseServiceHandle failed with Error=%i\n",err); break; } } И содержимое h-файла этого приложения: #define FIRST_IOCTL_INDEX 0x800 #define FILE_DEVICE_myDrv 0x00008000 #define TEST_SMTH CTL_CODE(FILE_DEVICE_myDrv, \ 0x800 + 101, \ METHOD_BUFFERED, \ FILE_ANY_ACCESS) Это приложение загружает драйвер, файл которого лежит в одной папке, что и exe-шник данного приложения. Посмотрим на исходный текст: В main(), мы создаем переменную с именем драйвера (myDrv.sys), и передаем это имя в функцию динамической загрузки драйвера drvLoad(), которая выполняет все необходимые действия по работе с менеджером служб, и в конце вызывает CreateFile(),которая возвращает дескриптор, нужный для работы с драйвером как файловым объектом. Этот дескриптор, в частности, используется при вызове функции DeviceIoControl. Если драйвер загружен успешно, то вызываем функцию TestSmth(), внутри которой мы создаем, и посылаем драйверу IRP-пакет (с помощью вызова DeviceIoControl()). Приняв этот пакет, наш драйвер возвращает адрес своей процедуры DriverEntry. После этого выгружаем драйвер. Все. |
ISAPI Filter |
Введение в WinInet Troubleshooting and Debugging MS BackOffice Unleashed Creating an ISAPI Filter |
SAFEARRAY |
// Описатель измерения SAFEARRAYBOUND rgsabound[1]; rgsabound[0].lLbound = 0; rgsabound[0].cElements = 0; WCHAR * pw = pf; while(*pw != 0) { pw+= ::wcslen(pw)+1; rgsabound[0].cElements++; } // Создаем массив с одним измерением SAFEARRAY * psa = SafeArrayCreate(VT_BSTR,1,rgsabound); if( psa == NULL ) { ::wcscpy( _ErrorMessage, _TEXT("Out of memory") ); return E_OUTOFMEMORY; } pw = pf; for( long i=0; i < rgsabound[0].cElements; i++ ) { BSTR ps = ::SysAllocString(pw); hr = SafeArrayPutElement(psa,&i,ps); if( FAILED(hr) ) return Error(_ErrorMessage); pw+= ::wcslen(pw)+1; } VARIANT R; ::VariantInit(&R); R.vt = VT_ARRAY|VT_BSTR; R.parray = psa; |