NUnit test with Fortran DLL

半世苍凉 提交于 2019-12-08 08:03:51

问题


I have a ServerCom DLL that comes from Fortran. I generate automatically using tlbimp a MyFortran.dll from the ServerCom.dll that can be referenced directly from C#.

In a C# Class Library I have referenced MyFortran.dll.

I created a console application that use the MyFortran.dll and generated the correct manifest (in order to have a free-interopt COM environment).

It works perfectly in the console application.

Now, I wrote a simple NUnit test and I got a COM Exception.

System.Runtime.InteropServices.COMException : Retrieving the COM class factory for component with CLSID {0FB0F699-4EF8-4732-B98E-C088825E3912} failed due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).

How can I solve this?

Thanks, Adrien.


回答1:


You can use Activation Context API's to achieve this. This blog post gives all the details.

Here's a summary.

Paste the following code into your project (thanks to Spike McLarty for this):

/// <remarks>
/// Code from http://www.atalasoft.com/blogs/spikemclarty/february-2012/dynamically-testing-an-activex-control-from-c-and
/// </remarks>
class ActivationContext
{
    static public void UsingManifestDo(string manifest, Action action)
    {
        UnsafeNativeMethods.ACTCTX context = new UnsafeNativeMethods.ACTCTX();
        context.cbSize = Marshal.SizeOf(typeof(UnsafeNativeMethods.ACTCTX));
        if (context.cbSize != 0x20)
        {
            throw new Exception("ACTCTX.cbSize is wrong");
        }
        context.lpSource = manifest;

        IntPtr hActCtx = UnsafeNativeMethods.CreateActCtx(ref context);
        if (hActCtx == (IntPtr)(-1))
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
        try // with valid hActCtx
        {
            IntPtr cookie = IntPtr.Zero;
            if (!UnsafeNativeMethods.ActivateActCtx(hActCtx, out cookie))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            try // with activated context
            {
                action();
            }
            finally
            {
                UnsafeNativeMethods.DeactivateActCtx(0, cookie);
            }
        }
        finally
        {
            UnsafeNativeMethods.ReleaseActCtx(hActCtx);
        }
    }

    [SuppressUnmanagedCodeSecurity]
    internal static class UnsafeNativeMethods
    {
        // Activation Context API Functions
        [DllImport("Kernel32.dll", SetLastError = true, EntryPoint = "CreateActCtxW")]
        internal extern static IntPtr CreateActCtx(ref ACTCTX actctx);

        [DllImport("Kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool ActivateActCtx(IntPtr hActCtx, out IntPtr lpCookie);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool DeactivateActCtx(int dwFlags, IntPtr lpCookie);

        [DllImport("Kernel32.dll", SetLastError = true)]
        internal static extern void ReleaseActCtx(IntPtr hActCtx);

        // Activation context structure
        [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Unicode)]
        internal struct ACTCTX
        {
            public Int32 cbSize;
            public UInt32 dwFlags;
            public string lpSource;
            public UInt16 wProcessorArchitecture;
            public UInt16 wLangId;
            public string lpAssemblyDirectory;
            public string lpResourceName;
            public string lpApplicationName;
            public IntPtr hModule;
        }

    }
}

Every time you need to create a COM object (COMObject in this example), wrap the call that creates it in a lambda function, and pass that to UsingManifestDo, like this:

object CreateManifestDependantCOMObject() 
{
   object myCOMObject = null;
   ActivationContext.UsingManifestDo(pathToManifestFile, () => myCOMObject = new COMObject());
   return myCOMObject;
}



回答2:


Yes, this doesn't work. The registry-free COM manifest needs to be embedded in the EXE that uses the COM server. Easy enough for your console app. Not easy when you use NUnit because the EXE is now the unit test runner. You can't/shouldn't mess with it. Hard to do anyway because there are a bunch of them.

Just don't bother, this is a deployment detail that's not relevant for the testing you want to do. Just register the server with regsvr32.exe on the machine that executes the tests and be done with it.




回答3:


Check out this answer:
How to do registration-free COM in a plug-in architecture

Eugene indicates that this is possible in one of two ways:
1. embed the manifest in the dll and compile with ISOLATION_AWARE_ENABLED
2. use context activation APIs



来源:https://stackoverflow.com/questions/4935536/nunit-test-with-fortran-dll

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!