Импорт COM из С++ в VB.NET

Язык Visual Basic на платформе .NET.

Модераторы: Ramzes, Sebas

Heimdall
Начинающий
Начинающий
 
Сообщения: 11
Зарегистрирован: 12.11.2006 (Вс) 7:09

Импорт COM из С++ в VB.NET

Сообщение Heimdall » 06.01.2007 (Сб) 9:46

Создаю COM-обьект в C++, регистрирую его, пробую импортировать в VB - пишет что библиотека не содержит COM-обьектов. При всем при этом приложение на C++ успешно вызывает этот COM через CoCreateInstance и получает доступ к его методам.

Поиск по форуму ничего не дал.

В мануалу по COM упоминалось что COM- обьект под VB требует более строгого оформления чем обьект под C++. Но ни в одном мануале конкретно как нужно оформить обьект в С++ чтобы VB его приняло, не было указано. Все мануалы которые мне попадались содержали описуху как импортировать COM написаные в том же языке и нигде не были указаны кросязыковые требования к COM. Кто имеет под рукой мануал с ответами на данный вопрос - киньте им в меня.. ну или на крайняк ссылкой где его можно раздобыть.

Заранее спасибо

Viper
Артефакт VBStreets
Артефакт VBStreets
Аватара пользователя
 
Сообщения: 4394
Зарегистрирован: 12.04.2005 (Вт) 17:50
Откуда: Н.Новгород

Сообщение Viper » 06.01.2007 (Сб) 10:13

О Com-объектах в VB.NET Эппельман писал, ищи его книгу "Переход на VB.NET ..."
Весь мир матрица, а мы в нем потоки байтов!

Heimdall
Начинающий
Начинающий
 
Сообщения: 11
Зарегистрирован: 12.11.2006 (Вс) 7:09

Сообщение Heimdall » 06.01.2007 (Сб) 10:51

Книга есть. Просмотрел. Узнал очень много нового про COM обьекты в VB, как их создавать в VB и использовать в VB...

Но ведь проблема не в этом. Проблема в том как их создать в С++ чтобы VB их воспринимал нормально.

Поспрашивал на форумах С++, там мне почти хором все сказали что вопросы экспорта их библиотек в какой-то там VB их мало интересует и нуно интересоваться у VBшников. Что я и пытаюсь сделать

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 06.01.2007 (Сб) 11:15

COM-объект, нормально созданный на C++, не требует ничего дополнительного к себе. В нём уже всё есть и VB его сразу видит. Как создаёшь?
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

Heimdall
Начинающий
Начинающий
 
Сообщения: 11
Зарегистрирован: 12.11.2006 (Вс) 7:09

Сообщение Heimdall » 08.01.2007 (Пн) 2:25

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

*.cpp файл
Код: Выделить всё
#include "stdafx.h"   //служебные объявления
#include "NeoIntr4.h" //объявления интерфейсов
#include <tchar.h>    //используется ради макроса _T("str")

#include <basetyps.h>
#include <wtypes.h>
#include <olectl.h>   //только ради SELFREG_E_CLASS

#include <wincrypt.h>
#include <Cspdk.h>

//счётчик ссылок всего сервера
DWORD  dwSrvRefCnt;

//дескриптор нашего модуля, получается при старте DLL и требуется для
//выяснения полного пути к нему
HINSTANCE hOwnModule;

HANDLE g_hHeap;

//******************************************************************************
//реализация необязательной экспортируемой функции сервера DllRegisterServer
//функция должна зарегистрировать в системном реестре минимум информации
//достаточной для того, чтобы слой COM операционной системы смог разыскать нас
//и организовать "подъём" любого из наших объектов
//минимум этой информации такой:
//HKEY_CLASSES_ROOT\CLSID\<GUID нашего объекта>
//HKEY_CLASSES_ROOT\CLSID\<GUID нашего объекта>\InprocServer32 = <модуль>
//делать это мы будем "старыми добрыми функциями" Reg???Key???
STDAPI DllRegisterServer()
{
   //получить полный путь к данному модулю
   TCHAR FileName[256];
   
   DWORD len = ::GetModuleFileName(hOwnModule,&FileName[0],255);
   
   //создать (открыть) параметр
   //HKEY_CLASSES_ROOT\CLSID\{34344FFD-0521-47d4-8938-CE6F46D1FDFA}
   //это GUID статического типа PBKI2CP
   HKEY hPBKI2CP;
   
   if(::RegCreateKey(HKEY_CLASSES_ROOT, _T("CLSID\\{34344FFD-0521-47d4-8938-CE6F46D1FDFA}"), &hPBKI2CP) != ERROR_SUCCESS) return SELFREG_E_CLASS; //не все типы корректно зарегистрированы
   
   //создать (под)параметр текущего параметра с именем InprocServer32
   //и установить его значение на полный путь сервера
   if(::RegSetValue(hPBKI2CP, _T("InprocServer32"), REG_SZ, &FileName[0], len) !=ERROR_SUCCESS) return SELFREG_E_CLASS; //не все типы корректно зарегистрированы

   //создать (под)параметр текущего параметра с пустым именем - это
   //значение по умолчанию и оно может быть любым...
   if(::RegSetValue(hPBKI2CP, NULL, REG_SZ, _T("PBKI2CP - COM обьект для доступа к CryptoPro"), len) !=ERROR_SUCCESS) return SELFREG_E_CLASS; //не все типы корректно зарегистрированы

   //закрыть ненужный ключ
   ::RegCloseKey(hPBKI2CP);

   return S_OK;  //работа функции успешно завершена...
}

//******************************************************************************
//реализация необязательной экспортируемой функции сервера DllUnregisterServer
//функция должна вычистить из системного реестра информацию о регистрации
//внесённую туда функцией DllRegisterServer
STDAPI DllUnregisterServer()
{
   //удалить параметр {34344FFD-0521-47d4-8938-CE6F46D1FDFA}\InprocServer32
   ::RegDeleteKey(HKEY_CLASSES_ROOT, _T("CLSID\\{34344FFD-0521-47d4-8938-CE6F46D1FDFA}\\InprocServer32"));
   
   //удалить сам параметр {34344FFD-0521-47d4-8938-CE6F46D1FDFA}
   ::RegDeleteKey(HKEY_CLASSES_ROOT, _T("CLSID\\{34344FFD-0521-47d4-8938-CE6F46D1FDFA}"));

   return S_OK;
}

//******************************************************************************
//реализация необязательной экспортируемой функции сервера DllCanUnloadNow
//функция должна сообщить равен ли нулю счётчик ссылок сервера - и все...
STDAPI DllCanUnloadNow()
{
   if(dwSrvRefCnt == 0 )return S_OK;   //можно выгружать
   else return S_FALSE;                //надо подождать с выгрузкой
}

//******************************************************************************
//реализация объектов сервера.
//******************************************************************************

//объявление статического типа PBKI2CP
class PBKI2CP: public PBKIInterface
{
private:
   ULONG     m_dwRefCnt;           //счетчик ссылок

public:
   PBKI2CP(){m_dwRefCnt = 0;}      //инициализация...

public:
  //экспонируемые методы
   STDMETHOD(QueryInterface)(REFIID riid,void ** ppvObject);
   STDMETHOD_(ULONG,AddRef)();
   STDMETHOD_(ULONG,Release)();

   int STDMETHODCALLTYPE Version();
};

//******************************************************************************
//объявление статического типа "фабрика класса".
//******************************************************************************

class PBKI2CPClassFactory: public IClassFactory{
private:
   ULONG     m_dwRefCnt;           //счетчик ссылок

public:
   PBKI2CPClassFactory(){m_dwRefCnt = 0;}//инициализация...

public:
  //экспонируемые методы
   STDMETHOD(QueryInterface)(REFIID riid,void ** ppvObject);
   STDMETHOD_(ULONG,AddRef)();
   STDMETHOD_(ULONG,Release)();
   STDMETHOD(CreateInstance)(IUnknown *pUnkOuter,REFIID riid, void **ppvObject);
   STDMETHOD(LockServer)(BOOL fLock);
};

//******************************************************************************
//реализация методов статического типа PBKI2CP
//******************************************************************************
//метод QueryInterface. Назначение метода - выдать указатель на любой
//интерфейс, реализуемый объектом или NULL в противном случае. Наш объект
//реализует два интерфейса - IUnknown и NeoInterface
STDMETHODIMP PBKI2CP::QueryInterface(REFIID riid,void ** ppvObject)
{
     //реализация QueryInterface крайне проста - мы должны разбираться
     //какой IID поступил на вход и преобразовывать указатель this к этому
     //абстрактному типу...

     *ppvObject = NULL;
    
     if(::memcmp(&riid,&IID_IUnknown,sizeof(GUID)) == 0) *ppvObject = static_cast<IUnknown *>(this);

     else if(::memcmp(&riid,&IID_PBKIInterface,sizeof(GUID)) == 0) *ppvObject = static_cast<PBKIInterface *>(this);
    
     //а больше мы никаких интерфейсов не реализуем. Код возврата E_NOINTERFACE
     //и есть тот код, который сообщает, что запрашиваемого интерфейса нет.
     else return E_NOINTERFACE;

     AddRef(); //счётчик нужно продвинуть - ссылка же размножилась

     return S_OK;  //нормальное успешное завершение
}

//******************************************************************************
//метод AddRef - просто продвигает вперед счётчик ссылок и возвращает его
//продвинутое значение
STDMETHODIMP_(ULONG) PBKI2CP::AddRef()
{
   m_dwRefCnt++;     //счётчик ссылок объекта
   dwSrvRefCnt++;    //счётчик ссылок сервера

   return m_dwRefCnt;
}

//******************************************************************************
//метод Release - просто продвигает назад счётчик ссылок и возвращает его
//продвинутое значение. Метод НЕ УДАЛЯЕТ объект, поскольку PBKI2CP у нас -
//статический объект уровня модуля, а не динамически получаемый по операции new.
//поэтому и аппарат подсчета ссылок в данном объекте - фактически не нужен.
//но клиент не знает, как реализован объект внутри сервера и клиент всё равно
//будет соблюдать протокол взаимодействия...
STDMETHODIMP_(ULONG) PBKI2CP::Release()
{
   m_dwRefCnt--;     //счётчик ссылок объекта
   dwSrvRefCnt--;    //счётчик ссылок сервера

   return m_dwRefCnt;
}

//******************************************************************************
//метод Show. Показывает сообщение на экране - "БАНЗАЙ". Параметр - дескриптор
//того окна, кому должно принадлежать показываемое методом сообщение
int STDMETHODCALLTYPE PBKI2CP::Version()
{
   return 5;
}

//******************************************************************************
//реализация объекта PBKI2CP в составе сервера
PBKI2CP oPBKI2CP;


//******************************************************************************
//реализация методов статического типа PBKI2CPClassFactory
//******************************************************************************
//метод QueryInterface. Назначение метода - выдать указатель на любой
//интерфейс, реализуемый объектом или NULL в противном случае. Наш объект
//реализует два интерфейса - IUnknown и NeoInterface
STDMETHODIMP PBKI2CPClassFactory::QueryInterface(REFIID riid,void ** ppvObject)
{
     //реализация QueryInterface крайне проста - мы должны разбираться
     //какой IID поступил на вход и преобразовывать указатель this к этому
     //абстрактному типу...
     *ppvObject = NULL;
    
     if(::memcmp(&riid,&IID_IUnknown,sizeof(GUID)) == 0) *ppvObject = static_cast<IUnknown *>(this);

     else if(::memcmp(&riid,&IID_IClassFactory,sizeof(GUID)) == 0) *ppvObject = static_cast<IClassFactory *>(this);
    
     //а больше мы никаких интерфейсов не реализуем. Код возврата E_NOINTERFACE
     //и есть тот код, который сообщает, что запрашиваемого интерфейса нет.
     else return E_NOINTERFACE;

     AddRef(); //счётчик нужно продвинуть - ссылка же размножилась

     return S_OK;  //нормальное успешное завершение
}

//******************************************************************************
//метод AddRef - просто продвигает вперед счётчик ссылок и возвращает его
//продвинутое значение
STDMETHODIMP_(ULONG) PBKI2CPClassFactory::AddRef()
{
   m_dwRefCnt++;     //счётчик ссылок объекта
   dwSrvRefCnt++;    //счётчик ссылок сервера
   
   return m_dwRefCnt;
}

//******************************************************************************
//метод Release
STDMETHODIMP_(ULONG) PBKI2CPClassFactory::Release()
{
   ULONG i = --m_dwRefCnt;
   
   dwSrvRefCnt--;    //счётчик ссылок сервера, можно декрементировать так
                      //поскольку сервер сам себя не выгружает

   if(i == 0) delete this;

   return i;
}

//******************************************************************************
//Основной метод интерфейса IClassFactory - CreateInstance. Его реализация по
//крайней мере понятна - в прошлом примере это была реализация тела процедуры
//DllGetClassObject
STDMETHODIMP PBKI2CPClassFactory::CreateInstance(IUnknown * pUnkOuter, REFIID riid, void ** ppvObject)
{
   //обнулить возвращаемый указатель на тот случай, если вызов
   //закончится неудачно
   *ppvObject = NULL;

   //нам не нужно проверять, что за тип - это уже выяснила процедура
   //DllGetClassObject. Здесь мы просто должны создать экземпляр объекта
   //и разобраться с тем, какой интерфейс этого объекта вернуть

   //нас могут попросить агрегироваться, а этого мы как раз и не умеем
   //поэтому не будем вводить клиента в заблуждение
   if(pUnkOuter != NULL) return CLASS_E_NOAGGREGATION;

   //здесь мы должны _создать_экземпляр_. И именно здесь находится та точка
   //в которой принимается решение - делать при всяком запросе новый экземпляр
   //или всем возвращать одну и ту же ссылку. Мне захотелось (у нас - исключительно
   //демонстрационная программа, не обязательно, что в реалььных программах вы
   //так и будете делать, но - технология позволяет), чтобы у "статического типа"
   //была "нестатическая фабрика класса", которая всем возвращает один
   //и тот же адрес, статического, по-сути, объекта программы. Это - экзотика,
   //так - практически никогда не требуется делать в реальных программах.

   return oPBKI2CP.QueryInterface(riid,ppvObject);
}

//******************************************************************************
//Метод "оставления сервера в живых". Реализуется сверхпросто - мы просто должны
//продвигать счётчик блокировок сервера в нужную сторону
STDMETHODIMP PBKI2CPClassFactory::LockServer(BOOL fLock)
{
   if(fLock != 0) dwSrvRefCnt++;
   else   dwSrvRefCnt--;
   
   return S_OK;
}


//******************************************************************************
//реализация собственно модуля сервера
//******************************************************************************

//стандартная main для DLL. В этом примере она используется - при старте
//DLL мы создаём свою кучу памяти, а при завершении - уничтожаем
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
   switch (ul_reason_for_call)
   {
      case DLL_PROCESS_ATTACH:
         g_hHeap = ::HeapCreate(0,0,0);
         if(g_hHeap == NULL) return FALSE; //завершимся аварийно...

         hOwnModule = (HINSTANCE)hModule;
         dwSrvRefCnt = 0; //начальная инициализация
      break;

      case DLL_THREAD_ATTACH:

      case DLL_THREAD_DETACH:
        break;

      case DLL_PROCESS_DETACH:
         ::HeapDestroy(g_hHeap); //а тут уже всё равно - и так завершаемся
      break;
    }
    return TRUE;
}


//******************************************************************************
//реализация экспортируемой функции DLL - DllGetClassObject
//Это - та самая функция, которая создаёт объекты, идентифицируемые со стороны
//клиента парой CLSID-IID и возвращает клиенту адрес этого объекта. В общем
//случае для всех статических типов, реализуемых сервером она должна уметь
//опознавать CLSID, проверять, реализует ли данный CLSID запрашиваемый IID
//и создавать ссылку на такой объект "внутри сервера".
//И в ней - ничего менять не нужно. Нужно только понимать, что если в прежних
//примерах эта функция возвращала ссылку на "экземпляр объекта", то в данном она
//возвращает ссылку на "элементарный COM-объект статических аспектов типа", проще
//говоря - на фабрику класса. А уж фабрика класса делает экземпляр...
STDAPI DllGetClassObject(const GUID& Guid, const GUID& Iid, void ** ppv)
{
   //правило хорошего тона - обнулить возвращаемый указатель на тот
   //случай, если вызов закончится неудачно. Тогда клиент не будет введён
   //в заблуждение относительно того, получил ли он что-то осмысленное
   *ppv = NULL;

   //реализация создания фабрики класса объекта статического типа.
   //реализация создания объекта статического типа Hello
   if(::memcmp(&Guid,&CLSID_PBKI2CP,sizeof(GUID)) == 0)
   {
      //в данном случае объект создаётся в динамической памяти по
      //запросу - до запроса он не существует. Помните - сейчас, как и ранее
      //мы создаём "элементарный COM-объект". Только ранее он представлял
      //собой "объект экземпляра", а сечас это - "объект типа". И поддерживает
      //он только два интерфейса: IUnknown и IClassFactory

      PBKI2CPClassFactory *p = new PBKI2CPClassFactory();

      if(p == NULL) return E_OUTOFMEMORY; //ну а что еще можно сделать?

      HRESULT hr = p->QueryInterface(Iid,ppv);

      if(hr == E_NOINTERFACE) delete p;

      return hr;
   }

   //а больше никаких CLSID мы не реализуем
   else return CLASS_E_CLASSNOTAVAILABLE;
   //вызов был успешен и в ppv - адрес запрашиваемого "объекта типа"
}


*.h файл
Код: Выделить всё
#include <unknwn.h>

//GUID cтатического типа PBKI2CP {34344FFD-0521-47d4-8938-CE6F46D1FDFA}
static const GUID CLSID_PBKI2CP = {0x34344ffd, 0x521, 0x47d4, 0x89, 0x38, 0xce, 0x6f, 0x46, 0xd1, 0xfd, 0xfa};

//GUID интерфейса  {34344FFD-0522-47d4-8938-CE6F46D1FDFA}
static const GUID IID_PBKIInterface = {0x34344ffd, 0x522, 0x47d4, 0x89, 0x38, 0xce, 0x6f, 0x46, 0xd1, 0xfd, 0xfa};


//определение интерфейса
class PBKIInterface : public IUnknown
{
public:
   virtual int STDMETHODCALLTYPE Version() = 0;
};


библиотека компилится успешно, регится через RegSvr32, в приложениях С++ норм создается экземпляр и тестовый метод успешно срабатывает. В VB пишет что в данной библиотеке нет COM обьекта либо COM обьект некоректен


Вернуться в Visual Basic .NET

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 116

    TopList