将简单和复杂的数据类型编组到 Object^% / void* 或从 Object^% / void* 编组

发布于 2024-10-12 01:53:33 字数 2803 浏览 2 评论 0原文

我想这对于 C++/CLI 专家来说会很简单。

我正在创建一个包装器,它将向 C# WinForms 应用程序公开高性能 C++ 本机类。 对于简单的已知对象,一切都很顺利,我还可以包装一个回调函数来委托。但现在我有点困惑。

原生 C++ 类有以下方法:

int GetProperty(int propId, void* propInOut)

起初我以为我可以使用 void* 作为 IntPtr,但后来我发现我需要从 C# 访问它。所以我想到了一个包装方法:

int GetProperty(int propId, Object^ propInOut)

但是当我查看C++源代码时,我发现该方法需要修改对象。显然我需要:

int GetProperty(int propId, Object^% propInOut)

现在我无法将对象传递给本机方法,所以我需要知道如何在包装器中处理它们。由于调用者应该始终知道他/她正在传递/接收什么类型的数据,因此我声明了一个包装器:

int GetProperty(int propId, int dataType, Object^% propInOut)

我想,我可以用它来传递引用和值类型,例如,像这样的 int:

Object count = 100; // yeah, I know boxing is bad but this will not be real-time call anyway
myWrapper.GetProperty(Registry.PROP_SMTH, DATA_TYPE_INT, ref count);

我刚刚添加了一堆我需要的所有数据类型的 dataType 常量:

DATA_TYPE_INT, DATA_TYPE_FLOAT, DATA_TYPE_STRING, DATA_TYPE_DESCRIPTOR, DATA_TYPE_BYTE_ARRAY

(DATA_TYPE_DESCRIPTOR 是一个简单的结构,有两个字段:int Id 和 wstring Description - 该类型也将被包装,所以我猜封送将是简单的来回复制数据;所有本机字符串是 Unicode)。

现在的问题是 - 如何实现这 5 种类型的包装方法? 当我可以将 Object^% 转换为某些内容(int、float 安全吗?)并传递给本机方法时,何时需要使用 pin_ptr 以及何时需要一些更复杂的到本机和返回的封送?

int GetProperty(int propId, int dataType, Object^% propInOut)
{
    if(dataType == DATA_TYPE_INT)
    {
        int* marshaledPropInOut = ???
        int result = nativeObject->GetProperty(propId, (void*)marshaledPropInOut);
        // need to do anything more?
        return result;
    }
else
    if(dataType == DATA_TYPE_FLOAT)
    {
        float* marshaledPropInOut = ???
        int result = nativeObject->GetProperty(propId, (void*)marshaledPropInOut);
        // need to do anything more ?
        return result;
    }
else
    if(dataType == DATA_TYPE_STRING)
    {
        // will pin_ptr be needed or it is enough with the tracking reference in the declaration?
        // the pointers won't get stored anywhere in C++ later so I don't need AllocHGlobal
        int result = nativeObject->GetProperty(propId, (void*)marshaledPropInOut);
        // need to do anything more?
        return result;
    }
else
    if(dataType == DATA_TYPE_BYTE_ARRAY)
    {
         // need to convert form managed byte[] to native char[] and back; 
         // user has already allocated byte[] so I can get the size of array somehow

         return result;
    }
else
    if(dataType == DATA_TYPE_DESCRIPTOR)
    {
         // I guess I'll have to do a dumb copying between native and managed struct, 
         // the only problem is pinning of the string again before passing to the native

         return result;
    }

    return -1;
}

PS 也许有一个更优雅的解决方案来用许多可能的数据类型包装这个 void* 方法?

I guess this will be simple for C++/CLI gurus.

I am creating a wrapper which will expose high-performance C++ native classes to C# WinForms application.
Everything went fine with simple known objects and I could wrap also a callback function to delegate. But now I am a bit confused.

The native C++ class has a following method:

int GetProperty(int propId, void* propInOut)

At first I thought I could use void* as IntPtr, but then I found out that I need to access it from C#. So I thought about a wrapper method:

int GetProperty(int propId, Object^ propInOut)

but as I looked through the C++ source, I found out that the method needs to modify the objects. So obviously I need:

int GetProperty(int propId, Object^% propInOut)

Now I cannot pass Objects to native methods so I need to know how to treat them in the wrapper. As the caller should always know what kind of data he/she is passing/receiving, I declared a wrapper:

int GetProperty(int propId, int dataType, Object^% propInOut)

I guess, I can use it to pass reference and value types, for example, an int like this:

Object count = 100; // yeah, I know boxing is bad but this will not be real-time call anyway
myWrapper.GetProperty(Registry.PROP_SMTH, DATA_TYPE_INT, ref count);

I just added a bunch of dataType constants for all the data types I need:

DATA_TYPE_INT, DATA_TYPE_FLOAT, DATA_TYPE_STRING, DATA_TYPE_DESCRIPTOR, DATA_TYPE_BYTE_ARRAY

(DATA_TYPE_DESCRIPTOR is a simple struct with two fields: int Id and wstring Description - this type will be wrapped too, so I guess marshaling will be simple copying data back and forth; all the native strings are Unicode).

Now, the question is - how to implement the wrapper method for all these 5 types?
When I can just cast Object^% to something (is int, float safe to do that?) and pass to native method, when do I need to use pin_ptr and when I need some more complex marshaling to native and back?

int GetProperty(int propId, int dataType, Object^% propInOut)
{
    if(dataType == DATA_TYPE_INT)
    {
        int* marshaledPropInOut = ???
        int result = nativeObject->GetProperty(propId, (void*)marshaledPropInOut);
        // need to do anything more?
        return result;
    }
else
    if(dataType == DATA_TYPE_FLOAT)
    {
        float* marshaledPropInOut = ???
        int result = nativeObject->GetProperty(propId, (void*)marshaledPropInOut);
        // need to do anything more ?
        return result;
    }
else
    if(dataType == DATA_TYPE_STRING)
    {
        // will pin_ptr be needed or it is enough with the tracking reference in the declaration?
        // the pointers won't get stored anywhere in C++ later so I don't need AllocHGlobal
        int result = nativeObject->GetProperty(propId, (void*)marshaledPropInOut);
        // need to do anything more?
        return result;
    }
else
    if(dataType == DATA_TYPE_BYTE_ARRAY)
    {
         // need to convert form managed byte[] to native char[] and back; 
         // user has already allocated byte[] so I can get the size of array somehow

         return result;
    }
else
    if(dataType == DATA_TYPE_DESCRIPTOR)
    {
         // I guess I'll have to do a dumb copying between native and managed struct, 
         // the only problem is pinning of the string again before passing to the native

         return result;
    }

    return -1;
}

P.S. Maybe there is a more elegant solution for wrapping this void* method with many possible datatypes?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

枕花眠 2024-10-19 01:53:33

将 C# 对象等同于 void* 不一定有意义。没有任何方法可以整理任意数据。即使有一个对象,C# 仍然知道它的底层类型,并且为了进行封送处理(意味着从 C++ 世界到 C# 的转换,反之亦然),需要知道数据的类型。 void* 只是一个指向完全未知类型的内存的指针,那么如何将其转换为类型必须已知的对象呢?

如果您所描述的可以从 C# 世界传入的类型数量有限,那么最好在 C++/CLI 代码中进行多次重载,每个重载都采用其中一种类型,然后您可以固定传入的类型(如果需要),将其转换为 void*,将其传递给采用 void* 的 C++ 函数,然后根据类型适当地封送回来。

您可以按照列出的方式实现 case 语句,但是如果您无法处理传入的类型,该怎么办?从 C# 调用该函数的人无法知道哪些类型是可接受的,并且编译器无法帮助您找出您做错了什么。

It doesn't necessarily make sense to equate a C# object to a void*. There isn't any way to marshal arbitrary data. Even with an object, C# still knows what type it is underneath, and for marshaling to take place -- meaning a conversion from the C++ world to C# or vice-versa -- the type of data needs to be known. A void* is just a pointer to memory of a completely unknown type, so how would you convert it to an object, where the type has to be known?

If you have a limited number of types as you describe that could be passed in from the C# world, it is best to make several overloads in your C++/CLI code, each of which took one of those types, and then you can pin the type passed in (if necessary), convert it to a void*, pass that to your C++ function that takes a void*, and then marshal back as appropriate for the type.

You could implement a case statement as you listed, but then what do you do if you can't handle the type that was passed in? The person calling the function from C# has no way to know what types are acceptable and the compiler can't help you figure out that you did something wrong.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文