Как сделать DllExport классом С++ для использования в приложении С#
Я создал проект С++ Dll, который содержит класс "myCppClass" и попытался экспортировать Dll с помощью следующего кода, как описано в:
http://msdn.microsoft.com/en-us/library/a90k134d(v=vs.80).aspx
class __declspec(dllexport) CExampleExport : //public CObject
{ ... class definition ... };
Я опустил "public CObject", поскольку для этого требуется afx.h и подразумевает, что это MFC Dll. Я не уверен, что это хорошо или нет, но оно отличается от настроек по умолчанию проекта DLL.
Из приведенной выше документации я убежден, что все "публичные функции и переменные-члены" доступны для импорта. Как это сделать на С#? Может просто создать экземпляр класса?
Изменить: я только понял, что Название сообщения может вводить в заблуждение. Акцент должен делаться на DllImport-ing из С# и гарантировать, что я правильно выполнил документацию на С++
Ответы
Ответ 1
С# не может напрямую импортировать классы С++ (которые фактически управляются с помощью интерфейсов C).
Ваши параметры выставляют класс через COM, создавая управляемую оболочку с использованием С++/CLI или выставляя интерфейс C-стиля. Я бы рекомендовал управляемую оболочку, поскольку это проще всего и обеспечит наилучшую безопасность.
Интерфейс C-стиля будет выглядеть примерно так (предупреждение: непроверенный код):
extern "C" __declspec(dllexport)
void* CExampleExport_New(int param1, double param2)
{
return new CExampleExport(param1, param2);
}
extern "C" __declspec(dllexport)
int CExampleExport_ReadValue(void* this, int param)
{
return ((CExampleExport*)this)->ReadValue(param)
}
Оболочка типа С++/CLI будет выглядеть так (предупреждение: непроверенный код):
ref class ExampleExport
{
private:
CExampleExport* impl;
public:
ExampleExport(int param1, double param2)
{
impl = new CExampleExport(param1, param2);
}
int ReadValue(int param)
{
return impl->ReadValue(param);
}
~ExampleExport()
{
delete impl;
}
};
Ответ 2
Насколько я знаю, С# может взаимодействовать только с интерфейсами COM. К счастью, ему не нужно быть полномасштабным COM-объектом с реестром, это может быть любой простой старый класс С++, реализующий IUnknown.
Так что сделайте что-нибудь подобное в С++:
#include <Windows.h>
// Generate with from VisualStudio Tools/Create Guid menu
static const GUID IID_MyInterface =
{ 0xefbf7d84, 0x3efe, 0x41e0, { 0x95, 0x2e, 0x68, 0xa4, 0x4a, 0x3e, 0x72, 0xca } };
struct MyInterface: public IUnknown
{
// add your own functions here
// they should be virtual and __stdcall
STDMETHOD_(double, GetValue)() = 0;
STDMETHOD(ThrowError)() = 0;
};
class MyClass: public MyInterface
{
volatile long refcount_;
public:
MyClass(): refcount_(1) { }
STDMETHODIMP QueryInterface(REFIID guid, void **pObj) {
if(pObj == NULL) {
return E_POINTER;
} else if(guid == IID_IUnknown) {
*pObj = this;
AddRef();
return S_OK;
} else if(guid == IID_MyInterface) {
*pObj = this;
AddRef();
return S_OK;
} else {
// always set [out] parameter
*pObj = NULL;
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG) AddRef() {
return InterlockedIncrement(&refcount_);
}
STDMETHODIMP_(ULONG) Release() {
ULONG result = InterlockedDecrement(&refcount_);
if(result == 0) delete this;
return result;
}
STDMETHODIMP_(DOUBLE) GetValue() {
return 42.0;
}
STDMETHODIMP ThrowError() {
return E_FAIL;
}
};
extern "C" __declspec(dllexport) LPUNKNOWN WINAPI CreateInstance()
{
return new MyClass();
}
И на стороне С# вы делаете что-то вроде этого:
[ComImport]
[Guid("EFBF7D84-3EFE-41E0-952E-68A44A3E72CA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface MyInterface
{
[PreserveSig] double GetValue();
void ThrowError();
}
class Program
{
[DllImport("mylib.dll")]
static extern MyInterface CreateInstance();
static void Main(string[] args)
{
MyInterface iface = CreateInstance();
Console.WriteLine(iface.GetValue());
try { iface.ThrowError(); }
catch(Exception ex) { Console.WriteLine(ex); }
Console.ReadKey(true);
}
}
Вы можете делать почти все, что хотите, до тех пор, пока связь между С++ и С# проходит через виртуальный интерфейс.
Ответ 3
Вы не можете создать экземпляр класса С++ через pinvoke из С#. Это сложная деталь реализации, только компилятор С++ знает, сколько памяти нужно выделить, и когда и как правильно вызвать конструктор и деструктор. Размер объекта, безусловно, самый твердый орех для взлома, нет никакого способа сделать это надежным.
Если вы не можете сгладить класс С++ в статические методы, вам нужно написать управляемую оболочку. Это сделано с языком С++/CLI, вы должны написать "класс ref", который имеет неуправляемый объект класса, сохраненный как указатель, созданный в конструкторе и удаленный в деструкторе и финализаторе.
Ответ 4
Собственно, вы можете напрямую ссылаться на искаженные имена, используя свойство EntryPoint DllImport. Подробнее см. этот ответ.
Ответ 5
С# и С++ не совместимы с ABI, такими как С++ и Delphi, поэтому вы не можете экспортировать члены виртуального класса (методы) и объявлять их чисто виртуальными на вызывающей стороне, вызывать их, потому что С# не может обрабатывать vtbl объектов С++.
Я бы предложил вам объединить ваши классы С++ с помощью COM, так что у вас есть еще один положительный побочный эффект, что другие совместимые с COM языки могут также использовать ваши классы.