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

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

我们有一个 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 接口(interface)中实现(如果这是我们需要的)...

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

编辑: 为了更清楚...

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

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

最佳答案

这很难...而且很丑...最简单的解决方案可能是创建一个 C 接口(interface):

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# 端你必须创建另一个包装器 :-) 这次是因为当你传递委托(delegate) C#->C++ 时,.NET 会创建一个“thunk”,但如果你不这样做如果不将代表保存在某个地方,这个“thunk”会被垃圾收集。所以最简单的解决方案通常是创建一个包装类,您可以在其中保存使用过的委托(delegate)。您甚至可以在此包装器中封装 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();
}

关于c# - 如何将 C++ 类转换为 .NET 对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43318407/

相关文章:

c++ - 我如何解决包含 65k 行代码的文件导致 [bcc32 fatal error ] F1008 内存不足错误?

c++ - 事件处理程序是否可重入 Embarcadero C++Builder?

c# - 创建一个 nuget 在 linux 和 windows 之间共享

c# - 列表模式下的 .NET ListView 停止显示在列中

c# - 在 C# 中存储 ref

c# - Task.WaitAny 的最大任务?

c# - 如何在不使用youtube API的情况下解析youtube xml

c# - Windows 8 网格应用程序中的自定义详细信息页面

.net - Lucene.NET 中的并发。