簡體   English   中英

如何將C ++類轉換為.NET對象?

[英]How to convert a C++ class into a .NET object?

我們有一個C#程序員,希望.NET對象完成所有基礎工作。 它應該基本上是一個帶有功能和事件的黑盒子。

我用C ++ Builder編寫了所有這些,使用非可視VCL類,現在看來我必須從中創建一個.NET對象。

我需要一個簡單的例子,說明如何使用一個函數和一個事件處理程序創建一個.NET“框”,從那里我應該能夠實現其余部分。 我應該在COM對象中執行此操作嗎? 我應該使用什么技術?

示例C ++方面。

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#到達的公開函數,事件和屬性。

從這個類我需要創建一個這樣的.NET對象:

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

我還需要C#將函數連接到C ++類中的Event處理程序。

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

這些事件處理程序在C ++類中調用,以觸發這樣的事件:

if(OnMode != NULL)
{
  OnMode(FMode,FReason);
}

還有一些屬性,但這些屬性很容易在COM接口中嵌入(如果這是我們需要的)...

由於這是用C ++編寫的,C ++構建器可以編寫組件(對於C ++ Builder和Delphi,使用ActiveX技術),也許可以將C ++ Builder組件庫轉換為.Net對象/組件?

編輯:使其更加清晰......

MyLib.IDevice.Connect()是我希望C#看到的......函數列表是C ++函數,就像帶有接口IDevice的.Net對象MyLib一樣。

假設我已經創建了一個MyLib.IDevice實例作為Device,我可以調用Device.Connect(); 來自C#。

這很難......丑陋......最簡單的解決方案可能是創建一個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#不兼容,因此您在包裝器中“代理”它們。

// __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#-side你必須創建另一個包裝器:-)這次因為當你傳遞一個委托C# - > C ++時,.NET會創建一個“thunk”,但是如果你不在某個地方保存委托,那么這個“thunk”收集垃圾。 因此,最簡單的解決方案通常是創建一個包裝類,您可以在其中保存已使用的委托。 你甚至可以在這個包裝器中封裝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();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM