У нас есть SDK с собственным кодом, который преимущественно использует тип C / C ++ size_t
для таких вещей, как размеры массивов.Мы дополнительно предоставляем оболочку .NET (написанную на C #), которая использует PInvoke для вызова нативного кода для тех, кто хочет интегрировать наш SDK в свое приложение .NET.
.NET имеет тип System.UIntPtr
, которыйотлично сочетается с size_t
функционально, а функционально все работает как положено.Некоторые из структур C #, предоставленных нативной стороне, содержат типы System.UIntPtr
, и они доступны пользователям .NET API, которые требуют от них работы с типами System.UIntPtr
.Проблема в том, что System.UIntPtr
плохо взаимодействует с типичными целочисленными типами в .NET.Требуются приведения, и различные «базовые» вещи, такие как сравнение с целыми числами / литералами, не работают без дополнительного приведения.
Мы попытались объявить экспортированные параметры size_t
как uint
и применить MarshalAsAttribute(UnmanagedType.SysUInt)
, но этоприводит к ошибке времени выполнения для неверного маршалинга.Например:
[DllImport("Native.dll", EntryPoint = "GetVersion")]
private static extern System.Int32 GetVersion(
[Out, MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 1)]
StringBuilder strVersion,
[In, MarshalAs(UnmanagedType.SysUInt)]
uint uiVersionSize
);
Вызов GetVersion в C # с передачей uint для 2-го параметра приводит к этой маршальной ошибке во время выполнения:
System.Runtime.InteropServices.MarshalDirectiveException: Cannot marshal 'parameter #2': Invalid managed/unmanaged type combination (Int32/UInt32 must be paired with I4, U4, or Error).
Мы могли бы создать упаковщики фасадов, которые предоставляют intтипы в .NET и внутренне выполняют приведение к System.UIntPtr
для нативно-совместимых классов, но (а) мы беспокоимся о производительности копирования буферов (которые могут быть очень большими) между почти дублирующимися классами и (б) это кучаработы.
Любые предложения о том, как PInvoke с size_t
типами при сохранении удобного API в .NET?
Вот пример одного случая, который фактически совпадает снаш реальный код, но с упрощенными / зачищенными именами. ПРИМЕЧАНИЕ Этот код получен из нашего производственного кода вручную.Он компилируется для меня, но я не запускаю его.
Собственный (C / C ++) код:
#ifdef __cplusplus
extern "C"
{
#endif
enum Flags
{
DEFAULT_FLAGS = 0x00,
LEVEL_1 = 0x01,
};
struct Options
{
Flags flags;
size_t a;
size_t b;
size_t c;
};
int __declspec(dllexport) __stdcall InitOptions(
Options * const pOptions)
{
if(pOptions == nullptr)
{
return(-1);
}
pOptions->flags = DEFAULT_FLAGS;
pOptions->a = 1234;
pOptions->b = static_cast<size_t>(0xFFFFFFFF);
pOptions->c = (1024 * 1024 * 1234);
return(0);
}
#ifdef __cplusplus
}
#endif
Управляемый (C #) код: (Этот должен чтобы воспроизвести неправильный маршаллинг. Изменение полей a, b и c в структуре на тип UIntPtr заставляет его функционировать должным образом.
using System;
using System.Runtime.InteropServices;
namespace Test
{
public enum Flags
{
DEFAULT_FLAGS = 0x00,
LEVEL_1 = 0x01,
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct Options
{
public Flags flags;
public uint a;
public uint b;
public uint c;
}
public class Test
{
[DllImport("my.dll", EntryPoint = "InitOptions", CallingConvention = CallingConvention.StdCall)]
internal static extern Int32 InitOptions(
[In, Out]
ref Options options
);
static void Main(string[] args)
{
Options options = new Options
{
flags = DEFAULT_FLAGS,
a = 111,
b = 222,
c = (1024 * 1024 * 1)
};
Int32 nResultCode = InitOptions(
ref options
);
if(nResultCode != 0)
{
System.Console.Error.WriteLine("Failed to initialize options.");
}
if( options.flags != DEFAULT_FLAGS
|| options.a != 1234
|| options.b != static_cast<size_t>(-1)
|| options.c != (1024 * 1024 * 1234) )
{
System.Console.Error.WriteLine("Options initialization failed.");
}
}
}
}
Я попытался изменить поле enum в управляемой структуре на тип intи он все еще не работает.
Далее я протестирую больше с параметрами функции size_t.