如何解决如何在 CLI/C++ 中保存不透明句柄的 List<>?
我正在为 C 库编写 CLI/C++ 包装器,以便在 C# 中使用它。必须说,我只能访问 C 头文件和 C 库的 .lib,而不能访问源代码。
我试图包装的一些函数正在返回不透明的句柄,例如:
typedef struct SanEvent_s *SanEvent;
typedef struct SanValue_s *SanValue;
在 C# 端返回这种类型的对象对我来说似乎很麻烦,因为我不知道结构的实现(我尝试在 C++ 包装器中返回 SanEvent 类型,但在 C# 端该类型不可访问由于“保护级别”或它所说的任何内容)。因此,我目前的计划是编写一些辅助函数,而不是只返回一个整数,该整数表示例如列表中的 San Event
或其他东西。该列表将保存在托管 C++ 包装器中,我可以在其中实际管理 San Event
类型。
我的问题是,我真的不知道如何使用这种类型的 type
。
这个:
using System::Collections::Generic::List;
namespace Wrapper {
public ref class Analytics
{
private:
static List<SanEvent^>^ events = gcnew List<SanEvent^>();
}
}
给我的错误:句柄来处理,指针,或引用是不允许的
右手边也抱怨预期类型说明符 + 与上面相同的错误。
谁能给我一些关于如何巧妙有效地解决这个问题的提示?我的 List 实现并非一成不变,我愿意接受更好的建议。
解决方法
让我们想象以下 SanEvent
声明
struct SanEvent_s
{
int test;
};
typedef SanEvent_s *SanEvent;
并遵循 C++ API 来处理此类事件:
SanEvent GetEvent()
{
auto e = new SanEvent_s();
e->test=42;
return e;
}
int UseEvent(SanEvent pEvent)
{
return pEvent->test;
}
所有这些代码都包含在静态库项目中(完全原生,没有 CLR)。
然后我们有 C++/CLI 项目来包装这个静态库。 这里我们有事件本身的包装器:
#include "./../CppLib/SanEvent_s.h"
public ref class SanEventWrapper: Microsoft::Win32::SafeHandles::SafeHandleZeroOrMinusOneIsInvalid
{
public:
static SanEventWrapper^ GetWrapper()
{
return gcnew SanEventWrapper(GetEvent());
}
internal:
SanEventWrapper(SanEvent event):SafeHandleZeroOrMinusOneIsInvalid(true)
{
this->e = event;
this->handle = System::IntPtr(event);
}
int UseWrapper()
{
return ::UseEvent(this->e);
}
protected:
bool ReleaseHandle() override
{
//todo: release wrapped event
return true;
}
private:
SanEvent e;
};
另一个使用这种包装器的类
public ref class SanEventConsumer
{
public:
int ConsumeEvent(SanEventWrapper^ wrapper)
{
return wrapper->UseWrapper();
}
};
最后,如何使用 C# 中的所有这些:
var wrapper = SanEventWrapper.GetWrapper();
var consumer = new SanEventConsumer();
var res = consumer.ConsumeEvent(wrapper);
Console.WriteLine(res);
这应该打印 42
;
注意事项: 备注:
- 这是一个非常简化的示例。它应该根据“SanEvent”结构的语义以及 SafeHandle 文档(https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle?view=netframework-4.8 和 https://docs.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safehandlezeroorminusoneisinvalid?view=netframework-4.8)的要求进行调整
- 您应该决定您的包装器是否拥有
SunEvent
对象,并相应地实现ReleaseHandle
和Dispose
。 - 您可以考虑使用此列表中的另一个基类 https://docs.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles?view=netframework-4.8 而不是“SafeHandleZeroOrMinusOneIsInvalid”,甚至直接从
SafeHandle
继承。 - 您甚至可以考虑完全放弃与
SafeHandle
相关的内容并自己制作简单的包装器,但它可能会给 GC 带来一些惊喜。 - 根据
SunEvent
的语义,您可能还需要实现工厂以确保始终将原始本机指针的所有等于值的相同包装器实例返回到托管代码。
这里有一些类似于上面@Serg 的内容,但明确表示您在 C# 世界中没有想法,对象内部是什么。
所以如果你有一个用 VS 制作的 C++/CLI 库,你会在 .h 文件中得到它:
#pragma once
#include <cstdint>
using namespace System;
namespace CppCliLibrary {
public ref class Class1
{
public:
static IntPtr getOpaqueInstance(int32_t argument);
static void useOpaqueInstance(IntPtr obj);
static void freeOpaqueInstance(IntPtr obj);
};
}
和上面一样,使用 IntPtr
来表示指向“whatever”的指针。对应的.cpp文件是这样的:
#include "pch.h"
#include "CppCliLibrary.h"
#include <string>
#include <iostream>
namespace CppCliLibrary
{
class OpaqueCppClass
{
public:
OpaqueCppClass(int32_t arg)
: m_int(arg) { }
int32_t m_int;
};
}
IntPtr CppCliLibrary::Class1::getOpaqueInstance(int32_t argument)
{
return IntPtr(new OpaqueCppClass(argument));
}
void CppCliLibrary::Class1::useOpaqueInstance(IntPtr obj)
{
CppCliLibrary::OpaqueCppClass* deref = reinterpret_cast<CppCliLibrary::OpaqueCppClass *>(obj.ToPointer());
std::cout << "Contents of class are: " << deref->m_int << std::endl;
}
void CppCliLibrary::Class1::freeOpaqueInstance(IntPtr obj)
{
CppCliLibrary::OpaqueCppClass* deref = reinterpret_cast<CppCliLibrary::OpaqueCppClass*>(obj.ToPointer());
std::cout << "Deleting class with contents: " << deref->m_int << std::endl;
delete deref;
}
然后在 C# 文件中你有这个:
namespace CsCoreConsole
{
class Program
{
static void Main(string[] args)
{
// Get an instance
var instance = CppCliLibrary.Class1.getOpaqueInstance(52);
// Use it
Console.WriteLine("Got an instance we're using");
CppCliLibrary.Class1.useOpaqueInstance(instance);
Console.WriteLine("Freeing it");
CppCliLibrary.Class1.freeOpaqueInstance(instance);
// Add a bunch to a list
List<IntPtr> opaqueInstances = new List<IntPtr>();
for(int i = 0; i < 5; i++)
{
opaqueInstances.Add(CppCliLibrary.Class1.getOpaqueInstance(i * 10));
}
// Use them all
foreach(var cur in opaqueInstances)
{
CppCliLibrary.Class1.useOpaqueInstance(cur);
}
// Delete them all
foreach (var cur in opaqueInstances)
{
CppCliLibrary.Class1.freeOpaqueInstance(cur);
}
}
}
}
当然,C# 项目需要引用 C++/CLI 项目,但您可以在这里了解这个想法。 C++/CLI 是 IntPtr
的工厂(仅此而已),它也可以使用,因为对 C# 来说它是不透明的。 C# 只知道 IntPtr
。
Serg 的想法是以类型安全的方式对其进行更多包装。当然,这可以工作,但这是“更原始”的变体,如果您想“直接”将其放入 List<>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。