Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading C# DLL with unmanaged exports into Python

I’ve built a C# DLL (MyTestDll) using the NuGet package UnmanagedExports:

[DllExport("Test", CallingConvention = CallingConvention.Cdecl)]
public static string Test(string name)
{
    return "hi " + name + "!";
}

I use it from Python via ctypes DLL import:

path = "C:\\Temp\\Test"
os.chdir(path)
dll = ctypes.WinDLL("MyTestDll.dll")
f = dll.Test
f.restype = ctypes.c_char_p
print f('qqq')

It’s just a fantasy, it works.

Then, I added one more DLL (NoSenseDll):

namespace NoSenseDll
{
    public class NoSenseClass
    {
        public static int Sum(int a, int b)
        {
            return a + b;
        }
    }
}

I started to use this NoSenseDll to implement MyTestDll:

[DllExport("Test", CallingConvention = CallingConvention.Cdecl)]
public static string Test(string name)
{
    return NoSenseDll.NoSenseClass.Sum(4, 5).ToString();
}

Unfortunately, it does not work. Python says:

WindowsError: [Error -532462766] Windows Error 0xE043435

I’ve tried to add C:\\Temp\\Test to path, but that did not help.


I’ve written a C++ test:

#include "stdafx.h"
#include "windows.h"
#include <iostream>
#include <string>
#include "WinBase.h"

typedef char*(__stdcall *f_funci)(const char*);

int _tmain(int argc, _TCHAR* argv[])
{
    int t;
    std::string s = "C:\\Temp\\Test\\MyTestDll.dll";
    HINSTANCE hGetProcIDDLL = LoadLibrary(std::wstring(s.begin(), s.end()).c_str());

    f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "Test");

    std::cout << "funci() returned " << funci(std::string("qqq").c_str()) << std::endl;
    std::cin >> t;
    return EXIT_SUCCESS;
}

It works if the second DLL (NoSenseDll) is in the same folder as the C++ executable. It does not work if I just add NoSenseDll folder to PATH.

like image 985
Dmitry Avatar asked Oct 16 '15 16:10

Dmitry


3 Answers

Draft solution:

  1. Copy NoSenseDll to the folder of Python, in my case %HOMEPATH%\Anaconda.
  2. Restart IPython/Spyder.

Final solution:

static MyTestDllClass() // static constructor
{
    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromSameFolder);
}
static Assembly LoadFromSameFolder(object sender, ResolveEventArgs args)
{
    string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll");
    if (File.Exists(assemblyPath) == false) return null;
    Assembly assembly = Assembly.LoadFrom(assemblyPath);
    return assembly;
}

Final note:

If you can’t use IronPython because of matplotlib or pandas,
if you can’t use python.net because of IPython or spyder,
if you don’t want to use COM Interop just because,
and you really want to get C# and Python to work together, use the solution above and C# reflection.

like image 102
Dmitry Avatar answered Sep 27 '22 19:09

Dmitry


You can also check out Costura.Fody.

This is a build task that will add your dependencies as resources to your assembly and even hooks up a module initializer to load them at runtime.

like image 24
Robert Giesecke Avatar answered Sep 27 '22 18:09

Robert Giesecke


you cant do it directly with managed code. register COM object from you dll:

%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\regasm.exe my.dll /tlb:my.tlb /codebase

and make com call from python. see here examples: http://www.codeproject.com/Articles/73880/Using-COM-Objects-in-Scripting-Languages-Part-Py

like image 32
kain64b Avatar answered Sep 27 '22 19:09

kain64b