【问题标题】:How to hold a List<> of opaque handles in CLI/C++?如何在 CLI/C++ 中保存不透明句柄的 List<>?
【发布时间】:2021-09-12 09:24:31
【问题描述】:

我正在为 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 实现并非一成不变,我愿意接受更好的建议。

【问题讨论】:

  • 您是否尝试过基于SafeHandle 创建托管友好的包装器?在 C++/CLI 端的这个包装器中,您可以只存储 SanEvent* C++ 风格的指针并提供一组托管友好的函数来对包装的对象进行必要的操作。因此,托管代码将使用此包装器,并且当您需要在 C++ API 中的某处传递原始 C++ pinter 时,包装器将处理这些情况。根据 C++ 对象的语义,您可能还需要为此类包装器创建工厂类,以便为等于原始指针值返回相同的包装器实例。
  • @Serg 嗨。不,我不熟悉 SafeHandle。您能否举例说明这将如何适用于我的案例?听你的解释有点麻烦。
  • 鉴于您的句柄是结构指针,最简单的方法可能是将其全部处理为 void 指针。因此,在 C# 中将 void* 列表作为不安全代码,将它们传递给 C++ 中也接受 void 指针的包装层,然后将它们转换为正确的类型并执行对 C 库的调用。
  • 指针大小取决于架构。 x64 上的 64 位!我会调查 IntPtr 是否有任何不透明的地方。

标签: c# c++ list wrapper handle


【解决方案1】:

让我们想象一下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;

注意事项: 备注:

【讨论】:

    【解决方案2】:

    这与@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 表示指向“随便”的指针。对应的.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&lt;&gt;

    【讨论】:

      猜你喜欢
      • 2014-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-16
      相关资源
      最近更新 更多