Dmitry
Dmitry

Reputation: 591

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.

Upvotes: 8

Views: 1757

Answers (3)

kain64b
kain64b

Reputation: 2326

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

Upvotes: 2

Dmitry
Dmitry

Reputation: 591

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.

Upvotes: 4

Robert Giesecke
Robert Giesecke

Reputation: 4314

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.

Upvotes: 2

Related Questions