Search code examples
python.netwindowsaxaptapython.net

Wrapping Microsoft Dynamics Business Connector .net assembly in python


I am writing a python wrapper to the Microsoft Dynamics Business Connector .net assembly.

This is my code:

"""Implements wrapper for axapta business connector."""

import pathlib
from msl.loadlib import LoadLibrary
import clr

DLL_PATH = pathlib.Path(__file__).parent / 'Microsoft.Dynamics.BusinessConnectorNet.dll'


def test_msl_connector():
    """Get Axapta object via msl-loadlib package."""
    connectorLL = LoadLibrary(DLL_PATH, 'net')
    Axapta = getattr(connectorLL.lib,
                     'Microsoft.Dynamics.BusinessConnectorNet').Axapta
    return Axapta


def test_pure_pythonnet_connector():
    """Get Axapta object via pythonnet package."""
    clr.AddReference(str(DLL_PATH))
    from Microsoft.Dynamics.BusinessConnectorNet import Axapta
    return Axapta

This is my errors when running pytest:

============================= test session starts =============================
platform win32 -- Python 3.6.2, pytest-3.4.0, py-1.5.2, pluggy-0.6.0
rootdir: C:\Users\AZ\Desktop\test_bom-mcs, inifile:
collected 2 items

test_main.py FF                                                          [100%]

================================== FAILURES ===================================
_____________________________ test_msl_connector ______________________________

    def test_msl_connector():
        """Get Axapta object via msl-loadlib package."""
>       connectorLL = LoadLibrary(DLL_PATH, 'net')

test_main.py:12:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[AttributeError("'LoadLibrary' object has no attribute '_lib'") raised in repr()] LoadLibrary object at 0
x4152c18>
path = WindowsPath('C:/Users/AZ/Desktop/test_bom-mcs/Microsoft.Dynamics.BusinessConnectorNet.dll')
libtype = 'net'

    def __init__(self, path, libtype='cdll'):

        _path = path

        # a reference to the .NET Runtime Assembly
        self._assembly = None

        # assume a default extension if no extension was provided
        if not os.path.splitext(_path)[1]:
            _path += DEFAULT_EXTENSION

        self._path = os.path.abspath(_path)
        if not os.path.isfile(self._path):
            # for find_library use the original 'path' value since it may be a library name
            # without any prefix like lib, suffix like .so, .dylib or version number
            self._path = ctypes.util.find_library(path)
            if self._path is None:  # then search sys.path and os.environ['PATH']
                success = False
                search_dirs = sys.path + os.environ['PATH'].split(os.pathsep)
                for directory in search_dirs:
                    p = os.path.join(directory, _path)
                    if os.path.isfile(p):
                        self._path = p
                        success = True
                        break
                if not success:
                    raise IOError('Cannot find the shared library "{}"'.format(path))

        if libtype == 'cdll':
            self._lib = ctypes.CDLL(self._path)
        elif libtype == 'windll':
            self._lib = ctypes.WinDLL(self._path)
        elif libtype == 'oledll':
            self._lib = ctypes.OleDLL(self._path)
        elif libtype == 'net' and self.is_pythonnet_installed():
            import clr
            try:
                # By default, pythonnet can only load libraries that are for .NET 4.0+.
                #
                # When MSL-LoadLib is installed, the useLegacyV2RuntimeActivationPolicy
                # property should have been enabled automatically to allow for loading
                # assemblies from previous .NET Framework versions.
                self._assembly = clr.System.Reflection.Assembly.LoadFile(self._path)

            except clr.System.IO.FileLoadException as err:
                # Example error message that can occur if the library is for .NET <4.0,
                # and the useLegacyV2RuntimeActivationPolicy is not enabled:
                #
                # " Mixed mode assembly is built against version 'v2.0.50727' of the
                #  runtime and cannot be loaded in the 4.0 runtime without additional
                #  configuration information. "
                #
                # To solve this problem, a <python-executable>.config file must exist and it must
                # contain a useLegacyV2RuntimeActivationPolicy property that is set to be "true".
                if "Mixed mode assembly" in str(err):
                    status, msg = self.check_dot_net_config(sys.executable)
                    if not status == 0:
                        raise IOError(msg)
                    else:
                        update_msg = 'Checking .NET config returned "{}" '.format(msg)
                        update_msg += 'and still cannot load library.\n'
                        update_msg += str(err)
                        raise IOError(update_msg)
                raise IOError('The above "System.IO.FileLoadException" is not handled.\n')

            # the shared library must be available in sys.path
            head, tail = os.path.split(self._path)
            sys.path.insert(0, head)

            # don't include the library extension
            clr.AddReference(os.path.splitext(tail)[0])

            # import namespaces, create instances of classes or reference a System.Type[] object
            dotnet = {}
>           for t in self._assembly.GetTypes():
E           System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Ret
rieve the LoaderExceptions property for more information.
E              at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
E              at System.Reflection.Assembly.GetTypes()

..\..\.virtualenvs\test_bom-mcs-bwfslhpz\lib\site-packages\msl\loadlib\load_library.py:132: ReflectionTypeLoadExc
eption
________________________ test_pure_pythonnet_connector ________________________

    def test_pure_pythonnet_connector():
        """Get Axapta object via pythonnet package."""
        clr.AddReference(str(DLL_PATH))
>       from Microsoft.Dynamics.BusinessConnectorNet import Axapta
E       ModuleNotFoundError: No module named 'Microsoft'

test_main.py:21: ModuleNotFoundError
========================== 2 failed in 0.61 seconds ===========================

.net decompiler shows: a picture from ilspy

P.S. The stackoverflow grader asks me to add more details:) What else - obj = clr.AddRefrence works and I can see a Microsoft attribute in obj. But no any Dynamics etc.

Repository for this example is here https://gitlab.com/remak_team/open-source/MicrosoftDynamicsConnector_python_wrapper/tree/master


Solution

  • This exception relates to the Microsoft.Dynamics.BusinessConnectorNet.dll library not finding all of the required dependencies.

    On a computer that was successfully able to load this library I used Dependency Walker and DependencyWalker for .NET to create a file that contains the full path to all of the dependencies that these two Walker's found. The list of dependencies that are required can be downloaded here.

    Hopefully, you will be able to locate the missing dependencies from this list and then you will be able to load the Microsoft.Dynamics.BusinessConnectorNet.dll library.