1

我们有一个 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++ 类中的事件处理程序。

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++ Builder 编写的,并且 C++ builder 可以编写组件(对于 C++ Builder 和 Delphi,使用 ActiveX 技术),也许可以将 C++ Builder 组件库转换为 .Net 对象/组件?

编辑:为了更清楚......

MyLib.IDevice.Connect() 是我希望 C# 看到的...函数列表是 C++ 函数,就像在带有接口 IDevice 的 .Net 对象 MyLib 中一样。

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

4

1 回答 1

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

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

呃......它很长...... 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# 端你必须创建另一个包装器:-) 这一次因为当你传递一个委托 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();
}
于 2017-04-10T09:31:04.883 回答