Bryan Wilkins
Bryan Wilkins

Reputation: 169

passing a HANDLE variable to an unmanaged .dll in C++/CLI

I am trying to wrap an unmanaged c++ dll that talks to a video capture card in c++/CLI so i can reference the functions from a c# project that i have. I am having trouble getting the 1st wrapped call to work as I am new to c++/cli syntax. here is what i have.

here is the function declataion i am trying to wrap.

__declspec(dllimport) BOOL AZ_DeviceCreate(HANDLE& hLiveEvent, DWORD* hEncoderEvent, DWORD* pdwEncoderAddress, HANDLE& hAudioEvent, DWORD& dwAudioAddress);

here is my c++/cli .h file

namespace CaptureLibrary 
{
    public ref class CaptureCard
    {
    public:
        HANDLE m_hLiveEvent;
        DWORD *m_hEncoderEvent;
        HANDLE m_hAudioEvent;

    public:
        CaptureCard();
        bool CreateDevice();
        void DisposeDevice();
    };
}

and my .cpp

namespace CaptureLibrary
{
    CaptureCard::CaptureCard()
    {
        m_hLiveEvent = INVALID_HANDLE_VALUE;

        m_hEncoderEvent = new DWORD[MAX_VIDEO_CHANNEL];
        for (BYTE i=0;i<MAX_VIDEO_CHANNEL;i++)
        {
            m_hEncoderEvent[i] = (DWORD)INVALID_HANDLE_VALUE;
        }

        m_hAudioEvent = INVALID_HANDLE_VALUE;
    }

    bool CaptureCard::CreateDevice()
    {
        DWORD dwEncoderBuff[MAX_VIDEO_CHANNEL];
        DWORD dwACaptureBuffer = 0;

        if(AZ_DeviceCreate(m_hLiveEvent, m_hEncoderEvent, dwEncoderBuff, m_hAudioEvent, dwACaptureBuffer)==FALSE)
        {
            return false;
        }

        return true;
    }

    void CaptureCard::DisposeDevice()
    {
        AZ_DeviceClose();
    }
}

when i compile this with the required headers, i get this error:

error C2664: 'AZ_DeviceCreate' : cannot convert parameter 1 from 'HANDLE' to 'HANDLE &'

Can anyone help me as I know this is a stupid syntax thing that I am doing wrong.

Thanks in advance.

Upvotes: 1

Views: 1176

Answers (2)

Matt Smith
Matt Smith

Reputation: 17434

The problem here is that you are trying to pass m_hLiveHandle as a reference (i.e. HANDLE &), but this would require that m_hLiveHandle could be pointed to by a native pointer (i.e. it would be guaranteed not to move in memory). However, m_hLiveHandle is a member of a ref class (CaptureCard) which means instances of it are stored on the managed heap. This in turn means that the instance of CaptureCard can be moved in memory (by a garbage collection action). So, if you want to use m_hLiveHandle as a pointer parameter or reference parameter, you'd have to use pin_ptr to tell the garbage collector not to move this object during the duration of the call to the native method. Read up here for more: http://msdn.microsoft.com/en-us/library/1dz8byfh(v=vs.80).aspx

Upvotes: 0

ildjarn
ildjarn

Reputation: 62975

I mean this constructively: you're off on the wrong foot. Your goal with C++/CLI here is to wrap the unmanaged library in a manner that won't seem foreign in .NET, but your CaptureCard class doesn't do that.

  • Don't expose fields, expose properties (I assume they should be get-only for CaptureCard's members)
  • Don't expose raw pointer types (e.g. HANDLE), expose IntPtr
  • Don't expose raw C-arrays (e.g. DWORD*), expose array<T>^, ReadOnlyCollection<T>^, or IEnumerable<T>^ (but don't expose array<T>^s intended to be read-only via properties, only via methods + Array::Copy)
  • Don't only expose a DisposeDevice method, also make the class actually implement IDisposable so the device can be closed with a using statement rather than forcing use of try..finally
  • As the class controls unmanaged resources, it needs a finalizer

.h:

namespace CaptureLibrary 
{
    public ref class CaptureCard sealed
    {
    public:
        CaptureCard();
        ~CaptureCard();
        !CaptureCard();

        property IntPtr LiveEvent { IntPtr get(); }
        property IEnumerable<DWORD>^ EncoderEvent { IEnumerable<DWORD>^ get(); }
        property IntPtr AudioEvent { IntPtr get(); }

        bool CreateDevice();
        void DisposeDevice();

    private:
        bool m_bOpened;
        IntPtr m_hLiveEvent;
        array<DWORD>^ m_hEncoderEvent;
        IntPtr m_hAudioEvent;
    };
}

.cpp:

namespace CaptureLibrary
{
    CaptureCard::CaptureCard()
      : m_hLiveEvent(INVALID_HANDLE_VALUE),
        m_hEncoderEvent(gcnew array<DWORD>(MAX_VIDEO_CHANNEL)),
        m_hAudioEvent(INVALID_HANDLE_VALUE)
    {
        for (int i = 0, i_max = m_hEncoderEvent->Length; i != i_max; ++i)
            m_hEncoderEvent[i] = reinterpret_cast<DWORD>(INVALID_HANDLE_VALUE);
    }

    CaptureCard::~CaptureCard()
    {
        this->!CaptureCard();
    }

    CaptureCard::!CaptureCard()
    {
        DisposeDevice();
    }

    IntPtr CaptureCard::LiveEvent::get()
    {
        return m_hLiveEvent;
    }

    IEnumerable<DWORD>^ CaptureCard::EncoderEvent::get()
    {
        return m_hEncoderEvent;
    }

    IntPtr CaptureCard::AudioEvent::get()
    {
        return m_hAudioEvent;
    }

    bool CaptureCard::CreateDevice()
    {
        DisposeDevice();

        DWORD dwAudioAddress = 0u;
        DWORD dwEncoderAddress[MAX_VIDEO_CHANNEL];

        HANDLE hLiveEvent = m_hLiveEvent.ToPointer();
        HANDLE hAudioEvent = m_hAudioEvent.ToPointer();
        {
            pin_ptr<DWORD> hEncoderEvent = &m_hEncoderEvent[0];
            m_bOpened = AZ_DeviceCreate(hLiveEvent, hEncoderEvent, dwEncoderAddress, hAudioEvent, dwAudioAddress) == TRUE;
        }
        m_hLiveEvent = IntPtr(hLiveEvent);
        m_hAudioEvent = IntPtr(hAudioEvent);

        return m_bOpened;
    }

    void CaptureCard::DisposeDevice()
    {
        if (m_bOpened)
        {
            AZ_DeviceClose();
            m_bOpened = false;
        }
    }
}

Suggestions for further improvement:

  • Get rid of CreateDevice and DisposeDevice altogether. This code has a very C-ish mentality; .NET users would expect a constructed object to have a meaningful value without calling a separate initialization function, so assuming AZ_DeviceCreate is not expected to fail regularly then CreateDevice's logic should go straight in the class' constructor and an exception should be thrown upon failure
  • If calling AZ_DeviceClose multiple times is harmless then get rid of m_bOpened altogether

Upvotes: 1

Related Questions