2

I have to use a VB (COM) DLL in a C++ DLL. I figured out how to access the VB (COM) DLL from the C++ DLL and it works.

Now I've got the problem that I have to use isolated COM/reg-free COM because I can't register the DLL on every PC it has to be used on.

I figured out to use manifest-files to achieve this but I can't get it to work and I don't know what is wrong.

I have a VB DLL called AccConnVB.dll with the following AccConnVB.manifest file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
 manifestVersion="1.0">
<assemblyIdentity
        type="win32"
        name="AccConnVB"
        version="1.0.0.0" />
<clrClass
        clsid="{70da7ef0-1549-4b27-9b00-ade5f37aecbe}"
        progid="AccConnVB.AccConnVB"
        threadingModel="Both"
        name="AccConnVB.tables" >
</clrClass>
</assembly>

And a C++ DLL called AccConn.dll with the following AccConn.manifest file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
<assemblyIdentity
        type = "win32"
        name = "AccConn"
        version = "1.0.0.0" />
<dependency>
        <dependentAssembly>
                    <assemblyIdentity
                                type="win32"
                                name="AccConnVB"
                                version="1.0.0.0" />
        </dependentAssembly>
</dependency>
</assembly>

My C++ DLL #defines _WIN32_DCOM in its stdafx.h and #imports the AccConnVB.tlb with no_namespace.

The following is a method from the C++ DLL:

JNIEXPORT jint JNICALL Java_natives_AccessConnection_refreshImportZwei
(JNIEnv *env, jclass jobj, jstring jDatabase){
  jint result;
  CComBSTR database;

  const char* nativeDatabase = env->GetStringUTFChars(jDatabase,0);
  database.Append((LPCSTR) nativeDatabase);

  CoInitializeEx(NULL,COINIT_MULTITHREADED);
  {
      ITablesPtr ptr;
      HRESULT hr = ptr.CreateInstance(__uuidof(tables));
      if (SUCCEEDED(hr))
      {
          result = (jint) ptr->refreshImportZwei(BSTR(database));
      }
  }
  CoUninitialize();
}

I made sure that everything works with a registered AccConnVB.dll, but using it on a computer where it is not registered fails.

The manifest files are embedded through executing mt.exe in cmd.exe with the following line: mt -manifest H:\AccConnVB.manifest -outputresource:H:\AccConnVB.dll;#1, for AccConn.dll and AccConn.manifest respectively.

Nothing else is set, when accessing AccConn.dll the AccConnVB.dll, the AccConn.manifest and the AccConnVB.manifest are in the same folder.

I followed the walkthrough here and tried some variations of it but nothing worked.

Thank you all very much in advance!

Attachment 1:

AccConn.manifest:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
<assemblyIdentity
     type = "win32"
    name = "AccConn"
    version = "1.0.0.0" />
<file name="AccConnVB.dll">
<comClass
        clsid="{70da7ef0-1549-4b27-9b00-ade5f37aecbe}" 
    tlbid="{1CA12FB4-4A5C-41FF-A508-DCC6CE0D26CD}"
    progid="AccConnVB.tables" />
<typelib
    tlbid="{1CA12FB4-4A5C-41FF-A508-DCC6CE0D26CD}"
    version="1.0" helpdir="" />
</file>
<dependency>
    <dependentAssembly>
                <assemblyIdentity
                            type="win32"
                            name="AccConnVB"
                            version="1.0.0.0" />
    </dependentAssembly>
</dependency>
</assembly>

AccConnVB.manifest:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
 manifestVersion="1.0">
<assemblyIdentity
    type="win32"
    name="AccConnVB"
    version="1.0.0.0" />
</assembly>

Attachment 2:

OfficeConn.manifest - C++-DLL - (changed the name):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<file name="OfficeConn.dll" hashalg="SHA1">
    <comClass clsid="{2C0D73B5-7AA4-4D17-970D-042804E206B2}" tlbid="{DB27F83B-DD8E-4AD8-A6A3-9232A9C1692C}">
    </comClass>
    <typelib tlbid="{DB27F83B-DD8E-4AD8-A6A3-9232A9C1692C}" version="1.0" helpdir="" flags="HASDISKIMAGE">
    </typelib>
</file>
<comInterfaceExternalProxyStub name="IOffice" iid="{19485BDA-0BAE-3527-8F9B-C90B43746B03}" tlbid="{DB27F83B-DD8E-4AD8-A6A3-9232A9C1692C}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}">
</comInterfaceExternalProxyStub>
<comInterfaceExternalProxyStub name="_offClass" iid="{1FA5D7FC-1CAE-49E0-A99E-B97E8FE3466E}" tlbid="{DB27F83B-DD8E-4AD8-A6A3-9232A9C1692C}" proxyStubClsid32="{00020424-0000-0000-C000-000000000046}">
</comInterfaceExternalProxyStub>
</assembly>

OfficeConnVB.manifest - VB-DLL - (changed the name):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="OfficeConnVB" version="1.0.0.0" publicKeyToken="38d072ba2818144d" processorArchitecture="msil">
</assemblyIdentity>
<clrClass clsid="{2c0d73b5-7aa4-4d17-970d-042804e206b2}" progid="OfficeConnVB.offClass" threadingModel="Both" name="OfficeConnVB.offClass" runtimeVersion="">
</clrClass>
<clrSurrogate clsid="{453B8C28-201B-3705-8CF1-C492C7B259EA}" name="Microsoft.Office.Interop.Outlook.OlDefaultFolders">
</clrSurrogate>
<clrSurrogate clsid="{B5181856-6837-3E65-AF7B-5020DD408339}" name="Microsoft.Office.Interop.Outlook.OlItemType">
</clrSurrogate>
<clrSurrogate clsid="{ECE70AEA-B928-3392-AE59-01373B29D3DA}" name="Microsoft.Office.Interop.Outlook.OlImportance">
</clrSurrogate>
<clrSurrogate clsid="{D74B5B88-8D75-3D21-A9BA-F6DBDC905F75}" name="Microsoft.Office.Interop.Word.WdSaveOptions">
</clrSurrogate>
<file name="OfficeConnVB.dll" hashalg="SHA1">
</file>
</assembly>

2 Answers2

4

You are making an oddly common mistake, expecting Windows to solve the chicken-and-egg problem. A brief word about the way manifest works might help.

Windows loads the content of a manifest when it load an executable file, the entries are added to an internal lookup table. Whenever an application first asks to create a COM object, underlying call is CoCreateInstance() which supplies the CLSID guid, it first consults that lookup table. If the CLSID is a match then the entry tells it what DLL must be loaded. If there is no match then it falls back to the traditional registry lookup.

The chicken-and-egg is that your DLL didn't get loaded yet. So its manifest entries are not yet active.

The egg must come first, the <clrClass> entry needs to go into the manifest you embed in the C++ executable. Like this:

  <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity type="win32" name="AccConn" version="1.0.0.0" />
    <file name="foobar.dll"/>
    <clrClass ...etc>
    </clrClass>
  </assembly>

MSDN article is here.

transistor1
  • 2,805
  • 23
  • 42
Hans Passant
  • 873,011
  • 131
  • 1,552
  • 2,371
  • Thank you very much for your answer! I removed the entry from the AccConnVB.dll manifest to the AccConn.dll manifest and embedded them again with mt.exe. Sadly I encountered the same problem, it still does not find the AccConnVB.dll. My manifest files look like this now (see Attachment 1 in question). –  Jan 14 '15 at 17:56
  • 1
    You are missing the `` node, the clrClass needs to appear inside of it. Post updated. The `` node is missing too btw, required if you use the component from a thread. – Hans Passant Jan 14 '15 at 18:22
  • Thanks for your answer. I put the comClass node in a file node and added a typelib node in this file node. Still I can't get it to run on another computer. I updated attachment 1 in question to reflect my current manifest files. I embedded the manifest file for the C++ dll with visual studio, sadly vs2010 doesn't offer me this option for the vb dll. Maybe I embed the manifests wrong? –  Jan 15 '15 at 14:56
  • This does not seem to work. clrClass is an unrecognized element to the mt.exe program that ships with V7.0, V7.1, and V8.1 Windows SDKs. If you try to compile, the manifest tool will output: manifest authoring warning 81010002: Unrecognized Element "clrClass" in namespace "urn:schemas-microsoft-com:asm.v1". – Derek Mar 18 '15 at 15:36
  • That's because the [`clrClass` element](https://msdn.microsoft.com/en-us/library/aa375635(v=vs.85).aspx) goes right under the root `assembly` element, not the `file` element. Dependency manifests are part of the activation context, so CLSID lookup will search through them. The `clrClass` assumes the manifest is named after the assembly to load, while `comClass` needs to be inside a `file` element, but this `file` element can be in a dependency manifest. The stated chicken-and-egg issue is moot. – acelent Mar 24 '15 at 21:59
  • plus 1 for correctly identifying the chicken and the egg in the stated analogy. – Shiva Dec 19 '20 at 01:19
0

You should use resource id #2 for DLLs, so change your execution of mt.exe to use #2 instead of #1. At least it is so for native DLLs, I'm not sure it has to be so for managed DLLs, so try it out.

You should move everything about the VB.NET DLL, except the dependency, to the VB.NET manifest, as that's the right place to keep that information.

You should use clrClass nodes, instead of comClass nodes, for .NET COM classes.

You may need to generate a type library (tlbexp.exe), and declare it in the manifest (optionally, you may also embed it in the DLL, but I advise against this if the DLL itself is big), if you want your classes and interfaces to be inspectable and marshalable by type library consumers, e.g. VBA, VC++ #import directive and code generators in general. On top of this, you may need to declare exported interfaces in the manifest, if you create objects outside VB.NET that implement its exported interfaces and which must me marshaled.

If your library is the sole generator of objects that implement the exported interfaces used in the same apartment, you don't need to generate nor declare a type library. I think .NET implements IMarshal, so this particular scenario works without a type library.


AccConn.manifest:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="AccConn"
                    version="1.0.0.0" />
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32"
                        name="AccConnVB"
                        version="1.0.0.0" />
    </dependentAssembly>
  </dependency>
</assembly>

AccConnVB.manifest:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="AccConnVB"
                    version="1.0.0.0" />
  <file name="AccConnVB.dll" />
  <clrClass clsid="{70da7ef0-1549-4b27-9b00-ade5f37aecbe}"
            name="AccConnVB.tables"
            progid="AccConnVB.AccConnVB"
            threadingModel="Both" />
</assembly>

EDIT: According to your question's update.

You don't have a dependency element on the C++ manifest, so it doesn't know about the VB.NET assembly COM-wise without registration.

It seems you're declaring the VB.NET type library in the C++ manifest.

It seems you're declaring a comClass in the C++ manifest that's declared as a clrClass in the VB.NET manifest. Don't do this!

acelent
  • 7,439
  • 17
  • 38
  • Thanks for your answer. I had to focus on other tasks until now. I tried using the above manifest files but the C++ dll still does not find the VB dll... I also tried using the mt.exe in the Microsoft SDKs to generate the manifests. I attached the current manifests in my question. I embedded the manifests with mt.exe in both dlls with #1/#1 and #2/#2 as well as #1/#2 and #2/#1... –  Mar 24 '15 at 16:10