Skip to content
qiannian edited this page Jun 13, 2026 · 2 revisions

C# 免注册调用示例

通过 tools.dllsetupW 加载 op_x86.dllop_x64.dll,不需要使用 regsvr32 注册插件。

准备目录

示例假设程序运行目录下有如下结构:

app.exe
op/
  x86/
    tools.dll
    op_x86.dll
  x64/
    tools.dll
    op_x64.dll

tools.dll 由项目的 tools/ 工程生成。当前 C# 进程、tools.dllop_x86.dll/op_x64.dll 的位数必须一致。

Code

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

internal sealed class OpSoft : IDisposable
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private delegate int SetupWDelegate([MarshalAs(UnmanagedType.LPWStr)] string path);

    private static IntPtr loadedToolsHandle = IntPtr.Zero;
    private static SetupWDelegate setupWFunc;

    private readonly object opObject;
    private readonly Type opType;
    private bool disposed;

    private OpSoft(object opObject, Type opType)
    {
        this.opObject = opObject;
        this.opType = opType;
    }

    public static OpSoft Create(string pluginRoot)
    {
        // 根据当前 C# 进程位数选择 x86 或 x64
        bool isX64 = Environment.Is64BitProcess;
        string arch = isX64 ? "x64" : "x86";
        string opName = isX64 ? "op_x64.dll" : "op_x86.dll";

        string runtimeDir = Path.Combine(pluginRoot, arch);
        string toolsDll = Path.Combine(runtimeDir, "tools.dll");
        string opDll = Path.Combine(runtimeDir, opName);

        if (!File.Exists(toolsDll))
        {
            throw new FileNotFoundException("找不到免注册工具", toolsDll);
        }
        if (!File.Exists(opDll))
        {
            throw new FileNotFoundException("找不到 op 插件", opDll);
        }

        // 加载 tools.dll,并获取 setupW 函数地址
        if (loadedToolsHandle == IntPtr.Zero)
        {
            loadedToolsHandle = LoadLibrary(toolsDll);
            if (loadedToolsHandle == IntPtr.Zero)
            {
                throw new InvalidOperationException("加载 tools.dll 失败");
            }

            IntPtr proc = GetProcAddress(loadedToolsHandle, "setupW");
            if (proc == IntPtr.Zero)
            {
                throw new MissingMethodException("tools.dll 未导出 setupW");
            }

            setupWFunc = Marshal.GetDelegateForFunctionPointer<SetupWDelegate>(proc);
        }

        // setupW 必须在 Activator.CreateInstance 之前调用
        int ret = setupWFunc(opDll);
        if (ret != 1)
        {
            throw new InvalidOperationException("免注册加载 op 插件失败");
        }

        // setupW 成功后,就可以按普通 COM 方式创建对象
        Type type = Type.GetTypeFromProgID("op.opsoft");
        if (type == null)
        {
            throw new InvalidOperationException("创建 op.opsoft 类型失败");
        }

        object op = Activator.CreateInstance(type);
        if (op == null)
        {
            throw new InvalidOperationException("创建 op.opsoft 对象失败");
        }

        return new OpSoft(op, type);
    }

    public string Ver()
    {
        // 调用 op.Ver()
        object result = opType.InvokeMember("Ver", BindingFlags.InvokeMethod, null, opObject, null);
        return result?.ToString() ?? string.Empty;
    }

    public void Dispose()
    {
        if (disposed)
        {
            return;
        }

        // 释放 COM 对象
        // tools.dll 内部安装了当前进程的 COM Hook,运行中不要 FreeLibrary
        Marshal.ReleaseComObject(opObject);
        disposed = true;
        GC.SuppressFinalize(this);
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

}

internal static class Program
{
    private static void Main()
    {
        // 程序运行目录下的 op 目录,也可以改成自己的插件释放目录
        string pluginRoot = Path.Combine(AppContext.BaseDirectory, "op");

        using (OpSoft op = OpSoft.Create(pluginRoot))
        {
            Console.WriteLine("op version: " + op.Ver());
        }
    }
}

说明

  • setupW 只在当前进程内生效,不会写入系统注册表。
  • setupW 需要传入 op_x86.dllop_x64.dll 的完整路径或可解析路径。
  • setupWcdecl 调用约定,C# 委托需要显式声明 CallingConvention.Cdecl
  • tools.dll 安装了当前进程内的 COM Hook,加载成功后保持到进程退出即可。
  • 如果是 64 位 C# 进程,请使用 x64/tools.dllx64/op_x64.dll
  • 如果是 32 位 C# 进程,请使用 x86/tools.dllx86/op_x86.dll

Clone this wiki locally