如何在dot net中使用无需注册的COM dll

6
在我的一个项目中,我有一个使用C++ DLL的C#应用程序。目前在客户端PC上,我们将C++ DLLS注册为COM组件,以便我们在C#中使用它们。
我在NET上了解到微软提供了一个无需注册的解决方案,链接如下:http://msdn.microsoft.com/en-us/library/ms973913.aspx
但是阅读后我并没有得到太多线索,因为我的应用程序架构如下:
1. 我有两个C++ dlls,假设为CPForms.dll和Rules.dll。 2. Rule.dll包含在CPForms.dll中。 3. 我有一个C# dll,称为ConsumeForm.dll,它正在使用CPForms.DLL。 4. 我还有另一个C# Exe,它正在使用ConsumeForm.dll。
我的客户端只打开C# Exe,然后调用ConsumeForm.dll,进而调用CPForms.dll,显示C++表单(UI),有一个验证按钮,当用户点击该按钮时,它在内部使用C++ Rules.dll。目前,我在注册表中注册了两个C++ dll。
现在,客户只希望将Rule.dll作为无需注册的安装引用,因为Rule.dll经常更改,客户不想使用管理员帐户反复注销和注册。
除此之外,客户对CPForms.dll的注册表示满意。
我的问题是,如何生成清单文件?它在我的情况下如何工作?

这篇文章提供了很多细节,你还需要什么?你尝试过什么?你遇到了什么错误? - Polyfun
1
实际上,它需要免注册吗?只要rules.dll安装在相同的位置(这将需要管理员权限,对吧?)并且实现与CPForms.dll构建的相同对象和接口,为什么每次都需要取消注册和重新注册呢?(不过,免注册的问题本身也很有趣。) - Rup
1
@Rup 的观点非常好。只需保持 rules.dll 二进制兼容性,就可以直接复制新版本,无需重新注册。 - Polyfun
2
这不是可行的解决方案。清单需要嵌入您的程序中。如果Rules.dll的所有者不断更改接口,则需要不断重建您的程序。不仅要更新嵌入式清单,还要调整自己的代码以处理更改。如果唯一的目的是隔离,则只需将DLL复制到自己的EXE文件夹中,并创建一个名为“yourapp.exe.local”的空目录。 - Hans Passant
我再次与客户检查了同样的问题,现在他们说他们想要所有COM dlls Reg Free,现在我该怎么办?我今天尝试了但没有成功。现在我能够生成ConsumeForm.dll的清单和Exe的清单,但我无法在exe清单中引用dll清单。第二个问题是,在ConsumeForm.dll.manifest中,我只得到了CPForms.dll而不是Rules.dll的信息。 - Hemant Kothiyal
显示剩余6条评论
2个回答

10

注意:考虑使用此答案中的清单文件来获取更好的解决方案:

如何在没有清单文件的情况下实现

COM注册表是一种可选的服务,用于inproc服务器。

再次强调:您不必注册对象才能创建它。如果它未注册(也未在清单文件中),则无法使用CoCreateInstance创建它,因为注册表(或清单)告诉CoCreateInstance要加载哪个DLL。

但是,您可以使用LoadLibraryGetProcAddressDllGetClassObjectIClassFactory::CreateInstance创建它。

(请注意,以这种方式创建的对象将不会获得任何COM+服务,并且您无法创建不兼容创建线程的进程之外的对象,或者其线程模型与创建线程不兼容的对象。如果您不向COM请求帮助,则这些问题会变成您自己的问题。)

CoCreateInstance提供的服务是定位正确的DLL以调用LoadLibrary,并为您调用其他函数。(并检查线程模型是否兼容,创建在适当的线程上,并使用CoMarshalInterthreadInterfaceInStream/CoUnmarshalInterfaceAndReleaseStream在不兼容时将接口进行编组。这听起来很麻烦,但如果您只有一个STA线程,则几乎可以忽略所有问题。)

像这样的代码应该能解决问题:

// Really you should break this up int GetClassFactoryFromDLL, then reuse the class factory.
// But this is all from memory...
// Load CLSID_MyID, from DLL pszMyDllPath
typedef HRESULT __stdcall (*_PFNDLLGETCLASSOBJECT)(
  __in   REFCLSID rclsid,
  __in   REFIID riid,
  __out  LPVOID *ppv
) PFNDLLGETCLASSOBJECT;

HRESULT CreateInstanceFromDll(LPCTSTR pszMyDllPath, CLSID clsidMyClass, IUknown** ppunkRet)
{
    // Handle bad callers
    *ppunkRet = NULL;
    HANDLE hDLL = LoadLibrary(pszMyDllPath);
    if(hDLL == NULL)
    {
        // Failed to load
        return HRESULT_FROM_NTSTATUS(GetLastError());
    }
    PFNDLLGETCLASSOBJECT pfnDllGetClassObject = GetProcAddress(hDLL);
    if(pfnDllGetClassObject == NULL)
    {
        // Not a COM dll
        HRESULT hrRet = HRESULT_FROM_NTSTATUS(GetLastError());
        FreeLibrary(hDLL);hDLL = NULL;
        return hrRet;
    }
    IClassFactory* pClassFactory = NULL;
    HRESULT hr = pfnDllGetClassObject(clsidMyClass, IID_IClassFactory, &pClassFactory);
    if(FAILED(hr)){
        FreeLibrary(hDLL);hDLL = NULL;
        return hr;
    }
    hr = pClassFactory->CreateInstance(NULL, IID_IUnknown, &ppunkRet);
    pClassFactory->Release();
    if(FAILED(hr))
    {
        *ppunkRet = NULL;
        FreeLibrary(hDLL);
        return hr;
    }
    return hr;
}
注意:这将允许您创建对象。但是,如果您调用的对象本身依赖于被注册,它将无法正确地运行。
具体而言,许多IDispatch实现依赖于类型库。如果特定对象是这种情况,则GetIDsOfNamesGetTypeInfo将失败,并且您将无法使用该对象的迟绑定方法。这包括在C#中使用dynamic和像Python这样的动态语言。
其他方法,如双接口方法和未继承自IDispatch的接口,即使IDispatch方法不起作用,也可能起作用。
底线是:对于无需注册的COM工作,要实例化的对象不能依赖于其自身的注册。

抱歉误导了您,我说的是dll。当我执行“regsvr32 AutoItX3.dll /u”时,我无法调用创建对象上的任何方法。它是不可用的。当我执行“regsvr32 AutoItX3.dll”时,一切正常。但在这种情况下,我不需要提供的代码,因为该代码不能完全回答“如何在dot net中使用无需注册的COM dll”以获得有效的COM对象。 - Dzmitry Lahoda
我发现,当AutoIt未注册时,执行反射调用会失败。System.Runtime.InteropServices.COMException (0x8002801D): Library not registered. (Exception from HRESULT: 0x8002801D (T YPE_E_LIBNOTREGISTERED)) at System.RuntimeType.InvokeDispMethod(...) 。我尝试从C#或IronPython中调用方法。在使用Interop DLL时,C#中的事情运行良好。但是IronPython仍然失败。因此,在我的情况下,答案正确,直到通过Interop DLL而不是反射进行调用。 - Dzmitry Lahoda
1
@DzmitryLahoda,对于未注册的对象,如果实现使用类型库,则可能会发现诸如IDispatch :: GetIDsOfNames或GetTypeInfo等方法失败(例如用于动态对象或动态语言的后期绑定)。但是,如果已执行早期绑定,则不应调用GetIDsOfNames。特别是如果涉及的接口不是IDispatch(例如您直接调用双重接口方法而不是通过Invoke),则应该可以工作。底线:您调用的对象本身不能依赖于其自身的注册! - Ben
@DzmitryLahoda,你发现的东西非常有趣。我已经扩展了答案,包括你观察到的故障原因的信息。 - Ben
这看起来像是我的情况。此外,我直接在C#中调用IDipatch::GetIDsOfNames方法进行了验证。没有注册 - 失败。 - Dzmitry Lahoda
显示剩余3条评论

2

对于那些搜索“在dot net中无需注册的COM dll”的人,我能够在这个答案https://dev59.com/f2w15IYBdhLWcg3wIYO_#19996424中演示如何以无需注册方式从C#中使用AutoItX COM对象。创建和使用其他无需注册的对象类似。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接