TuesPechkin icon indicating copy to clipboard operation
TuesPechkin copied to clipboard

Create a TuesPechkin.Wkhtmltox.AnyCPU package

Open tuespetre opened this issue 9 years ago • 8 comments

This package should reference both Win32 and Win64 packages and dynamically choose the correct one to use during runtime. This should work because .NET only loads an assembly once a type is invoked.

tuespetre avatar Mar 10 '15 13:03 tuespetre

The GetContents method is protected on each package, which makes it impossible to call from within the GetContents method on my wrapper without using reflection. I didn't want to go that route.

I then figured I'd just create a simple utility for selecting the right EmbeddedDeployment based on the current architecture.

public static class WinAnyCPUUtility
{
    public static EmbeddedDeployment GetEmbeddedDeployment(IDeployment physical)
    {
        if (IntPtr.Size == 8)
        {
            return new Win64EmbeddedDeployment(physical);
        }
        else
        {
            return new Win32EmbeddedDeployment(physical);
        }
    }
}

Usage:

var converter = new ThreadSafeConverter(
    new RemoteToolset<PdfToolset>(
        WinAnyCPUUtility.GetEmbeddedDeployment(
            new TempFolderDeployment())));

However, it appeared to be trying to load both DLLs, so clearly I didn't understand.

I resorted to making a new project, putting both DLLs in the project, as embedded resources, and doing the following:

namespace TuesPechkin
{
    [Serializable]
    public class WinAnyCPUEmbeddedDeployment : EmbeddedDeployment
    {
        public WinAnyCPUEmbeddedDeployment(IDeployment physical) : base(physical) { }

        public override string Path
        {
            get
            {
                return System.IO.Path.Combine(
                    base.Path,
                    GetType().Assembly.GetName().Version.ToString());
            }
        }

        protected override IEnumerable<KeyValuePair<string, Stream>> GetContents()
        {
            var resource = IntPtr.Size == 8
                ? Resources.wkhtmltox_64_dll 
                : Resources.wkhtmltox_32_dll;

            return new[]
            { 
                new KeyValuePair<string, Stream>(
                    key: WkhtmltoxBindings.DLLNAME,
                    value: new GZipStream(
                        new MemoryStream(resource), 
                        CompressionMode.Decompress))
            };
        }
    }
}

I understand this isn't what you are shooting for, but it's the best I was able to come up with on my own.

The only other thing I can think of is not referencing the TuesPechkin.Wkhtmltox.Win32 and TuesPechkin.Wkhtmltox.Win64 DLLs directly, and loading them at run time. That might solve the problem?

crush83 avatar Jun 11 '15 22:06 crush83

Because it is protected, you're right that you can't call the method on 32 or 64 directly, but you should be able to cast them to their base type, whose method you can call, and it should run the 32 or 64 method due to the override keyword.

As for both of the assemblies getting loaded, I thought an assembly only gets actually loaded when the runtime needs to

tuespetre avatar Jun 12 '15 01:06 tuespetre

Excuse me, slippery mobile thumbs. As I was saying -- because you reference those types in the same method, the runtime is going to load both of the assemblies. If you put each reference into separate private methods and add the 'no inlining' attribute to the methods, you should be able to 'trick' the runtime into only loading whichever one gets used.

tuespetre avatar Jun 12 '15 01:06 tuespetre

Any progress on this issue? The reason I am asking is because we're having the problem that on IIS TuesPechkin doesn't work (we are not able to enable 32 bit apps on the AppPool for certain reasons). I tried using

new StaticDeployment(HostingEnvironment.ApplicationPhysicalPath + @"resources\htmltopdf"))

This works on development (IIS Express) but when on IIS we put the 64bit dll there, it says the following:

System.DllNotFoundException: Unable to load DLL 'wkhtmltox.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)

Server stack trace: 
   at TuesPechkin.WkhtmltoxBindings.wkhtmltopdf_init(Int32 useGraphics)
   at TuesPechkin.PdfToolset.Load(IDeployment deployment)

While the correct (64bit) dll is there for sure... Any suggestions are welcome

johanhaest avatar Aug 27 '15 10:08 johanhaest

Turns out the server did not have the correct dependencies as described here: https://github.com/tuespetre/TuesPechkin/issues/65

johanhaest avatar Aug 27 '15 10:08 johanhaest

Hi guys, I could not get this to work either of suggested ways, so I'd thought I'd share my experience:

  • As for assemblies getting loaded when they're needed, I'm not sure about that. I started a brand new emtpy asp net mvc project, installed both 32 and 64 packages via nuget, and NOT written a single line of code ie not used it, and when I run the project I get: [BadImageFormatException: Could not load file or assembly 'TuesPechkin.Wkhtmltox.Win32' or one of its dependencies. An attempt was made to load a program with an incorrect format.]
  • I've tried the separate project with dynamic loading hack, which gets the project me running, but when try to use it I get:

ERROR Opter.MvcApplication System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B) Server stack trace: at TuesPechkin.WkhtmltoxBindings.wkhtmltopdf_init(Int32 useGraphics) at TuesPechkin.PdfToolset.Load(IDeployment deployment) at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Object[]& outArgs) at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg) Exception rethrown at [0]: at TuesPechkin.ThreadSafeConverter.Invoke[TResult](FuncShim1 delegate) at Opter.Controllers.Catalogs.AbstractCatalogController1.PrintGridCatalog(List1 ColModel, Nullable1 id, Int32 page, Int32 rows, String sidx, String sord, T filter, Boolean _search) in d:\Projekti\Medicina\Opter\src\website2\Opter\Controllers\Catalogs\AbstractCatalogController.cs:line 102 at lambda_method(Closure , ControllerBase , Object[] ) at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult2.CallEndDelegate(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass48.<InvokeActionMethodFilterAsynchronouslyRecursive>b__41() at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass33.<BeginInvokeActionMethodWithFilters>b__32(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) at System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) at System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid1.CallEndDelegate(IAsyncResult asyncResult) at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep. Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

I have used the wkhtmltox_64.dll.gz as my embedded resource (from the project that can be downloaded from the main page). I'm using VS 2013 Ultimate, update 4.

Finnaly, to get thing to work, I've opted for x64 and dropped the AnyCPU. Best,

Igor

mekterovic avatar Aug 31 '15 07:08 mekterovic

@crush83 In your solution: Resources.wkhtmltox_64_dll is a resx file where you added dll file as "Existing file"? I did: var resource = IntPtr.Size == 8 ? DllResources.TuesPechkin_Wkhtmltox_Win64 : DllResources.TuesPechkin_Wkhtmltox_Win32;

        return new[]
        { 
            new KeyValuePair<string, Stream>(
                key: WkhtmltoxBindings.DLLNAME,
                value: new GZipStream(
                    new MemoryStream(resource), 
                    CompressionMode.Decompress))
        };

but the GZip key is not correct. Can you help me?

codeweb avatar Oct 20 '15 08:10 codeweb

Also check my version https://github.com/cratu/TuesPechkin I've made NuGet package: https://www.nuget.org/packages/TuesPechkin.Wkhtmltox.AnyCPU/

cratu avatar Oct 12 '16 08:10 cratu