C#源生成器原生封送:如何使用其他程序集定义的类型?
解决共享项目中PInvoke自定义封送器的MarshalDirectiveException问题
以下是几个直接可行的修复方案,按优先级排序:
1. 确保调用项目正确配置源生成器支持
共享项目本身不独立编译,依赖调用项目的编译环境,必须在调用项目的.csproj中明确启用源生成器相关配置:
- 添加必要的NuGet包引用:
<ItemGroup> <PackageReference Include="System.Runtime.InteropServices.Marshalling" Version="8.0.0" PrivateAssets="all" /> </ItemGroup> - 启用预览特性和源生成PInvoke:
<PropertyGroup> <EnablePreviewFeatures>true</EnablePreviewFeatures> <EnableSourceGeneratedPInvokes>true</EnableSourceGeneratedPInvokes> <LangVersion>12</LangVersion> <!-- 或对应支持的C#版本,需匹配SDK版本 --> </PropertyGroup>
2. 显式标记共享项目中的可封送类型
给共享项目里的结构体/record添加[StructLayout]特性,同时确保MarshalUsing和CustomMarshaller的定义完整且可访问:
// 共享项目中的示例类型 [StructLayout(LayoutKind.Sequential)] [MarshalUsing(typeof(MyStructMarshaller))] public struct MyStruct { public int Value; } // 自定义封送器(可放在共享项目或调用项目中) [CustomMarshaller(typeof(MyStruct), MarshalMode.ManagedToUnmanagedOut, typeof(MarshallerImpl))] public static class MyStructMarshaller { public class MarshallerImpl { public IntPtr ConvertToUnmanaged(MyStruct managed) { var ptr = Marshal.AllocHGlobal(Marshal.SizeOf<int>()); Marshal.WriteInt32(ptr, managed.Value); return ptr; } public void Free(IntPtr unmanaged) { Marshal.FreeHGlobal(unmanaged); } } }
3. 将共享项目改为类库项目
如果上述配置无效,直接把共享项目转换成类库项目(Class Library):
- 这样项目会编译成带有完整元数据的DLL,源生成器能正确识别类型上的封送特性,避免因共享项目代码嵌入导致的元数据丢失问题。
- 调用项目直接引用这个类库项目即可。
4. 强制源生成器扫描共享项目代码
在调用项目的.csproj中添加额外文件引用,确保源生成器能扫描到共享项目的所有代码文件:
<ItemGroup> <AdditionalFiles Include="..\YourSharedProject\**\*.cs" /> </ItemGroup>
问题本质
共享项目的代码是嵌入到调用项目中编译的,如果源生成器没有正确扫描到共享项目类型上的MarshalUsing/CustomMarshaller特性,会 fallback 到传统封送逻辑。此时生成的泛型PInvoke代码会触发非blittable类型的封送限制,抛出MarshalDirectiveException。将类型移到调用项目时,源生成器能直接捕获到特性,生成正确的自定义封送代码,因此不会报错。
内容的提问来源于stack exchange,提问作者Johann Studanski
相关产品推荐
相关产品推荐

