miguelkachondo / or-tools

Automatically exported from code.google.com/p/or-tools
0 stars 0 forks source link

C# bindings enhancements #58

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
The C# bindings have, IMHO, some shortcomings:

1. The generated DLLs have no version information. This makes it difficult to 
verify which version of a binary is in use, and makes specific-version assembly 
references impossible/meaningless.

2. The generated DLLs have the same name for both x86 and x64 platforms. The 
DLL file cannot simply be renamed due to hard-coded references to the name of 
the DLL within the DLL itself in the form of [DllImport] attributes generated 
by SWIG. This complicates (to some extent) creating platform-independent (ie, 
AnyCPU) .NET applications. Other side effects may include MSBuild's rather 
promiscuous assembly reference resolution mechanism finding the wrong assembly 
despite correct HintPath item metadata.

3. The generated DLLs have the corflags value 0x10 
(COMIMAGE_FLAGS_NATIVE_ENTRYPOINT) set. This appears to be prohibited by 
ECMA-335 II.25.3.3.1, which states that this flag "Shall be 0". This is 
probably a SWIG 'cunningness' that can't be changed, but I mention it here for 
reference.

--

I have addressed (1) and (2) above with modifications to 
makefiles/Makefile.csharp.mk, as follows:

i. The file $(OBJ_DIR)/CommonAssemblyAttributes.cs is automatically generated 
at build time, and contains assembly attribute definitions for AssemblyVersion, 
AssemblyFileVersion and AssemblyInformationalVersion. The version used is 
effectively 0.0.0.$(SVNVERSION). As $(SVNVERSION) may not be a simple number 
(eg, suffixed 'M' in a modified working copy), the leading digits are used with 
AssemblyVersion and AssemblyFileVersion, and the actual value, possibly 
including alpha chars, is used for AsssemblyInformationalVersion. The generated 
.cs file is included as a source file in all calls to CSC.

ii. If the variable CLR_PER_PLATFORM_ASSEMBLY_NAMING is defined (with any 
value), the assembly generated in x64 builds will be named 
Google.OrTools.x64.dll, and in x86 builds it will be named 
Google.OrTools.x86.dll. This name change is passed throughout the toolchain, 
including SWIG, so that the generated [DllImport] attributes have the 
appropriate DLL name. Test executables will be suffixed with '_x64' in x64 
builds, no suffix in x86 builds. Note that the dotnet_archive target is not 
(presently) compatible with setting CLR_PER_PLATFORM_ASSEMBLY_NAMING, and will 
generate a meaningful error if such use is attempted.

A diff against 3804 is attached.

Thanks,

Tom

Original issue reported on code.google.com by t...@zanyants.com on 31 Dec 2014 at 6:15

Attachments:

GoogleCodeExporter commented 9 years ago
Impressive, 

I have included:
  - the SVNVERSION_SIMPLE (in Makefile.port)
  - the CommonAssemblyAttributes part
  -  CLR_PER_PLATFORM_ASSEMBLY_NAMING and its derivatives

I have changed NETPLATFORM to x64 on mac os X.

I have not included:
  - Adding AssemblyInfo file to all binaries, do we need than in addition to the dll?

Question: on Windows (I am far from a windows machine), does the assembly 
loading mechanism loads Google.OrTools.x64.dll with the Google.OrTools prefix 
(for references)?

Many thanks!

Original comment by laurent....@gmail.com on 1 Jan 2015 at 12:00

GoogleCodeExporter commented 9 years ago
Hi Laurent,

Thanks for your comments, and I'm glad you approve!

Re: Adding AssemblyInfo file to all binaries, do we need than in addition to 
the dll?

While it's not vital, I don't think it does any harm. If I understand it 
correctly, the executables are all examples or unit tests, so they're unlikely 
to travel far, thus reducing the importance.

As regards assembly binding:

Despite fairly extensive research and experience, I'm not aware of any 
out-of-the-box mechanism to have the correct platform-specific DLL resolved for 
a reference from a platform independent assembly at runtime, regardless of the 
naming scheme of the referenced DLL. One avenue I have not investigated is the 
Native Image Cache, as typically populated using the ngen.exe framework tool, 
but frankly I'd not hold out much hope with that approach given that it's 
indented to cache platform-specific compilations of IL assemblies, and the 
OrTools assemblies are quite hard for most IL-manipulating tools to digest.

However, I have developed a mechanism for automatic runtime selection that we 
use within our organisation. The mechanism is packaged using nuget, requires 
powershell during nuget package installation, and MSBuild as the build system. 
It also uses some of our custom in-house build tasks. And currently will only 
work with C# projects (not VB, F# etc). So in its current form, the mechanism 
is a) Windows-specific (as I understand it, due to needing MSBuild and 
powershell) b) C#-specific and c) requires some of our in-house tools. It would 
not be too difficult to abstract the in-house tools, so issue (c) could be 
worked-around. So, if a Windows-specific, C#-specific nuget-packaged mechanism 
is of significant interest, I could look at open-sourcing and publishing it, 
subject to my organisation granting me the permission and time to resolve issue 
(c). Issue (b) could also be worked-around, with time.

In case anyone else wishes to reproduce the mechanism by hand, here's what we 
do:

1. Obtain .NET assemblies Google.OrTools.x86.dll and Google.OrTools.x64.dll.

2. Take one of these assemblies and run it through our in-house tool, 
eviscerate.exe. eviscerate.exe uses Cecil to remove all the innards of the 
assembly, leaving behind just the public .NET facade. All remaining method 
bodies simply throw. The corflags are fixed up, and a pure-IL, 
platform-agnostic assembly is emitted. I call this the "eviscerated facade", 
and it is named Google.OrTools.dll. In r3804, it ends up as just 166 KiB.

3. Assume we have an AnyCPU C# project, Foo, that needs to use OrTools.

4. To Foo.csproj, add an assembly reference to the eviscerated facade, 
Google.OrTools.dll. Set CopyLocal to false. This is what csc will compile 
against, and will be used for IntelliSense in Visual Studio.

5. Add Google.OrTools.x86.dll and Google.OrTools.x64.dll to Foo.csproj as 
content items (*not* as assembly references). Set CopyToOutputDirectory to 
Always.

6. Before any other code in Foo executes, subscribe to the 
AppDomain.Current.AssemblyResolve event.

7. In the AssemblyResolve handler, if the assembly being resolved is 
Google.OrTools, load and return the correct platform-specific assembly.

8. Ensure that you don't have a Google.OrTools.dll in the GAC. And there should 
not be a Google.OrTools.dll in Foo's build output directory (hence setting 
CopyLocal to false). If you produced your own strong name signed OrTools 
binaries (we do), then you're immune from the consequences of some other 
software dumping a copy of Google.OrTools.dll in the GAC. Essentially, we don't 
want Foo.exe to be able to find Google.OrTools.dll - this failure to resolve 
will cause the AssemblyResolve handler to be called.

Notes:

i. In our implementation, all the above steps are performed seamlessly at build 
time as a consequence of adding the nuget package to the project. The only 
visible effect within Visual Studio is a an assembly reference to the 
eviscerated facade.

ii. To acheive (6) and (7) we inject a module initializer into the compiled DLL 
using an in-house custom build task, which iteslf uses Cecil. The event 
subscription handling code is in a .cs file that is silently added to the 
project during the build process (hence the C#-only limitation). A module 
initializer is a CLR feature that is not exposed by any of the standard CLR 
langauages, but can be used by direct IL manipulation/injection. It essentially 
creates a module-global static constructor which the CLR VM guarantees to 
execute before any other code in the assembly is executed. This works for .NET 
executables and DLLs.

Thanks,

Tom

Original comment by t...@zanyants.com on 1 Jan 2015 at 1:10

GoogleCodeExporter commented 9 years ago
Just to clarify - in our mechanism, steps 1 & 2 are not performed at build 
time, but are part of creating the nuget package.

Original comment by t...@zanyants.com on 1 Jan 2015 at 1:12