3

I'm building a library that generates a user-agent string that reports some nifty data like OS version and currently installed .NET Framework versions. I'm curious:

Is it possible to detect programmatically which language is calling my library? Or is the source language completely opaque once it's compiled into CIL?

Community
  • 1
  • 1
Nate Barbettini
  • 51,256
  • 26
  • 134
  • 147
  • 1
    Since an application may be assembled from pieces in several languages, what would be the benefit of knowing the language of the immediate caller? – HABO Oct 16 '15 at 02:40
  • @HABO Curiosity, mostly. I agree that the benefit is iffy since the immediate caller may not even be representative of the application at large. – Nate Barbettini Oct 16 '15 at 03:23

2 Answers2

5

Edit: I turned this into a small library that encapsulates a few heuristics and makes it easy to call.

I came up with a heuristic that seems to work well enough for my own needs.

@Don's answer and these questions gave me some hints:

Caveats:

  • Only differentiates between VB.NET and C#, not any other CLR languages. Assumes C# if it doesn't have enough evidence of VB.
  • It's making an educated guess, so the chance of false positives is > 0.
  • Some of the hints are based on compiler implementation details, which could change.
  • This seems to work on Mono too, but YMMV.
  • It's expensive reflection, so in real life you'd want to wrap it in a Lazy<> or some other mechanism to ensure it's only called once.
  • As @HABO mentioned, this may or may not be very useful information. I was mostly curious to see if it could be done.

var lang = DetectAssemblyLanguage(Assembly.GetCallingAssembly());

public static string DetectAssemblyLanguage(Assembly assembly)
{
    var referencedAssemblies = assembly
        .GetReferencedAssemblies()
        .Select(x => x.Name);

    var types = assembly
        .GetTypes();

    // Biggest hint: almost all VB.NET projects have a
    // hidden reference to the Microsoft.VisualBasic assembly
    bool referenceToMSVB = referencedAssemblies.Contains("Microsoft.VisualBasic");

    // VB.NET projects also typically reference the special
    // (YourProject).My.My* types that VB generates
    bool areMyTypesPresent = types.Select(x => x.FullName).Where(x => x.Contains(".My.My")).Any();

    // If a VB.NET project uses any anonymous types,
    // the compiler names them like VB$AnonymousType_0`1
    bool generatedVbNames = types.Select(x => x.Name).Where(x => x.StartsWith("VB$")).Any();

    // If a C# project uses dynamic, it'll have a reference to Microsoft.CSharp
    bool referenceToMSCS = referencedAssemblies.Contains("Microsoft.CSharp");

    // If a C# project uses any anonymous types,
    // the compiler names them like <>f__AnonymousType0`1
    bool generatedCsNames = types.Select(x => x.Name).Where(x => x.StartsWith("<>")).Any();

    var evidenceForVb = new bool[] 
    {
        referenceToMSVB,
        myTypesPresent,
        vbGeneratedNames
    };

    var evidenceForCsharp = new bool[] {
        true, // freebie. ensures ties go to C#
        referenceToMSCS,
        csGeneratedNames
    };

    var scoreForVb = evidenceForVb.Count(x => x)
                     - evidenceForCsharp.Count(x => x);

    // In the case of a tie, C# is assumed
    return scoreForVb > 0
        ? "vb"
        : "cs";
}
Community
  • 1
  • 1
Nate Barbettini
  • 51,256
  • 26
  • 134
  • 147
  • That's an interesting approach, but a C# assembly can reference `Microsoft.VisualBasic` (it contains a few useful types such as `TextFieldParser`), and a VB assembly could theoretically reference `Microsoft.CSharp` (much less likely IMO). – Thomas Levesque Mar 12 '17 at 02:30
  • @ThomasLevesque I agree, this can produce misleading results in a lot of cases. It also doesn't work well (or at all) on .NET Core code. I'm mostly leaving this here as an interesting exercise. :) – Nate Barbettini Mar 13 '17 at 15:26
3

Unfortunately this isn't possible, as you have identified by mentioning the CIL.

I can think of two possible solutions:

  1. If however the PDB file of the corresponding .NET dll (assembly of calling code) is available, then you can inspect this file to determine the language used. However that's a big "IF" and I'd stay away from such a solution.
  2. Inspect the assemblies referenced by the calling assembly. Sometimes (rarely) there could be language specific assemblies referenced.
Don
  • 6,632
  • 3
  • 26
  • 34