Managed код в DLL

Все, что вы хотели знать о программизме, но боялись спросить.
Ответить
vg
Маньяк
Сообщения: 2803
Зарегистрирован: 29 май 2003, 22:29
Откуда: Магадан - Миссиссага

Managed код в DLL

Сообщение vg »

Я «немного» в шоке, после того, как попробовал создать DLL с управляемым кодом (.NET) для обычных приложений с «неуправляемым» кодом.
То, что для простых задач это работает легко (unmanaged в managed и наоборот) не обсуждается. Статьи есть на MSDN, да и сам делал - «Hello world» легко печатается из экспортируемой функции DLL с управляемым кодом. Но вот, когда я попросил NET использовать соглашение __stdcall в экпортируемых функциях, то компилятор выдал предупреждение, что моя просьба проигнорирована - __stdcall заменён модификатором __clrcall. Это может быть важным ( возможно не только для меня ) , если потребуется создать классы-обёртки для использования в неуправляемом коде управляемого кода DLL.

Например, есть декларация «интерфейсов» неких объектов, пусть, для работы с СУБД. Эти объекты расположены в DLL. Спецификация методов интерфейсов объектов, положим, в файле publicdbinterface.h:

Код: Выделить всё

// file publicdbinterface.h
struct IUnknownInterface
{
		virtual	HRESULT	__stdcall	QueryInterface (LPCTSTR int_type, void **ppv) = 0;
		virtual	void	__stdcall	CanErrorMessage(const int can_msg) = 0;
		virtual	void	__stdcall	ErrorMessage(LPTSTR* outval ) = 0;
		…
		…
};

// basic database interface definition
struct IDatabaseInterface : public IUnknownInterface
{
		virtual HRESULT __stdcall	Open  (void) = 0;
		virtual void    __stdcall	Delete(void) = 0;
		virtual void    __stdcall	Close (void) = 0;
		virtual void   __stdcall		Connection(LPCTSTR ConnectionStr) = 0;
		virtual HRESULT __stdcall	StartTransaction(void) = 0;
		virtual HRESULT __stdcall	InTransaction(void) = 0;
		virtual void    __stdcall	Commit  (void) = 0;
		virtual void    __stdcall	Rollback(void) = 0;
		virtual void    __stdcall	TransIsolation(LPCTSTR type) = 0;
		…
		…
};
typedef HRESULT (__stdcall *pCreateDatabaseInstance) (IDatabaseInterface ** pInterface, LPCTSTR inst_type);
// end of cut
Положим, в DLL выполнен экспорт только одной функции CreateDatabaseInstance, которая при создании DLL была объявлена, как:

Код: Выделить всё

#define DB_API	extern "C"	__declspec(dllexport)
DB_API	HRESULT	 __stdcall	CreateDatabaseInstance (IDatabaseInterface ** pInterface, LPCTSTR inst_type);
И имеет в простейшем случае реализацию, вроде:

Код: Выделить всё

HRESULT __stdcall CreateDatabaseInstance(IDatabaseInterface ** pInterface, LPCTSTR inst_type)
{ 
	try {
	        if( !_tcsicmp(inst_type, _TEXT("ODBC.MFC")) )
		        *pInterface = (IDatabaseInterface *) new CI_ODBCdb();
		else if( !_tcsicmp(inst_type, _TEXT("ADO.MFC")) )
			*pInterface = (IDatabaseInterface *) new CI_ADOdb();
	        else
		        *pInterface = NULL;
		}
	catch(...)
		{
                                *pInterface = NULL;
		return E_FAIL;
		}
	return ( *pInterface )? S_OK : E_FAIL;
}
Всё, что выше – это для «неуправляемого» кода (приложения и DLL). Например, программист здесь может выполнить следующие обычные вызовы типа:

Код: Выделить всё

HINSTANCE hlib;
pCreateDatabaseInstance         CreateDatabaseInstance;

void _tmain( void )
{
        hlib = LoadLibrary(_TEXT("mydb.dll"));
        if (!hlib)
        {
                _tprintf(_TEXT("Can't load library"));
                return;
        }
        CreateDatabaseInstance = (pCreateDatabaseInstance)
                                GetProcAddress(hlib,_TEXT "CreateDatabaseInstance"));
        if (!CreateDatabaseInstance )
        {
                _tprintf(_TEXT("Function CreateDatabaseInstance not found\n"));
                FreeLibrary(hlib);
                return;
        }
		
	IDatabaseInterface*  IDatabase;

	if( FAILED( CreateDatabaseInstance(&IDatabase, _TEXT("BDE") )) )
	{
               _tprintf(_TEXT("...Can't create BDE database instance\n"));
                FreeLibrary(hlib);
                return;
 	}
        
	IDatabase->Connection( _TEXT("ExampleDB") );

        if( FAILED( IDatabase->StartTransaction() ) )
        {
               IQuery->Delete();
               IDatabase->Delete();
               FreeLibrary(hlib);
               _tprintf(_TEXT("Can't open transaction\n"));
               return;
        }
…
…
…
}
В этом коде консольного приложения и коде DLL нет ничего, что касалось бы managed. Но, положим, захотелось сделать так, чтобы «обернуть» классы ADO.NET для работы с СУБД и «расширить» указанную выше DLL. Если попытаться пойти проторенной дорожной (как для обычных DLL), и попробовать создать в managed-DLL класс, типа того, что ниже – это будет крах:

Код: Выделить всё

// msadonet.h

#include "publicdbinterface.h"

#using <System.dll>
#using <System.Data.dll>

using namespace System;
using namespace System::Data;
using namespace System::Data::OleDb;

#define	DB_API	extern "C"	__declspec(dllexport)

namespace msadonet
{

DB_API	HRESULT		__stdcall	CreateDatabaseInstance	(IDatabaseInterface ** pInterface, LPCTSTR inst_type);

class CI_ADONETdb: public IDatabaseInterface
{
private:
		OleDbConnection*	Database;
		…
		…
public:
		CI_ADONETdb(); 
                                ~CI_ADONETdb();

		// IDatabaseInterface 
		HRESULT	__stdcall	Open( void );
       		void	__stdcall	Delete( void );
		…
		…

		// IUnknownInterface
		HRESULT	__stdcall	QueryInterface(LPCTSTR int_type, void **ppv);
                                 void	__stdcall	CanErrorMessage(const int can_msg);
		void	__stdcall	ErrorMessage(LPTSTR* outval);
		…
		…

};
}
// end file msadonet.h
Это не компилируется, поскольку нельзя в неуправляемом коде (класс CI_ADONETdb, см. выше) использовать ссылки на объекты классов, которые работают только под CLR (указатель OleDbConnection* Database, см. выше).

Положение можно «подправить», сделав код класса CI_ADONETdb управляемым:

Код: Выделить всё

public __gc class  CI_ADONETdb: public IDatabaseInterface
{
private:
		OleDbConnection*	Database;
		…
		…
};
Но при этом «просто так» снова не получится. Компилятор выдаст ошибку – нельзя managed классы наследовать от unmanaged. Поэтому придётся переписать и интерфейсы IDatabaseInterface и IUnknownInterface, чтобы сделать их managed тоже.
Т.е. то, что было написано выше относительно «чисто абстрактных» классов IDatabaseInterface и IUnknownInterface теперь придётся изменить на следующее:

Код: Выделить всё

public __gc class  IUnknownInterface
{
		virtual	HRESULT	__stdcall	QueryInterface(LPCTSTR int_type, void **ppv) 
		…
		…
};

public __gc class  IDatabaseInterface : public IUnknownInterface
{
		virtual HRESULT __stdcall	Open  (void) = 0;
		…
		…
};
Результат таков – копилируется. При этом компилятор сообщает о предупреждении - warning C4440: calling convention redefinition from '__clrcall ' to '__stdcall ' ignored.
То, что компилируется – не большое утешение. Я уже не могу использовать эту DLL в своих проектах из–за ignored '__clrcall ' to '__stdcall '. Несмотря на то, что DLL компилируется, даже пробовать использовать её не стоит.

Как мне быть? Как «обернуть» объекты типа OleDbConnection так, чтобы сохранить соглашения __stdcall, указанные в начале моего поста?
vg
Маньяк
Сообщения: 2803
Зарегистрирован: 29 май 2003, 22:29
Откуда: Магадан - Миссиссага

Сообщение vg »

PS. Как ни пытался подправить форматирование - не получается. Экскьюз ми.
Ответить