-
Notifications
You must be signed in to change notification settings - Fork 256
qiannian edited this page Jun 13, 2026
·
2 revisions
通过 tools.dll 的 setupW 加载 op_x86.dll 或 op_x64.dll,不需要使用 regsvr32 注册插件。
示例假设程序运行目录下有如下结构:
app.exe
op/
x86/
tools.dll
op_x86.dll
x64/
tools.dll
op_x64.dll
tools.dll 由项目的 tools/ 工程生成。当前 C# 进程、tools.dll、op_x86.dll/op_x64.dll 的位数必须一致。
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.dll或op_x64.dll的完整路径或可解析路径。 -
setupW是cdecl调用约定,C# 委托需要显式声明CallingConvention.Cdecl。 -
tools.dll安装了当前进程内的 COM Hook,加载成功后保持到进程退出即可。 - 如果是 64 位 C# 进程,请使用
x64/tools.dll和x64/op_x64.dll。 - 如果是 32 位 C# 进程,请使用
x86/tools.dll和x86/op_x86.dll。