Sunday, February 27, 2011

Book — Summer of the Ubume by Natsuhiko Kyogoku

The first novel by Natsuhiko Kyogoku, and the one to which Mouryou no Hako is a sequel.

The prose is rendered nicely into English, while retaining some of the stylistics tics of the original (no sentence broken between pages, at least for the first 200-odd pages); there are a few translators notes where allusions are made to various folk-tales. The most obtrusive translation quirks are where dates are concerned

The book he was reading was something from the Edo Period [1603-1868]...

but those are quickly past after the opening chapter.

Many of the cast of Mouryou no Hako appear in this first novel -- many more than I had expected. And Kyogoku seems to have a thing about bizarre hospitals.

By the end, this is clearly a strong first novel -- and it's also clear why the next book he wrote about the same cast was the one that got the adaptations : this one relied just a bit too much on the power of coincidence (three different characters suffering the same hysterical blindness, for one), and on having some of the main cast involved at some point in the past with one or other of the major players in the mystery.

Still, if you enjoyed the Mouryou no Hako anime, this is the nearest thing you'll get to having another fix of the same in English language.

Signs of Spring

Sunlight through the curtains and birdsong woke me at just after 07:00 today. In the garden, the snowdrops are fading after having gone strong all month, and the forsythia has been out for over a week. Purple crocuses are joining the yellow ones; and the primroses in shadier parts are flowering, following the ones in the sunny bed by the greenhouse wall, which have been out for weeks.

Monday, as most of the office were out karting, I took the chance to cycle to work, and bunk off ahead of dusk and rain; but it'll be a couple of weeks yet before starting to do it around the normal working day.

Saturday, February 19, 2011

Mono 2.10, XBuild, F# -- first attempts

Following the announcement that F# and the Iron languages are being bundled with Mono 2.10 for Mac and Linux, I thought it worth taking the system for a spin on Windows as well. This is a record of my experiments, and are nowhere near a complete HOW-TO as yet (they more closely resemble a HOW NOT TO at the moment).

The easy bit is as per Zor's blog, here --

  • copy C:\program files\MSBuild\FSharp to C:\Program Files\Mono-2.10\lib\mono\xbuild
  • Get the mono strong-name key https://github.com/mono/mono/blob/master/mcs/class/mono.snk
  • Now this is voodoo -- without going & 'C:\Program Files\Mono-2.10\bin\sn.bat' -R .\FSharp.Core.dll .\mono.snk on a copy of the F# runtime, the GACing step fails : yet the operation doesn't actually change the embedded key (going as far as ILDasm/ILAsm seems a bit extreme!)
  • As administrator, & 'C:\Program Files\Mono-2.10\bin\mono.exe' 'C:\Program Files\Mono-2.10\lib\mono\2.0\gacutil.exe' -i '.\FSharp.Core.dll', and then copy the original Fsharp.core.sigdata and .optdata alongside
  • in C:\Program Files\Mono-2.10\lib\mono\3.5\Microsoft.Common.targets undo the commenting out of _ComputeNonExistentFileProperty
  • In the copy of the Microsoft.Sharp.targets file, comment out the <ItemGroup Remove="..."> clauses after Fsc task in the CoreCompile target, and the two in CreateManifestResourceNames (might there be a CreateItem equivalent? Haven't tried yet).

At this point, pure F# projects for VS2008 will build happily, so long as you don't have any fancy modern task attributes in BeforeBuild or AfterBuild (e.g. Error executing task Copy: Task does not have property "OverwriteReadOnlyFiles" defined if you try to use that).

Where I have yet to manage to persuade the build process to work is for projects with embedded resources (such as Glade XML definitions for a UI). These yield up

        Target CopyNonResxEmbeddedResources:
C:\PROGRA~1\Mono-2.10\lib\mono\3.5\Microsoft.Common.targets: error : 
You must specify DestinationFolder or DestinationFiles attribute.
        Task "Copy" execution -- FAILED

which may possibly relate to some of the <ItemGroup Remove="..."> operations not being carried out. Certainly, some more MSBuild hackery will be required to track down what is going on and make sure that both sides of the copy operation match up -- so that either the source is empty, and no copy is attempted, or so that the destination doesn't end up null.

Support for VS2010 projects isn't there yet -- after sn -R (with equally little effect on the strong-name given by sn -Tp) and GACing the 4.0 version of FSharp.Core.dll, trying to build a VS2010 project yields up the cryptic message, sometimes as an error, sometimes as a warning, that "Operation is not valid due to the current state of the object" -- even with /verbosity:diagnostic turned on no more detail is divulged. And having the 4.0 version GAC'd interferes with the 2.0 version in the GAC -- the later version is selected by preference and then fails for being incompatible with the 2.0-level mscorlib when building a vanilla VS2008 project with just a simple reference (without explicit version information like <Reference Include="FSharp.Core" />) to the F# runtime.

Friday, February 18, 2011

ILSpy and F#

Suddenly a new contender in the post-Reflector age -- ILSpy, emerging from the SharpDevelop stable.

So I decided to give it a whirl with the same F# code as with the simple tests I made last week with Cecil.Decompiler -- a few extension methods for Option

module Augment =
type Microsoft.FSharp.Core.Option<'T> with
static member filter (f : 'T -> bool) (x : option<'T>) =
match x with
| Some v when f(v) -> Some v
| _ -> None
static member getOrElse (fallback : 'T) (x : option<'T>) = defaultArg x fallback
static member select (f : 'T -> bool) (x : 'T) =
if f(x) then Some x
else None
static member nullable (x : 'a when 'a : null) : option<'a> =
if x <> null then Some x
else None
view raw gistfile1.fs hosted with ❤ by GitHub

The first thing I notice is that I can't copy and paste code from the decompilation screen -- I have to save it, and which point I get a button to open Explorer in the same directory as I saved. The C# code generated looks sane

public static FSharpOption<T> Option`1.select.Static(FSharpFunc<T, bool> f, T x)
{
if (f.Invoke(x))
{
}
else
{
goto Block_3;
}
return FSharpOption<T>.Some(x);
Block_3:
return null;
}
view raw gistfile1.cs hosted with ❤ by GitHub

Unlike Cecil, this gets the F# branching right in representing the highlighted section

IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: callvirt !1 Microsoft.FSharp.Core.FSharpFunc`2<T,System.Boolean>::Invoke(!0)
IL_0008: brfalse.s IL_000c
IL_000a: br.s IL_000e
IL_000c: br.s IL_0015
IL_000e: ldarg.1
IL_000f: call Microsoft.FSharp.Core.FSharpOption`1<!0> Microsoft.FSharp.Core.FSharpOption`1<T>::Some(!0)
IL_0014: ret
IL_0015: ldnull
IL_0016: ret
view raw gistfile1.cs hosted with ❤ by GitHub

It also handles compiler generated temporaries just fine -- albeit with the same 'C'-style if

public static FSharpOption<T> Option`1.filter.Static(FSharpFunc<T, bool> f, FSharpOption<T> x)
{
FSharpOption<T> fSharpOption = x;
if (fSharpOption)
{
FSharpOption<T> fSharpOption2 = fSharpOption;
T t = fSharpOption2.Value;
if (f.Invoke(t))
{
goto Block_4;
}
}
return null;
Block_4:
return FSharpOption<T>.Some(fSharpOption2.Value);
}
view raw gistfile1.cs hosted with ❤ by GitHub

And ૼ bonus ૼ while Cecil choked on this C# code

private static void DumpAssembly(string path)
{
var assembly = AssemblyDefinition.ReadAssembly(path);
var pdbpath = Path.ChangeExtension (path, ".pdb");
var provider = new PdbReaderProvider();
var reader = provider.GetSymbolReader(assembly.MainModule, pdbpath);
assembly.MainModule.ReadSymbols(reader);
assembly.MainModule.Types.SelectMany(x => x.Methods).ToList().ForEach(Decompile);
}
view raw gistfile1.cs hosted with ❤ by GitHub

ILSpy comes up with

private static void DumpAssembly(string path)
{
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(path);
string text = Path.ChangeExtension(path, ".pdb");
ISymbolReader symbolReader = new PdbReaderProvider().GetSymbolReader(assemblyDefinition.MainModule, text);
assemblyDefinition.MainModule.ReadSymbols(symbolReader);
var arg_5F_0 = assemblyDefinition.MainModule.Types;
if (!Program.CS$<>9__CachedAnonymousMethodDelegate1)
{
Program.CS$<>9__CachedAnonymousMethodDelegate1 = delegate(TypeDefinition x)
{
IEnumerable<MethodDefinition> enumerable = x.Methods;
return enumerable;
}
;
}
Enumerable.ToList<MethodDefinition>(Enumerable.SelectMany<TypeDefinition, MethodDefinition>(arg_5F_0, Program.CS$<>9__CachedAnonymousMethodDelegate1)).ForEach(new Action<MethodDefinition>(Program.Decompile));
}
view raw gistfile1.cs hosted with ❤ by GitHub

which leaks some of the internal plumbing -- delegate caching; how extension methods are actually handled -- but does at least deliver.

Monday, February 14, 2011

Scripting the Win32 API - F# and IronPython FFI

Mainly a worked example for reminding me of how the syntax goes, reimplementing sn -k in F# and IronPython:

open System
open System.Diagnostics
open System.IO
open System.Runtime.InteropServices
#nowarn "51"
// "The use of native pointers may result in unverifiable .NET IL code" --
// for arguments &&keyBlob, &&generatedSize in the call to StrongNameKeyGenEx
module NativeMethods =
[<DllImport("mscoree.dll")>]
extern int StrongNameErrorInfo()
[<DllImport("mscoree.dll")>]
extern bool StrongNameKeyGenEx(
[<MarshalAs(UnmanagedType.LPWStr)>]String wszKeyContainer,
int dwFlags, int dwKeySize, IntPtr* ppbKeyBlob, int* pcbKeyBlob)
[<DllImport("mscoree.dll")>]
extern void StrongNameFreeBuffer(IntPtr pbMemory)
let GetLastStrongNameError() =
Marshal.GetExceptionForHR(NativeMethods.StrongNameErrorInfo()).Message
let GenerateKeyPair (keyK : int) =
if keyK < 0 || keyK > 2 then
raise (new ArgumentOutOfRangeException("keyK", keyK, "Invalid Key Size -- should be 1 or 2"))
// variables that hold the unmanaged key
let mutable keyBlob = IntPtr.Zero
let mutable generatedSize = 0
// create the key
let createdKey = NativeMethods.StrongNameKeyGenEx(
null, 0, 1024 * keyK,
&&keyBlob, &&generatedSize)
try
// if there was a problem, translate it and report it
if not createdKey || keyBlob = IntPtr.Zero then
raise (new InvalidOperationException(GetLastStrongNameError()))
// make sure the key size makes sense
if generatedSize <= 0 || generatedSize > Int32.MaxValue then
raise (new InvalidOperationException("InternalError"))
// get the key into managed memory
let key = Array.create generatedSize 0uy
Marshal.Copy(keyBlob, key, 0, generatedSize)
key
finally
// release the unmanaged memory the key resides in
if keyBlob <> IntPtr.Zero then
NativeMethods.StrongNameFreeBuffer(keyBlob)
// path to generate new string name key
let keypath = fsi.CommandLineArgs.[1]
// write the key to the specified file if it is not already present
if not (File.Exists(keypath)) then
let key = GenerateKeyPair 1
use snkStream = new FileStream(keypath, FileMode.Create, FileAccess.Write)
use snkWriter = new BinaryWriter(snkStream)
snkWriter.Write(key)
view raw gistfile1.fs hosted with ❤ by GitHub

This about halfway between how you'd just call the APIs naturally in C++/CLI, and the full process of C# P/Invoke. You do need to do your own extern method declarations, whereas IronPython is something else again:

from System import Int32, Byte, IntPtr
import System
from System.IO import *
from System.Runtime.InteropServices import Marshal
from ctypes import *
import sys
mscoree = windll.mscoree
def GetLastStrongNameError():
error = Marshal.GetExceptionForHR(mscoree.StrongNameErrorInfo())
return error.Message
def GenerateKeyPair (keyK):
if keyK < 0 or keyK > 2 :
raise System.ArgumentOutOfRangeException(
"keyK", keyK, "Invalid Key Size -- should be 1 or 2")
# variables that hold the unmanaged key
keyBlob = c_void_p()
generatedSize = c_uint(0)
# create the key
createdKey = mscoree.StrongNameKeyGenEx(
None,
c_uint(0),
c_uint(1024 * keyK),
byref(keyBlob),
byref(generatedSize))
try:
# if there was a problem, translate it and report it
if not createdKey or keyBlob.value == 0:
raise System.InvalidOperationException(GetLastStrongNameError())
#make sure the key size makes sense
generatedSize = generatedSize.value
if generatedSize <= 0 or generatedSize > Int32.MaxValue:
raise System.InvalidOperationException("InternalError")
# get the key into managed memory
key = System.Array.CreateInstance(Byte, generatedSize)
Marshal.Copy(IntPtr(keyBlob.value), key, 0, generatedSize)
return key
finally:
# release the unmanaged memory the key resides in
if keyBlob.value != 0:
mscoree.StrongNameFreeBuffer(keyBlob)
# write the key to the specified file if it is not already present
keypath = sys.argv[1]
if not (File.Exists(keypath)):
print 'Generating key at %s' % (keypath)
key = GenerateKeyPair (1)
# should do this using a 'with'
snkStream = FileStream(keypath, FileMode.Create, FileAccess.Write)
snkWriter = BinaryWriter(snkStream)
snkWriter.Write(key)
snkWriter.Close()
view raw gistfile1.py hosted with ❤ by GitHub

where it was easier to fudge the pointer value for the key buffer into an IntPtr than to try and dereference it via ctypes and make a manual copy.

The code here is an adapted subset of a managed API for strong-name keys; the use-case being a contingent key generation as part of a build process.

Saturday, February 12, 2011

Signs of Spring

After the front-loaded winter in November and December, mild and often wet weather has brought the snowdrops to their height now, and in recent days crocuses and early irises too; even in sunny spots, early primroses, while the viburnum continues.

I cycled into town today to give my newly rebuilt bike (a full 3 star service from the new bike shop near work) a try-out; and having put on a winter-weight jacket was quite hot by the time I got there. So I stuffed the jacket in one pannier and was nigh-on the only person around with T-shirt and shades -- heavy coats and jackets were everywhere. Indeed so mild it was that I was comfortable cycling home without putting the jacket back on as the sun occasionally went behind the first real fair-weather cumulus I've spotted this year.

Other notes -- 6 months with the new car and a whisker under 2200 miles done on it, allowing for the clock showing 14 when I took delivery (from parking at home a fortnight ago with exactly 2000 showing).

MSBuild -- getting the assemblies you depend on from other projects

So in one project, you'd like to do something where you automatically pick up the other assemblies you've built in projects you depend on -- an obvious case in point being running some automated test based on the dependent projects. Just going for the list of references will pick you up a whole raft of system assemblies that you can't get rid of in .net4; so you need to turn the project references into the output names.

For that we have the MSBuild task and the GetTargetPath target, thus:

<PropertyGroup>
<TargetList>@(Targets->'%(filename)%(extension)', '; ')</TargetList>
</PropertyGroup>
<Target Name="BeforeBuild">
<MSBuild
Projects="@(ProjectReference)"
Targets="GetTargetPath">
<Output TaskParameter="TargetOutputs" ItemName="Targets" />
</MSBuild>
<Message Text="My reference list is $(TargetList)" />
</Target>
view raw gistfile1.xml hosted with ❤ by GitHub

which is a trivial example where we just echo the values to the console, like

BeforeBuild:
  My reference list is AnotherAssembly.dll; NewAssembly.dll

Friday, February 11, 2011

Getting the public key and token from a strong-name key

I hope we're all familiar with sn -Tp to get the public key details from an assembly -- here using an example where the key is public and anyone can reproduce the results:

>sn -Tp .\Mono.Cecil.dll

Microsoft (R) .NET Framework Strong Name Utility  Version 3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

Public key is
002400000480000094000000060200000024000052534131000400000100010079159977d2d03a
8e6bea7a2e74e8d1afcc93e8851974952bb480a12c9134474d04062447c37e0e68c080536fcf3c
3fbe2ff9c979ce998475e506e8ce82dd5b0f350dc10e93bf2eeecf874b24770c5081dbea7447fd
dafa277b22de47d6ffea449674a4f9fccf84d15069089380284dbdd35f46cdff12a1bd78e4ef00
65d016df

Public key token is 0738eb9f132ed756

But if you haven't built anything yet, you might still want these values from a string-name key pair (e.g. to put in an InternalsVisibleTo attribute on the first assembly at the bottom of the calling stack for the unit tests that are to come). So here's how

open System
open System.IO
open System.Reflection
open System.Security.Cryptography
let key = fsi.CommandLineArgs.[1]
let stream = new System.IO.FileStream(key, System.IO.FileMode.Open, System.IO.FileAccess.Read)
let pair = new StrongNameKeyPair(stream)
// get the public key token as 8 bytes from the end of a SHA1 hash of the key material
let hash = new System.Security.Cryptography.SHA1CryptoServiceProvider()
let token = hash.ComputeHash(pair.PublicKey)
|> Array.rev
|> Seq.take 8
let patchArray s =
s
|> Seq.map (fun (x:byte) -> x.ToString("X2").ToLower() )
|> Seq.toList
let rec chunkArray (s:string list) =
let chunk = 39
s
|> Seq.truncate chunk
|> Seq.iter Console.Write
Console.WriteLine(String.Empty)
if chunk > (List.length s ) then ()
else s
|> Seq.skip chunk
|> Seq.toList
|> chunkArray
let dumpArray s =
s
|> patchArray
|> chunkArray
Console.WriteLine("Public key is")
pair.PublicKey
|> dumpArray
Console.WriteLine(String.Empty)
Console.Write("Public key token is ")
token
|> dumpArray
Console.WriteLine(String.Empty)
view raw gistfile1.fs hosted with ❤ by GitHub

so now we can do

>& 'C:\Program Files\FSharp-2.0.0.0\bin\fsi.exe' .\sntp.fsx .\mono.snk
Public key is
002400000480000094000000060200000024000052534131000400000100010079159977d2d03a
8e6bea7a2e74e8d1afcc93e8851974952bb480a12c9134474d04062447c37e0e68c080536fcf3c
3fbe2ff9c979ce998475e506e8ce82dd5b0f350dc10e93bf2eeecf874b24770c5081dbea7447fd
dafa277b22de47d6ffea449674a4f9fccf84d15069089380284dbdd35f46cdff12a1bd78e4ef00
65d016df

Public key token is 0738eb9f132ed756

and get the values we want. As most of the time you'll want the public key as a continuous string, much of the format nonsense in the script can be dispensed with.

Thursday, February 10, 2011

Cecil.Decompiler and F#

In this new post-Reflector age, I thought I'd have a look-see how the main competition worked on F#. So I whipped up a simple driver for Cecil.Decompiler:

private static void Decompile(MethodDefinition m)
{
try {
Formatter.WriteMethodBody(Console.Out, m);
var lang = CSharp.GetLanguage(CSharpVersion.V3);
var writer = lang.GetWriter(new PlainTextFormatter(Console.Out));
writer.Write(m);
} catch (Exception e) {
Console.WriteLine(m.FullName);
Console.WriteLine(e.Message);
}
}
private static void DumpAssembly(string path)
{
var assembly = AssemblyDefinition.ReadAssembly(path);
var pdbpath = Path.ChangeExtension (path, ".pdb");
var provider = new PdbReaderProvider();
var reader = provider.GetSymbolReader(assembly.MainModule, pdbpath);
assembly.MainModule.ReadSymbols(reader);
assembly.MainModule.Types.SelectMany(x => x.Methods).ToList().ForEach(Decompile);
}
view raw gistfile1.cs hosted with ❤ by GitHub

and tried it on an assembly of some fairly simple F# -- a few extension methods for Option

module Augment =
type Microsoft.FSharp.Core.Option<'T> with
static member filter (f : 'T -> bool) (x : option<'T>) =
match x with
| Some v when f(v) -> Some v
| _ -> None
static member getOrElse (fallback : 'T) (x : option<'T>) = defaultArg x fallback
static member select (f : 'T -> bool) (x : 'T) =
if f(x) then Some x
else None
static member nullable (x : 'a when 'a : null) : option<'a> =
if x <> null then Some x
else None
view raw gistfile1.fs hosted with ❤ by GitHub

and we get things like

public static FSharpOption<T> Option`1.select.Static(FSharpFunc<T, bool> f, T x)
{
if (f.Invoke(x))
{
}
return FSharpOption<T>.Some(x);
return null;
}
view raw gistfile1.cs hosted with ❤ by GitHub

This is better than Reflector was when I first tried it about 18 months ago -- F#'s cluster of branch instructions

IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: callvirt !1 Microsoft.FSharp.Core.FSharpFunc`2<T,System.Boolean>::Invoke(!0)
IL_0008: brfalse.s IL_000c
IL_000a: br.s IL_000e
IL_000c: br.s IL_0015
IL_000e: ldarg.1
IL_000f: call Microsoft.FSharp.Core.FSharpOption`1<!0> Microsoft.FSharp.Core.FSharpOption`1<T>::Some(!0)
IL_0014: ret
IL_0015: ldnull
IL_0016: ret
view raw gistfile1.cs hosted with ❤ by GitHub

caused the old Reflector to just crash.

F#'s habit of lacing in temporaries did flummox the decompilation a bit:

public static FSharpOption<T> Option`1.filter.Static(FSharpFunc<T, bool> f, FSharpOption<T> x)
{
if (x)
{
FSharpOption<T> = ;
T v = .get_Value();
if (f.Invoke(v))
{
}
T v = .get_Value();
return FSharpOption<T>.Some(v);
}
return null;
}
view raw gistfile1.cs hosted with ❤ by GitHub

compared with the current Reflector's take of

public static FSharpOption<T> Option`1.filter.Static<T>(FSharpFunc<T, bool> f, FSharpOption<T> x)
{
FSharpOption<T> option = x;
if (option != null)
{
FSharpOption<T> option2 = option;
T v = option2.get_Value();
if (f.Invoke(v))
{
return FSharpOption<T>.Some(option2.get_Value());
}
}
return null;
}
view raw gistfile1.cs hosted with ❤ by GitHub

MonoDevelop 2.4.2.'s Assembly Browser gets it a bit better than the raw decompiler, though it still has the same 'C'-style if in there:

public static FSharpOption<T> Option`1.filter.Static(FSharpFunc<T, bool> f, FSharpOption<T> x)
{
if (x)
{
FSharpOption<T> V_1 = V_0;
T V_2 = V_1.get_Value();
if (f.Invoke(V_2))
{
}
T V_3 = V_1.get_Value();
return FSharpOption<T>.Some(V_3);
}
return null;
}
view raw gistfile1.cs hosted with ❤ by GitHub

Amusingly, I was able to get the decompilation to throw by feeding the little driver program into itself -- the DumpAssembly method would not turn back into C#! MonoDevelop silently refused to load the contents of the whole assembly.

Sunday, February 06, 2011

Getting the baked-in .pdb location from an assembly with Mono.Cecil 0.9.4

Because it isn't always as simple as

public static string GetPdbFileName (string assemblyFileName)
{
return Path.ChangeExtension (assemblyFileName, ".pdb");
}
view raw gistfile1.cs hosted with ❤ by GitHub

as is done in Mono.Cecil.Pdb.PdbHelper, if assemblies and symbols have been moved to separate locations during a build.

The tools are there -- we just need to get the debug data from the PE image if it's present, skip the first 24 bytes, and interpret the rest as a string. Alas, all the are annoyingly just slightly encapsulated from us. But never mind! Reflection gets us there without having to negotiate a patch or make a fork:

/// <summary>
/// Violate Cecil encapsulation to get the PDB path -- a candidate
/// for another method on ModuleDefinition
/// </summary>
/// <param name="assembly">The assembly to find the .pdb path for</param>
/// <returns>The path (null if the operation fails)</returns>
public static string GetPdbFromImage(AssemblyDefinition assembly)
{
var m = assembly.MainModule;
var imageField = typeof(ModuleDefinition).GetField("Image", BindingFlags.Instance | BindingFlags.NonPublic);
var image = imageField.GetValue(m);
var getDebugHeaderInfo = image.GetType().GetMethod("GetDebugHeader");
byte[] result = null;
var args = new object[] { result };
try
{
getDebugHeaderInfo.Invoke(image, args);
var SizeOfDebugInfo = 0x18;
result = args[0] as byte[];
if (null == result)
{
return null;
}
var byteValue = result.Skip(SizeOfDebugInfo).
TakeWhile(x => x != 0).ToArray();
if (byteValue.Length > 0)
{
// UTF-8 encoding works for an assembly named GetÞePdbLocation.
return Encoding.UTF8.GetString(byteValue);
}
}
catch (TargetInvocationException)
{}
return null;
}
view raw gistfile1.cs hosted with ❤ by GitHub