支持具有全局数据的插件DLL的多个实例

Bru*_*ine 6 c windows dll pinvoke ipc

上下文:我将旧版独立引擎转换为组合工具的插件组件.从技术上讲,这意味着我将引擎代码库编译为C DLL,我使用P/Invoke从.NET包装器调用它; 包装器实现由组合工具定义的接口.这非常有效,但现在我收到了为不同项目加载多个引擎实例的请求.由于引擎将项目数据保存在一组全局变量中,并且由于具有引擎代码库的DLL仅加载一次,因此加载多个项目意味着项目数据被覆盖.

我可以看到许多解决方案,但它们都有一些缺点:

  1. 您可以使用相同的代码创建多个DLL,这些代码被Windows视为不同的DLL,因此不会共享其代码.如果你有不同名称的引擎DLL的多个副本,这可能已经有效.但是,使用DllImport属性从包装器调用引擎,我认为在编译包装器时需要知道引擎DLL的名称.显然,如果我必须为每个项目编译不同版本的包装器,这非常麻烦.

  2. 引擎可以作为单独的进程运行.这意味着包装器在加载项目时会为引擎启动一个单独的进程,并且它将使用某种形式的IPC与此进程进行通信.虽然这是一个相对干净的解决方案,但它需要一些努力才能开始工作,我现在不知道哪种IPC技术最适合建立这种结构.通信也可能存在很大的开销:引擎需要经常交换浮点数组.

  3. 该引擎可以适应多个项目.这意味着全局变量应该放入项目结构中,并且对全局变量的每个引用都应该转换为相对于特定项目的相应引用.大约有20-30个全局变量,但是可以想象,这些全局变量是从整个代码库引用的,因此需要以某种自动方式完成此转换.一个相关的问题是你应该能够在所有地方引用"当前"项目结构,但是在每个函数签名中作为额外参数传递它也很麻烦.是否存在一种技术(在C中)来考虑当前的调用堆栈并在那里找到最近的相关数据值的封闭实例?

stackoverflow社区可以就这些(或其他)解决方案提供一些建议吗?

Ben*_*igt 6

将整个darn的东西放在C++类中,然后引用变量将自动找到实例变量.

您可以创建一个指向活动实例的全局指针.这应该是线程本地的(参见参考资料__declspec(thread)).

添加extern "C"委托给活动实例上相应成员函数的包装函数.提供创建新实例,拆除现有实例和设置活动实例的功能.

OpenGL使用这个范例可以产生很好的效果(参见参考资料wglMakeCurrent),找到它的状态数据而不必将状态指针传递给每个函数.


Bru*_*ine 5

虽然我收到了很多答案,暗示去解决方案3,虽然我同意这是一个更好的解决方案概念,我觉得没有办法认识到,解决实际可靠地在我的约束。

相反,我实际实现的是解决方案#1的变体。尽管DLL中的DLL名称DLLImport需要是一个编译时常量,但该问题说明了如何动态地进行操作。

如果之前的代码如下所示:

using System.Runtime.InteropServices;

class DotNetAccess {
    [DllImport("mylib.dll", EntryPoint="GetVersion")]
    private static extern int _getVersion();

    public int GetVersion()
    {
        return _getVersion();
        //May include error handling
    }
}
Run Code Online (Sandbox Code Playgroud)

现在看起来像这样:

using System.IO;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Assembly = System.Reflection.Assembly;

class DotNetAccess: IDisposable {
    [DllImport("kernel32.dll", EntryPoint="LoadLibrary", SetLastError=true)]
    private static extern IntPtr _loadLibrary(string name);
    [DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]
    private static extern bool _freeLibrary(IntPtr hModule);
    [DllImport("kernel32.dll", EntryPoint="GetProcAddress", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
    private static extern IntPtr _getProcAddress(IntPtr hModule, string name);

    private static IntPtr LoadLibrary(string name)
    {
        IntPtr dllHandle = _loadLibrary(name);
        if (dllHandle == IntPtr.Zero)
            throw new Win32Exception();
        return dllHandle;
    }

    private static void FreeLibrary(IntPtr hModule)
    {
        if (!_freeLibrary(hModule))
            throw new Win32Exception();
    }

    private static D GetProcEntryDelegate<D>(IntPtr hModule, string name)
        where D: class
    {
        IntPtr addr = _getProcAddress(hModule, name);
        if (addr == IntPtr.Zero)
            throw new Win32Exception();
        return Marshal.GetDelegateForFunctionPointer(addr, typeof(D)) as D;
    }

    private string dllPath;
    private IntPtr dllHandle;

    public DotNetAccess()
    {
        string dllDir = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location);
        string origDllPath = Path.Combine(dllDir, "mylib.dll");
        if (!File.Exists(origDllPath))
            throw new Exception("MyLib DLL not found");

        string myDllPath = Path.Combine(dllDir, String.Format("mylib-{0}.dll", GetHashCode()));
        File.Copy(origDllPath, myDllPath);
        dllPath = myDllPath;

        dllHandle = LoadLibrary(dllPath);
        _getVersion = GetProcEntryDelegate<_getVersionDelegate>(dllHandle, "GetVersion");
    }

    public void Dispose()
    {
        if (dllHandle != IntPtr.Zero)
        {
            FreeLibrary(dllHandle);
            dllHandle = IntPtr.Zero;
        }
        if (dllPath != null)
        {
            File.Delete(dllPath);
            dllPath = null;
        }
    }

    private delegate int _getVersionDelegate();
    private readonly _getVersionDelegate _getVersion;

    public int GetVersion()
    {
        return _getVersion();
        //May include error handling
    }

}
Run Code Online (Sandbox Code Playgroud)

ew

如果您看到两个版本彼此相邻,这似乎非常复杂,但是一旦建立了基础架构,这是一个非常系统的更改。更重要的是,它可以将修改本地化在我的DotNetAccess层中,这意味着我不必在不是我自己的非常大的代码库中分散修改。