将简单和复杂的数据类型编组到 Object^% / void* 或从 Object^% / void* 编组
我想这对于 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
将 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.