我正在为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^>();
}
}
给我错误:句柄到句柄、指针或引用是不允许的
右侧还抱怨预期的类型说明符+与上面相同的错误。
有人能给我一些关于如何巧妙有效地解决这个问题的建议吗?我的清单的执行不是板上钉钉的,我愿意接受更好的建议。
让我们想象一下以下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://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle?view=netframework-4.8和https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safehandlezeroorminusoneisinvalid?view=netframework-4.8(
- 您应该决定您的包装器是否拥有
SunEvent
对象,并相应地实现ReleaseHandle
和Dispose
- 您可以考虑使用该列表中的另一个基类https://learn.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
来表示指向"0"的指针;不管怎样";。相应的.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<>