如何在Rust中调用C# DLL中的类对象?
问题描述
我需要调用外部DLL函数,示例C#代码如下:
[DllImport("shell32.dll", CharSet = CharSet.Unicode)] private static extern void SHAddToRecentDocs(ShellAddToRecentDocsFlags flags, [MarshalAs(UnmanagedType.LPStr)] string file);
我自己构建了如下结构的C# DLL:
namespace MyObject { public class MyObjectHandler { // 一些DllImport声明 public MyObjectHandler() {} public void my_function1(){/* 实现代码 */} // 其他方法 } }
尝试用Rust调用时,发现libloading或sharelib只支持调用DLL导出函数,无法直接调用类。我尝试直接调用类中的函数,Rust代码如下:
fn call_dynamic() -> Result<bool, Box<dyn std::error::Error>> { unsafe { let program_exe_path = current_exe().unwrap(); let dll_path = program_exe_path.parent().unwrap().join("ClassLibrary1.dll"); println!("path {}", dll_path.display()); if dll_path.exists() { let lib = libloading::Library::new(dll_path.to_str().unwrap())?; println!("Load dll success!"); let func: libloading::Symbol<unsafe extern fn() -> bool> = lib.get(b"my_function1")?; return Ok(func()); } Ok(true) } }
执行后报错:GetProcAddress failed。我想知道是否可以在Rust中调用C# DLL中的类?如果可行,具体该如何实现?
解决方案
直接通过libloading这类原生动态库加载工具调用C#类的方法是行不通的——C# DLL是.NET程序集,并非原生PE导出函数格式的DLL,类的方法不会直接暴露为可通过GetProcAddress获取的导出符号。下面是几种可行的实现方式:
方法1:将C#类的方法包装为导出函数
借助UnmanagedExports库(需安装NuGet包UnmanagedExports),把C#类的方法包装成原生导出函数,让Rust可以直接调用。
修改C#代码:
using RGiesecke.DllExport; using System.Runtime.InteropServices; namespace MyObject { public class MyObjectHandler { // 保持原有逻辑 public MyObjectHandler() {} public void my_function1(){/* 实现代码 */} } // 导出包装类 public static class NativeExports { // 导出创建实例的函数 [DllExport("CreateMyObjectHandler", CallingConvention = CallingConvention.Cdecl)] public static IntPtr CreateInstance() { var handler = new MyObjectHandler(); // 将对象转换为非托管指针 return Marshal.GetIUnknownForObject(handler); } // 导出调用my_function1的函数 [DllExport("CallMyFunction1", CallingConvention = CallingConvention.Cdecl)] public static void CallMyFunction1(IntPtr instancePtr) { // 将指针转换回对象 var handler = Marshal.GetObjectForIUnknown(instancePtr) as MyObjectHandler; handler?.my_function1(); } // 导出释放实例的函数 [DllExport("ReleaseMyObjectHandler", CallingConvention = CallingConvention.Cdecl)] public static void ReleaseInstance(IntPtr instancePtr) { Marshal.Release(instancePtr); } } }
对应的Rust调用代码:
use libloading::{Library, Symbol}; use std::ptr; use std::error::Error; type CreateInstanceFn = unsafe extern "C" fn() -> *mut (); type CallMyFunction1Fn = unsafe extern "C" fn(*mut ()); type ReleaseInstanceFn = unsafe extern "C" fn(*mut ()); fn call_dynamic() -> Result<bool, Box<dyn Error>> { unsafe { let program_exe_path = std::env::current_exe()?; let dll_path = program_exe_path.parent().unwrap().join("ClassLibrary1.dll"); println!("path {}", dll_path.display()); if dll_path.exists() { let lib = Library::new(dll_path)?; println!("Load dll success!"); // 获取导出函数 let create_instance: Symbol<CreateInstanceFn> = lib.get(b"CreateMyObjectHandler")?; let call_func: Symbol<CallMyFunction1Fn> = lib.get(b"CallMyFunction1")?; let release_instance: Symbol<ReleaseInstanceFn> = lib.get(b"ReleaseMyObjectHandler")?; // 创建实例 let instance = create_instance(); if instance != ptr::null_mut() { // 调用方法 call_func(instance); // 释放实例 release_instance(instance); } return Ok(true); } Ok(true) } }
方法2:将C#类注册为COM组件
把C#类标记为COM可见,注册为COM组件后,Rust可以通过COM互操作调用类的方法。
修改C#代码:
using System.Runtime.InteropServices; // 定义COM接口 [ComVisible(true)] [Guid("自行生成的GUID,可通过Visual Studio工具生成")] public interface IMyObjectHandler { void my_function1(); } [ComVisible(true)] [Guid("另一个生成的GUID")] [ClassInterface(ClassInterfaceType.None)] public class MyObjectHandler : IMyObjectHandler { public MyObjectHandler() {} public void my_function1() {/* 实现代码 */} }
在项目属性中开启"使程序集COM可见",编译后用regasm.exe注册组件:
regasm.exe ClassLibrary1.dll /codebase
Rust中可使用com-rs库调用COM接口,示例:
use com::prelude::*; // 定义COM接口绑定 #[com_interface("你的GUID")] pub trait IMyObjectHandler: IUnknown { unsafe fn my_function1(&self) -> HRESULT; } fn call_com() -> Result<(), Box<dyn Error>> { com::runtime::init()?; unsafe { let handler: ComPtr<IMyObjectHandler> = CoCreateInstance( &CLSID_MyObjectHandler, // 需定义对应的CLSID None, CLSCTX_INPROC_SERVER, )?; handler.my_function1()?; } Ok(()) }
方法3:使用.NET Native AOT编译(.NET 7+)
若使用.NET 7及以上版本,可将C#项目编译为原生AOT DLL,直接导出类的方法为原生函数。
修改C#项目文件(.csproj):
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Dll</OutputType> <TargetFramework>net7.0</TargetFramework> <PublishAot>true</PublishAot> <NativeLib>Shared</NativeLib> </PropertyGroup> </Project>
编写导出方法:
using System.Runtime.InteropServices; namespace MyObject { public class MyObjectHandler { public MyObjectHandler() {} public void my_function1(){/* 实现代码 */} } public static class NativeExports { [UnmanagedCallersOnly(EntryPoint = "CreateMyObjectHandler")] public static IntPtr CreateInstance() { var handler = new MyObjectHandler(); return GCHandle.ToIntPtr(GCHandle.Alloc(handler)); } [UnmanagedCallersOnly(EntryPoint = "CallMyFunction1")] public static void CallMyFunction1(IntPtr handlePtr) { var handle = GCHandle.FromIntPtr(handlePtr); var handler = handle.Target as MyObjectHandler; handler?.my_function1(); } [UnmanagedCallersOnly(EntryPoint = "ReleaseMyObjectHandler")] public static void ReleaseInstance(IntPtr handlePtr) { var handle = GCHandle.FromIntPtr(handlePtr); handle.Free(); } } }
编译后得到原生DLL,Rust调用方式与方法1一致,直接用libloading加载导出函数即可。
内容的提问来源于stack exchange,提问作者Hellagur
相关产品推荐
相关产品推荐

