GAC hell

F

Fredo

I'm writing a wrapper for the VBScript and JScript engines in .NET. I'm
trying to add a little uniformity where Microsoft has some divergence.

With the JScript engine, you can add references and provide a path to where
each DLL is. With the VBScript engine, you can't do that. For DLLs that
aren't in the GAC, you have to provide a directory where all the referenced
..DLLs are.

To keep the user of the wrapper from having to deal with this, for the
VBScript I want to add some code to copy all the references that aren't in
the GAC, to a temporary directory. The problem is, how would one
programmatically determine which ones are in the GAC and which ones aren't?
I know I can go snooping around in \Windows\assembly\GAC, but I wanted ot
know if there was a better way of doing this that doesn't require me digging
through directories, checking DLL versions and such. I don't know precisely
how .NET normally does this and unfortunately, I can't see how the VBScript
engine deals with it since that part of the framework code appears to be
unmanaged.

Thanks.
 
W

Willy Denoyette [MVP]

Fredo said:
I'm writing a wrapper for the VBScript and JScript engines in .NET. I'm
trying to add a little uniformity where Microsoft has some divergence.

With the JScript engine, you can add references and provide a path to
where each DLL is. With the VBScript engine, you can't do that. For DLLs
that aren't in the GAC, you have to provide a directory where all the
referenced .DLLs are.

To keep the user of the wrapper from having to deal with this, for the
VBScript I want to add some code to copy all the references that aren't in
the GAC, to a temporary directory. The problem is, how would one
programmatically determine which ones are in the GAC and which ones
aren't? I know I can go snooping around in \Windows\assembly\GAC, but I
wanted ot know if there was a better way of doing this that doesn't
require me digging through directories, checking DLL versions and such. I
don't know precisely how .NET normally does this and unfortunately, I
can't see how the VBScript engine deals with it since that part of the
framework code appears to be unmanaged.

Thanks.

The scripting engine is a COM client and COM uses the registry to locate the
COM server DLL(s) that contains the components as referred to by the
scripts.
Now, for .NET components you want to expose to COM clients, you have a
number of options when registering your assembly:
1. you can run regasm without the /codebase command line switch, and you
have to install the assembly in the GAC, or
2. you can run regasm with the /codebase switch, and you don't have to
install the assembly in the GAC, or
3. you can run regasm without the /codebase switch and install the assembly
in the same directory as the client.

Note that the COM client in your case is the scripting engine (cscript.exe)
which is found in %windir%\system32 (or %windir%\syswow64), so installing
the assemblies with the client is excluded, which leaves you with option 1
and 2. Installing in the GAC is
only a viable option if you have a small number of assemblies used by
different clients and client types. Option 2 is preferable when you have a
number of assemblies used by a single client (or a limited number of
clients). If your component is only used by scripting clients (well.. there
is only a single one) then you should definitely go for 2.

Willy.
 
F

Fredo

Willy,

Sorry, I think you're misunderstanding. I'm talking about the scripting
engines: Microsoft.JScript, Microsoft.Vsa, and Microsoft.VisualBasic.Vsa
managed engines, not the Windows scripting host or cscript.exe.

These engines work with managed objects, much in the way WSH works with COM
objects, but they're different engines, at least the JScript engine is. The
entire (or a vast majority of the) JScript interpreter is managed code. The
VBScript seems to be doing a lot of stuff in unmanaged code, so for all I
know, they're somehow using WSH underneath, but I doubt it since I don't see
how it would deal with managed objects.

So my question has to do with the resolution of referenced managed
assemblies, not COM components.
 
W

Willy Denoyette [MVP]

Hmm... So, you are talking about the CodeDom providers, right? In that case
I don't see where "VBScript and WSH" comes from, there is no language
provider for unmanaged languages in the framework. All there is, are the
language providers for CSharp, VisualBasic, J#, C++ and JScript, but all are
producing pure managed CSharp, VB.NET, J#, C++ and JScript.NET code.
Also all CodeDom providers share a common interface, so I don't know exactly
what problem you have with "adding references".
Mind to give an example that illustrates your issue?

Willy.
 
F

Fredo

Willy,

What you're referring to are the CodeDom providers. What I'm talking about
is the scripting engines. These are different things as well.

Here's an article that discuesses VSA scripting:

http://www.developerfusion.co.uk/show/4675/

More specifically, adding references which is discussed on this page:

http://www.developerfusion.co.uk/show/4675/3/

There's a difference in the way VBScript and JScript handle assembly
references. JScript will allow you to pass a path to the assembly. VBScript
requires that all referenced assemblies that aren't in the GAC, be located
in a single directory and you then have to call
IVsaEngine.SetOption("AssemblyBase", referencesDirectory) to set the
directory.

So, my question is: I need to figure out which references are GAC references
and which ones aren't, so I can copy the ones that aren't GAC references
into a temporary directory for the script to access them.
 
W

Willy Denoyette [MVP]

Don't know what version of VS and the Framework you are using, but the
article you are referring to is old hat (the article was written back in
2002 and was based on v1.0 of the framework), the namespace the author is
talking about was deprecated a long time ago (since VS2005 and V2 of the
framework).
VSA was deprecated back in 2004 in favor of VSTA, something the author was
not aware of (see the articles FAQ).
The V2 way to script your application, that is generate/compile/run scripts,
is by using the CodeDom providers (included with the framework for C#,
managed C++, JS.NET and VB.NET). Other languages are supported through the
VSTA SDK, but this implies licensing the SDK and the Runtime.
The sample project included with the article from the same author here
http://www.123aspx.com/redir.aspx?res=32808 , compiles with a bunch of
warnings and doesn't run after the build.
Also, the author talks about a non existing language VBScript.NET, which he
dislikes (<quote ..I tended to dislike ..VBScript /quote>).
Take a look at the demo sample included with the article, the author
includes two (what he calls) script files (Scripts-DebugWrite.js and
Scripts-DebugWrite.vb), but both are *managed* language code , the .js code
being JScript.NET and the .vb code being VB.NET. Both have nothing in common
with VBScript or JScript, apart some of the syntaxes.

So, please if you have a small but complete sample that uses the (obsolete)
Microsoft.Vsa namespace to generate/compile/Run "VBScript" and "JScript"
scripting code (that is interpreted scripting code), feel free to post it
here.

Willy.
 
F

Fredo

Willy,

This is the engine I'm using. I'm aware it's deprecated. Be that as it may,
it's the one I'm choosing to use because it meets my needs on several
levels.

But we've gotten so far off topic here, we're nowhere near my original
question anymore, which had to do with determining which referenced
assemblies are in the GAC and which are not.
 
W

Willy Denoyette [MVP]

The only way to get at the GAC is by calling into the unmanaged fusion
API's.
Following is a complete sample that illustrates how you can perform a name
lookup.
Beware that the GAC uses fully qualified assembly names to store assembly
references, because you don't know whether an assembly resides in the GAC,
you also don't have a FQAN, this may result in false positives!

Willy.

// Note that this requires V2 of the framework.
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace GacStuff
{
internal class GacApi
{
[DllImport("fusion.dll")]
internal static extern int CreateAssemblyCache(
out IAssemblyCache ppAsmCache,
int reserved);

}
// GAC Interfaces - IAssemblyCache. Non used vtable entries declared as
dummy.
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae")]
internal interface IAssemblyCache
{
int Dummy1();
[PreserveSig()]
int QueryAssemblyInfo(
int flags,
[MarshalAs(UnmanagedType.LPWStr)]
String assemblyName,
ref ASSEMBLY_INFO assemblyInfo);
int Dummy2();
int Dummy3();
int Dummy4();
}
[StructLayout(LayoutKind.Sequential)]
internal struct ASSEMBLY_INFO
{
public int cbAssemblyInfo;
public int assemblyFlags;
public long assemblySizeInKB;
[MarshalAs(UnmanagedType.LPWStr)]
public String currentAssemblyPath;
public int cchBuf;
}

class Program
{
static void Main()
{
Console.WriteLine(QueryAssemblyInfo("System"));
}
// If assemblyName is not fully specified, a random matching will be
returned!!!!
public static String QueryAssemblyInfo(String assemblyName)
{
ASSEMBLY_INFO assembyInfo = new ASSEMBLY_INFO ();
assembyInfo.cchBuf = 512;
assembyInfo.currentAssemblyPath = new String('\0',
assembyInfo.cchBuf) ;
IAssemblyCache assemblyCache = null;
// Get IAssemblyCache pointer
int hr = GacApi.CreateAssemblyCache(out assemblyCache, 0);
if (hr >= 0)
hr = assemblyCache.QueryAssemblyInfo(1, assemblyName, ref
assembyInfo);
else
Marshal.ThrowExceptionForHR(hr);
return assembyInfo.currentAssemblyPath;
}
}
}
//-------------------------------
 
F

Fredo

Sweet! Thanks Willy.


Willy Denoyette said:
The only way to get at the GAC is by calling into the unmanaged fusion
API's.
Following is a complete sample that illustrates how you can perform a name
lookup.
Beware that the GAC uses fully qualified assembly names to store assembly
references, because you don't know whether an assembly resides in the GAC,
you also don't have a FQAN, this may result in false positives!

Willy.

// Note that this requires V2 of the framework.
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace GacStuff
{
internal class GacApi
{
[DllImport("fusion.dll")]
internal static extern int CreateAssemblyCache(
out IAssemblyCache ppAsmCache,
int reserved);

}
// GAC Interfaces - IAssemblyCache. Non used vtable entries declared as
dummy.
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae")]
internal interface IAssemblyCache
{
int Dummy1();
[PreserveSig()]
int QueryAssemblyInfo(
int flags,
[MarshalAs(UnmanagedType.LPWStr)]
String assemblyName,
ref ASSEMBLY_INFO assemblyInfo);
int Dummy2();
int Dummy3();
int Dummy4();
}
[StructLayout(LayoutKind.Sequential)]
internal struct ASSEMBLY_INFO
{
public int cbAssemblyInfo;
public int assemblyFlags;
public long assemblySizeInKB;
[MarshalAs(UnmanagedType.LPWStr)]
public String currentAssemblyPath;
public int cchBuf;
}

class Program
{
static void Main()
{
Console.WriteLine(QueryAssemblyInfo("System"));
}
// If assemblyName is not fully specified, a random matching will
be
returned!!!!
public static String QueryAssemblyInfo(String assemblyName)
{
ASSEMBLY_INFO assembyInfo = new ASSEMBLY_INFO ();
assembyInfo.cchBuf = 512;
assembyInfo.currentAssemblyPath = new String('\0',
assembyInfo.cchBuf) ;
IAssemblyCache assemblyCache = null;
// Get IAssemblyCache pointer
int hr = GacApi.CreateAssemblyCache(out assemblyCache, 0);
if (hr >= 0)
hr = assemblyCache.QueryAssemblyInfo(1, assemblyName, ref
assembyInfo);
else
Marshal.ThrowExceptionForHR(hr);
return assembyInfo.currentAssemblyPath;
}
}
}
//-------------------------------

Fredo said:
Willy,

This is the engine I'm using. I'm aware it's deprecated. Be that as it
may, it's the one I'm choosing to use because it meets my needs on
several levels.

But we've gotten so far off topic here, we're nowhere near my original
question anymore, which had to do with determining which referenced
assemblies are in the GAC and which are not.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

ReferencedAssemblies and the gac 4
Debug + GAC 7
Registering in GAC 2
About GAC 2
Gac to Private assembly 3
Assembly referencing problem with creating third party plug-in 2
.NET and COM 4
Deployment problems 2

Top