[英]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.