C++/CLI wrapper dla biblioteki C++ do użytku w C# - Access Violation

0

Piszę bibliotekę w C++/CLI, żeby spiąć ze sobą dwa światy. W założeniu ma ona mieć minimalną ilość kodu, aby zbędnie nie generować błędów, dlatego zdecydowałem się na odwzorowanie klas 1:1. Gdy wywołam ConnectToServer aplikacja działa jeszcze przez dwie sekundy, potem zawiesza się i po około minucie otrzymuję Exception:

Exception thrown at 0x00000000 in ClrWrapperTest.exe: 0xC0000005: Access violation executing location 0x00000000.

Po śladach wywnioskowałem, że wywala się po jednym cyklu próby połączenia z serwerem. Możliwe, że coś jest z tym SDK (napisałem do autorów - czekam na odpowiedź), ale mam przykładową aplikację w czystym C++ gdzie wykonanie tych samych operacji działa. Co ważne brak połączenia z serwerm nie zawiesza tam aplikacji, więc prawdopodobnie powinno to działać w innym wątku. Mi to wygląda na problem z utworzeniem wątku do zestawiania połączeń w kodzie natywnym. Zastanawiam się czy gdzieś nie popełniłem błędu, albo jest jakiś mankament o którym nie wiem. Nie mam tutaj żadnego speca od C++, a sam to Kali jeść, Kali pić. Będę wdzięczny za wszelkie sugestie. Poniżej zamieszczam kod:

Nagłówek klasy natywnej (okroiłem z nieistotnych metod):

class ICmsServer : public IUnknown
{
public:
	// connection type
	enum CONNECTION_TYPE { 
		TYPE_1 = 1U<<1, 
		TYPE_2 = 1U<<2, 
		TYPE_3 = 1U<<3 };

	// connects to remote server, 
	// client auto reconnects after an error.
	virtual void ConnectToServer( 
			LPCOLESTR szAddress, int nPort, 
			LPCOLESTR szLogin, 
			LPCOLESTR szPassword,
			int nConnectionType = TYPE_1,
			BOOL bArchBrowser = FALSE ) = 0;

	// disconnect
	virtual void DisconnectServer() = 0;

	// current connection status
	// it is always DISCONNECTED after DisconnectServer(),
	// but it can be any value after ConnectToServer() - Example:
	// If client can't connect to a server status will be like this:
	// CONNECTING,ERROR_OCURRED,DISCONNECTED, .... CONNECTING,....
	enum STATUS { 
		DISCONNECTED	= 0x01, 
		CONNECTING = 0x02, 
		CONNECTED = 0x03, 
		ERROR_OCCURED = 0x04 };

	// return connection status
	virtual int GetStatus() = 0;
};

class __declspec( uuid("{C48B8E60-ABB7-4e86-8399-8E5EBE926EFA}") ) ICmsServer;

Tak wygląda mój kod:

Plik .h

#pragma once
#include "SDKHeaders\ICmsClient.h" //to jest includowane dlatego, że includowanie "ICmsServer.h" powoduje błędy - widocznie mają to spaprane

using namespace System;

namespace SDKClrWrapper
{
	public ref class CmsServer
	{
	public:
		enum class Status
		{
			Disconnected = ICmsServer::DISCONNECTED,
			Connecting = ICmsServer::CONNECTING,
			Connected = ICmsServer::CONNECTED,
			ErrorOccured = ICmsServer::ERROR_OCCURED
		};
	private:
		ICmsServer * server;
	internal:
		CmsServer(ICmsServer * nativeServer);
	public:
		~CmsServer();
		void ConnectToServer(String^ address, int port, String^ login, String^ password);
		void DisconnectServer();
		CmsServer::Status GetStatus();
	protected:
		!CmsServer();
	private:
		LPOLESTR Convert(String^ s);

	};
}

i plik cpp:

#include "stdafx.h"
#include "CmsServer.h"
#include <vcclr.h>

namespace SDKClrWrapper
{
	CmsServer::CmsServer(ICmsServer * nativeServer)
	{
		server = nativeServer;
	}

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

	void CmsServer::ConnectToServer(String^ address, int port, String^ login, String^ password)
	{
		LPOLESTR lpAddress = Convert(address);
		LPOLESTR lpLogin = Convert(login);
		LPOLESTR lpPassword = Convert(password);
		server->ConnectToServer(lpAddress, port, lpLogin, lpPassword);
	}

	void CmsServer::DisconnectServer()
	{
		if (server == NULL || GetStatus() != Status::Connected)
			return;

		server->DisconnectServer();
	}

	CmsServer::Status CmsServer::GetStatus()
	{
		if (server == NULL)
			return Status::Disconnected;

		return (Status)server->GetStatus();
	}

	CmsServer::!CmsServer() 
	{
		if (server == NULL)
			return;

		if (GetStatus() == Status::Connected)
			server->DisconnectServer();

		server->Release();
	}

	LPOLESTR CmsServer::Convert(String^ s)
	{
		pin_ptr<const wchar_t> wch = PtrToStringChars(s);
		return (LPOLESTR)wch;
	}
}

plik Form1.cs

using System;
using System.Windows.Forms;
using SDKClrWrapper;

namespace ClrWrapperTest
{
    public partial class Form1 : Form
    {
        private SdkLibrary _library;
        private CmsClientFactory _factory;
        private CmsClient _client;
        private CmsServer _server;
        private CmsVideoView _videoView;

        private uint _counter = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            _server?.Dispose();
            _videoView?.Dispose();
            _client?.Dispose();
            _factory?.Dispose();
            _library?.Dispose();
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            _library = new SdkLibrary();
            _factory = _library.GetClientFactory();
            _client = _factory.GetCmsClient();
            _server = _client.GetServer(0);

            _server.ConnectToServer("localhost", 9001, "admin", "admin");

        }
    }
}

Wydaje mi się, że to jest cały potrzebny kod, ale resztę mogę dorzucić na żądanie.

1
        LPOLESTR lpAddress = Convert(address);
        LPOLESTR lpLogin = Convert(login);
        LPOLESTR lpPassword = Convert(password);
        server->ConnectToServer(lpAddress, port, lpLogin, lpPassword);
...
    LPOLESTR CmsServer::Convert(String^ s)
    {
        pin_ptr<const wchar_t> wch = PtrToStringChars(s);
        return (LPOLESTR)wch;
    }

Wyjście z Convert powoduje odpięcie wskaźnika, w efekcie tak jakby tego pin_ptr nie było i GC może ci wskaźnik zabrać.

Użyj marshal_as.

#include <msclr/marshal.h>
using namespace msclr::interop;

void CmsServer::ConnectToServer(String ^address, int port, String ^login, String ^password)
{
	marshal_context mct;
	LPCOLESTR lpAddress = mct.marshal_as<LPCOLESTR>(address);
	LPCOLESTR lpLogin = mct.marshal_as<LPCOLESTR>(login);
	LPCOLESTR lpPassword = mct.marshal_as<LPCOLESTR>(password);

	server->ConnectToServer(lpAddress, port, lpLogin, lpPassword);
}
0

Dzięki. Poprawiłem. Nic to jednak nie zmieniło w (nie)działaniu aplikacji.

1 użytkowników online, w tym zalogowanych: 0, gości: 1