如何将C ++类转换为.NET对象?

时间:2017-04-10 08:37:11

标签: c# .net c++builder-10.1-berlin

我们有一个C#程序员,希望.NET对象完成所有基础工作。它应该基本上是一个带有功能和事件的黑盒子。

我用C ++ Builder编写了所有这些,使用非可视VCL类,现在看来我必须用它来制作一个.NET对象。

我需要一个简单的例子来说明如何创建一个.NET"框"使用一个函数和一个事件处理程序,从那里我应该能够实现其余部分。我应该在COM对象中执行此操作吗?我应该使用什么技术?

示例C ++方面。

<?php

// Establish connection to the server
$mysqli = mysqli_connect("localhost", "root", "");
// Selecting Database
$db = mysqli_select_db($mysqli, "p00702");
// Starting session
session_start();
// Storing Session
$user_check = $_SESSION['login_user'];
$_SESSION['user_id'] = userid;
$_SESSION['user_firstname'] = firstname;
// Test to see the content of the global session variable
print_r($_SESSION);
// SQL Query To Fetch Complete Information Of User
$ses_sql = mysqli_query($mysqli, "SELECT username FROM logins WHERE username='$user_check'");
$row = mysqli_fetch_assoc($ses_sql);
$login_session = $row['username'];
if (!isset($login_session)) {
    // Closing Connection
    mysqli_close($mysqli);
    // Redirecting To Home Page
    header('Location: index.php');
    // Make sure that codes below do not execut upon redirection.
    exit;
}

我删除了所有内部内容,只留下了应该可以从C#中获取的公开函数,事件和属性。

从这个类我需要创建一个这样的.NET对象:

typedef void __fastcall (__closure *TIntEvent)(int Status);
typedef void __fastcall (__closure *TVoidEvent)(void);
typedef void __fastcall (__closure *TResultEvent)(String cmd, int code);
typedef void __fastcall (__closure *TModeEvent)(int mode, int reason);

class TDevice : public TObject {

    private:
        // properties
        String FPortName;
        String FDevice;
        String FComment;
        String FID;
        double FBootware;
        double FFirmware;

    protected:

    public:
        // properties
        __property String PortName     = { read=FPortName     };
        __property String Device       = { read=FDevice       };
        __property String Comment      = { read=FComment      };
        __property String ID           = { read=FID           };
        __property double Bootware     = { read=FBootware     };
        __property double Firmware     = { read=FFirmware     };

        // event function pointers
        TModeEvent   OnMode;
        TIntEvent    OnStatus;
        TIntEvent    OnSensors;
        TVoidEvent   OnInfo;
        TResultEvent OnResult;

       // public interface
       bool Connect(void);
       void Disconnect(void);

       void Reset(void);
       void Boot(void);
       void GetInfo(void);
       void GetTag(void);
};

我还需要C#将函数连接到C ++类中的Event处理程序。

MyLib.IDevice.Connect();
MyLib.IDevice.Disconnect();
MyLib.IDevice.Reset();
MyLib.IDevice.Boot();
MyLib.IDevice.GetInfo();
MyLib.IDevice.GetTag();

在C ++类中调用这些事件处理程序来触发这样的事件:

MyLib.IDevice.OnMode    = CSharpEventHandler1;
MyLib.IDevice.OnStatus  = CSharpEventHandler2;
MyLib.IDevice.OnSensors = CSharpEventHandler3;
MyLib.IDevice.OnInfo    = CSharpEventHandler4;
MyLib.IDevice.OnResult  = CSharpEventHandler5;

还有一些属性,但这些属性很容易在COM界面中形成(如果这是我们需要的)......

由于这是用C ++编写的,C ++构建器可以编写组件(对于C ++ Builder和Delphi,使用ActiveX技术),也许可以将C ++ Builder组件库转换为.Net对象/组件?

编辑: 为了使它更清晰......

MyLib.IDevice.Connect()是我希望C#看到的...函数列表是C ++函数,如.Net对象MyLib和接口IDevice。

假设我已经创建了一个MyLib.IDevice实例作为Device,我可以调用Device.Connect();来自C#。

1 个答案:

答案 0 :(得分:3)

很难......丑陋......最简单的解决方案可能就是创建一个C接口:

extern "C"
{
    __declspec(dllexport) __stdcall TDevice* NewDevice()
    {
        return new TDevice();
    }

    __declspec(dllexport) void __stdcall DeleteDevice(TDevice *pDevice)
    {
        delete pDevice;
    }

    __declspec(dllexport) bool __stdcall ConnectDevice(TDevice *pDevice)
    {
        return pDevice->Connect();
    }

    .. and so on
}

在C#中:

[DllImport("YourDll.dll", CallingConvention = CallingConvention.Stdcall)]
public static extern IntPtr NewDevice();

[DllImport("YourDll.dll", CallingConvention = CallingConvention.Stdcall)]
public static extern void DeleteDevice(IntPtr pDevice);

[DllImport("YourDll.dll", CallingConvention = CallingConvention.Stdcall)]
public static extern bool ConnectDevice(IntPtr pDevice);

... and so on

如果你对此感到满意,我们可以开始谈论传递代表......这将是一种痛苦,相信我: - )

Uff ......这很长... C ++方面,如果你为你的班级创建一个包装器会更好。这是因为您在事件中使用__fastcall __closure。这两个修饰符都与C#不兼容,所以你&#34; proxy&#34;他们在包装中。

// __fastcall not handled by C#
typedef void __stdcall (*TIntEventFunc)(int Status);
typedef void __stdcall (*TVoidEventFunc)(void);
typedef void __stdcall (*TResultEventFunc)(const wchar_t *cmd, int code);
typedef void __stdcall (*TModeEventFunc)(int mode, int reason);

class TDeviceWrapper {
    public:
        // You could even use directly a TDevice Device, depending on how your program works.
        // By using a TDevice *, you can attach the wrapper to a preexisting TDevice.
        TDevice *PDevice;

        TModeEventFunc      OnModeFunc;
        TIntEventFunc       OnStatusFunc;
        TIntEventFunc       OnSensorsFunc;
        TVoidEventFunc      OnInfoFunc;
        TResultEventFunc    OnResultFunc;

        void __fastcall OnStatus(int status) {
            OnStatusFunc(status);
        }

        void __fastcall OnResult(String cmd, int code)
        {
            OnResultFunc(cmd.c_str(), code);
        }
};

extern "C" {
    __declspec(dllexport) TDeviceWrapper* __stdcall NewDevice()
    {
        auto pWrapper = new TDeviceWrapper();
        pWrapper->PDevice = new TDevice();
        return pWrapper;
    }

    __declspec(dllexport) void __stdcall DeleteDevice(TDeviceWrapper *pWrapper)
    {
        delete pWrapper->PDevice;
        delete pWrapper;
    }

    __declspec(dllexport) const wchar_t* __stdcall GetPortName(TDeviceWrapper *pWrapper)
    {
        return pWrapper->PDevice->PortName.c_str();
    }

    __declspec(dllexport) bool __stdcall Connect(TDeviceWrapper *pWrapper)
    {
        return pWrapper->PDevice->Connect();
    }   

    __declspec(dllexport) void __stdcall SetStatus(TDeviceWrapper *pWrapper, TIntEventFunc statusFunc) {
        pWrapper->OnStatusFunc = statusFunc;

        if (statusFunc) {
            pWrapper->PDevice->OnStatus = pWrapper->OnStatus;
        } else {
            pWrapper->PDevice->OnStatus = nullptr;
        }
    }

    __declspec(dllexport) void __stdcall SetResult(TDeviceWrapper *pWrapper, TResultEventFunc resultFunc) {
        pWrapper->OnResultFunc = resultFunc;

        if (resultFunc) {
            pWrapper->PDevice->OnResult = pWrapper->OnResult;
        } else {
            pWrapper->PDevice->OnResult = nullptr;
        }
    }
}

然后C# - 你必须创建另一个包装器:-)这次因为当你传递一个代表C# - &gt; C ++时,.NET会创建一个&#34; thunk&#34; ,但是如果你不在某个地方保存代表,那么&#34; thunk&#34;收集垃圾。因此,最简单的解决方案通常是创建一个包装类,您可以在其中保存已使用的委托。您甚至可以在此包装器中封装Dispose()模式: - )

public class TDeviceWrapper : IDisposable
{
    // Fastcall not handled by C#
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void TIntEventFunc(int Status);

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void TVoidEventFunc();

    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    public delegate void TResultEventFunc(string cmd, int code);

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void TModeEventFunc(int mode, int reason);

    IntPtr ptr;

    [DllImport("TDevice.dll")]
    static extern IntPtr NewDevice();

    [DllImport("TDevice.dll")]
    static extern void DeleteDevice(IntPtr pWrapper);

    [DllImport("TDevice.dll")]
    static extern IntPtr GetPortName(IntPtr pWrapper);

    [DllImport("TDevice.dll")]
    static extern void Connect(IntPtr pWrapper);

    [DllImport("TDevice.dll")]
    static extern void SetStatus(IntPtr pWrapper, TIntEventFunc statusFunc);

    [DllImport("TDevice.dll")]
    static extern void SetResult(IntPtr pWrapper, TResultEventFunc resultFunc);

    // To prevent the GC from collecting the managed-tounmanaged thunks, we save the delegates
    TModeEventFunc modeFunc;
    TIntEventFunc statusFunc;
    TIntEventFunc sensorsFunc;
    TVoidEventFunc infoFunc;
    TResultEventFunc resultFunc;

    public void Init()
    {
        ptr = NewDevice();
    }

    public string PortName
    {
        get
        {
            // Important! .NET will try to free the returned
            // string if GetPortName returns directly a string.
            // See for example https://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/
            IntPtr ptr2 = GetPortName(ptr);
            return Marshal.PtrToStringUni(ptr2);
        }
    }

    public void Connect()
    {
        Connect(ptr);
    }

    public void SetStatus(TIntEventFunc statusFunc)
    {
        this.statusFunc = statusFunc;
        SetStatus(ptr, statusFunc);
    }

    public void SetResult(TResultEventFunc resultFunc)
    {
        this.resultFunc = resultFunc;
        SetResult(ptr, resultFunc);
    }

    ~TDeviceWrapper()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (ptr != IntPtr.Zero)
        {
            DeleteDevice(ptr);
            ptr = IntPtr.Zero;
        }

        if (disposing)
        {
            modeFunc = null;
            statusFunc = null;
            sensorsFunc = null;
            infoFunc = null;
            resultFunc = null;
        }
    }
}

然后你可以,例如:

public class MyClass
{
    public void StatusEvent(int status)
    {
        Console.WriteLine("Status: {0}", status);
    }

    public void ResultEvent(string cmd, int code)
    {
        Console.WriteLine("Resukt: {0}, {1}", cmd, code);
    }
}

var mc = new MyClass();

using (var wrapper = new TDeviceWrapper())
{
    wrapper.Init();
    wrapper.SetStatus(mc.StatusEvent);
    wrapper.SetResult(mc.ResultEvent);
    wrapper.Connect();
}