You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 21:15:33