Creating a wrapper for a C# library in Python - c#

Main goal: Create a wrapper for a C# library, which can be used in Python (2.6).
UPDATE: Now, I have updates to the method I am using, which is however not working well.
The code for the simple C# class library:
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace Test
{
[Guid("8F38030D-52FA-4816-B587-A925FDD33302")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface _TestClass
{
[DispId(1)]
string Eureka();
}
[Guid("BC3F6BB3-42C4-4F30-869A-92EA45BF68D2")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Test.TestClass")]
public class TestClass : _TestClass
{
public TestClass()
{
}
public string Eureka()
{
return "Hudson, we no longer have a problem!";
}
}
}
enter code here
In addition to this, I went into Project Properties and enabled the setting: Register for COM interop.
Also, in order to make the class library available to COM, I ticked Signing -> Sign the Assembly, and gave it a strong key.
Furthermore, whenever I compile, I unregister the old version with:
regasm -u Test /tlb:Test
And I register it with:
regasm Test.dll /tlb:Test
My problem is then, in the Python environment, I have the following main.py, which is not working:
import win32com.client
o = win32com.client.Dispatch("Test.TestClass")
The error is unforgiven.
thank you in advance!

A alternative would be if you you use Python for .NET. There seem to be alpha releases for Windows CPython 2.6 and 2.7 available. You could run simply:
import clr
clr.AddReference("Your.Assembly.Name")
import Test
test = Test.TestClass()
print test.Eureka()

Related

Using C# code in C++ (COM) Dll import not working quite right

As the title implies, I am having difficulty calling C# code from C++. A bit of context: there is an API call in C# (that does not exist in the C++ version of the API) and I need to integrate it into a much larger C++ project.
I have been reading over this SO post, and made the most headway with the COM method.
My C# dll compiles*. After copying the resulting dll and tlb files into the appropriate C++ project directory, I open an admin cmd prompt in the same C++ project directory and run:
regasm MyFirstDll.dll /codebase /tlb
(*I compile it as a Class Library, Assembly name: MyFirstDll, Default namespace: MyMethods, Assembly Information... -> Make assembly Com-Visible is checked, Build->Register for COM interop is also checked.)
Using the Object Browser, I am able to see the class I defined, as well as a method with the appropriate args and signature.
Screenshot of it showing up in Object Browser
The issue I am experiencing is with the method call (and not the class). Even though the method is visible in the Object Browser, in my code it is not recognized as a method of the object.
class "ATL::_NoAddRefReleaseOnCComPtr" has no member "Add"
Here is my code:
MyFirstDll C# Project:
using System;
using System.Runtime.InteropServices;
//
namespace MyMethods
{
// Interface declaration.
[Guid("8a0f4457-b08f-4124-bc49-18fe11cb108e")]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface Easy_Math
{
[DispId(1)]
long Add(long i, long j);
};
}
namespace MyMethods
{
[Guid("0cb5e240-0df8-4a13-87dc-50823a395ec1")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyMethods.AddClass")]
public class AddClass: Easy_Math
{
public AddClass() { }
[ComVisible(true)]
public long Add(long i, long j)
{
return (i + j);
}
}
}
NateMath.h:
#include <atlcomcli.h>
#import "MyFirstDll.tlb"
class NateMath
{
public:
NateMath(void);
~NateMath(void);
long Add(long a, long b);
private:
CComPtr<MyFirstDll::AddClass> adder;
};
NateMath.cpp:
#include "stdafx.h"
#include <atlcomcli.h>
#import "MyFirstDll.tlb"
#include "NateMath.h"
NateMath::NateMath(void)
{
CoInitialize(NULL);
adder.CoCreateInstance(__uuidof(MyFirstDll::AddClass));
}
NateMath::~NateMath(void)
{
CoUninitialize();
}
long NateMath::Add(long a, long b) {
return adder->Add(a,b);
}
The issue being that in "return adder->Add(a,b)" (NateMath.cpp) Add(a,b) shows up red with class "ATL::_NoAddRefReleaseOnCComPtr" has no member "Add"
This is because you are trying to use your class name in CComPtr instead of the interface. With COM, all methods are defined on an interface, not on the class that implements an interfaces.
You can CoCreateInstance(__uuidof(YourClass)) because the intent is to create an instance of YourClass (which is identified by the GUID expressed by __uuidof(YourClass)). However, YourClass in the C++ is a dummy struct that's only present so that you can read the uuid -- the definition of YourClass in the C++ generated from the #import is empty and will always be empty.
To fix this, use CComPtr<YourInterface>. This tells the C++ that you want to communicate with the referenced object via that interface. Here's a rule to remember: The type argument to CComPtr and CComQIPtr must always be a COM interface. That COM interface can either be an explicitly-defined interface, or it can be the "class interface" which was automatically produced by .NET.
Speaking of class interfaces: If you had used ClassInterfaceType.AutoDual instead of None, you could have used a CComPtr<_YourClass> (note the leading underscore -- _YourClass is the class interface, whereas YourClass would be the class. I would recommend doing it the way you already have, however.

Robert Giesecke UnmanagedExports, dumpbin check fails

I used the popular Robert Giesecke template for unmanaged exports in order to create a DLL for usage from native code.
Actually it should be really simple because one only has to adapt the given example function. Building works, but this command doesn't show me any function:
$ dumpbin.exe /exports <mydllname>.dll
My exported function looks like this:
using RGiesecke.DllExport;
namespace HelloWorld
{
internal static class UnmanagedExports
{
[DllExport("_adddays", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
static double AddDays(double dateValue, int days)
{
return System.DateTime.FromOADate(dateValue).AddDays(days).ToOADate();
}
}
}
Does anyone have an idea what I am doing wrong? I'd be very happy about any help.
FYI: I'm using VS 2012, .NET-Framework 4.5, used class-library project template. I already tried changing the platform target to x86 (recommended in other posts) but didn't help.

No functions in C# DLL with RGiesecke.DllExport

I am trying to make a DLL in C# for use in a couple other languages. I found RGiesecke's DllExport but it doesn't seem to work. It builds just fine and makes a dll, but when I open it in Dependency Walker it doesn't show any functions, and my calling code can't find them either.
I created a new "Class Library" project (VS 2013) and then installed "Unmanaged Exports (DllExport for .Net)" from NuGet. Are there any project settings I need?
Here is my code.
using System;
using System.Collections.Generic;
using System.Text;
using RGiesecke.DllExport;
namespace ToolServiceDLL
{
public class Class1
{
[DllExport("addUp", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
public static double addUp(double num1, double num2)
{
return num1 + num2;
}
[DllExport("get5", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
public static int get5()
{
return 5;
}
}
}
I found the problem. It has it in the RGiesecke Documentation, but I missed it. In the project settings->Build->Platform target: you can not have it set to "Any CPU". You must have it set to x64 or x86 depending on if you want to use it in a a 64 or 32 bit application.
I had a similar problem, but had already set the platform target to x64 and had the following error:
The name 'CallingConvention' does not exist in the current context
I found adding the using directive System.Runtime.InteropServices resolve the problem.
This is a good question and worked for me. Just took a bit more time than it should due to the Python side where I made a mistake. Here is the working code in Python3
import sys
import clr
sys.path.insert(0,'C:\\your_path\\Py2NetComm\\bin\\Debug')
clr.AddReference("Py2NetComm")
import ToolServiceDLL as p2n
ret = p2n.Class1.addUp(2,3)
print("addUp:", ret)

Detect c# version at compile time

I have an old line of c# code that looks basically like this:
foo.set_Parent(parent);
It has compiled fine for years. Now in VS2015 I get the error:
CS0571 'Foo.Parent.set': cannot explicitly call operator or accessor
So I can rewrite the line as:
foo.Parent=parent;
This builds fine in VS2015, but in VS2013 it gives the error:
'Foo.Parent' is not supported by the language; try directly calling
accessor methods 'Foo.get_Parent()' or Foo.set_Parent(Foo)'
So the simple fix is to simply ifdef these two lines based upon which version of the compiler is running. But how do you detect which version of the compiler is executing?
And for the record, no, I can't just dictate that everyone on the team simultaneously upgrades to VS2015.
Additional info -
For everyone smelling a rat, I'll go ahead and drag out the ugly truth, although I don't think it will change much of anything. The class Foo is from an ancient Borland assembly that is all bound up in Delphi (and yes, we're migrating away but not there yet). So the actual code, that compiles up to VS2013, looks like this:
using Borland.Vcl;
using RepGen;
using SnapReportsForm;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace MigrantCOM {
[ComVisible(true)]
[Guid("48245BA3-736B-4F98-BDC5-AD86F77E39F4")]
[ProgId("MigrantCOM.Exports")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class MigrantCLRExports { // : MarshalByRefObject
public string Test(string s) { return s+s; }
}
[ComVisible(true)]
[Guid("1154D364-B588-4C31-88B9-141072303117")]
[ProgId("MigrantCOM.SnapRepCOM")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class SnapRepCOM {
TRepGen repGen;
TStringList snapRefs=new TStringList();
TForm parent=new TForm(null);
TMemo designerMemo;
List<TReference> references=new List<TReference>();
TRunAsSnapContext runAsSnapContext=new TRunAsSnapContext();
public SnapRepCOM() {
designerMemo=new TMemo(parent); designerMemo.set_Parent(parent);
...
}
So the class being instantiated is Borland.Vcl.TMemo which is part of the old Delphi assembly.
I'm leaving this as an answer, linking an image will fit better here than in a comment.
So if you want to use VS 2015 but still use the same good ol' version of the C# language that worked for years, you can configure your project to target a specific version:
This adds <LangVersion>5</LangVersion> in the csproj.

COM Callable Wrapper not working when called from delphi program

I've been requested to create a .Net dll for an old delphi program. I'm trying to do this with a COM Callable Wrapper, but I keep getting an error when it tries to load the dll (pretty general, something like "I couldn't load the dll"). Here is what the technical documentation says:
The DLL only needs to export one function under the name 'AUTHORIZE'.
function Authorize(InXml: PChar): PChar; stdcall;
(Delphi syntax. May be different in other languages.)
Here is my code for the CCW:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ComCallableWrapper
{
[Guid("C3FD922A-FB44-47B1-9C0C-8F7FAF57098B")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IAuthorizer
{
[DispId(1)]
string Authorize(string lnpInXml);
}
[ProgId("ComCallableWrapper.Authorizer")]
[ClassInterface(ClassInterfaceType.None)]
public class Authorizer : IAuthorizer
{
public Authorizer()
{
}
public string Authorize(string lnpInXml)
{
return "Approved!";
}
}
}
I also run this command "regasm /tlb:ComCallableWrapper.tlb ComCallableWrapper.dll /codebase" on the computer where the delphi program is running.
I've been doing some research on google about how delphi invokes functions on a dll, and I found at least 2 ways:
function Authorize(lnpInXml: pchar): pchar; stdcall; external 'DLLName.dll';
and
oleObject := CreateOleObject('ComCallableWrapper.Authorizer');
ShowMessage(oleObject.Authorize('Approved?'));
It looks like COM works a little bit different. Is there a way to change my CCW to work like the first way?
Regards.
You con't need COM. And indeed using COM is a mistake because the Delphi program is not looking for a COM DLL.
What you need to do is to export an unmanaged function from your managed C# DLL. That's a little tricky and is in fact not supported. These are your most attractive options:
Use Robert Giesecke's UnmanagedExports.
Write a mixed mode C++/CLI DLL that consumes your C# code. The mixed mode C++/CLI is capable of export native functions using __declspec(dllexport), .def files etc.
If you chose to use UnmanagedExports, the function would look like this:
[DllExport]
public static IntPtr Authorize(string InXml)
{
// your code goes here, for now return the input value
return Marshal.StringToHGlobalAnsi(InXml);
}
Implementing the function is a little tricky because you need to return a Delphi PAnsiChar, that is a C++ char*. You cannot use string for the return type and have to use IntPtr. But how do you allocate the string so that it remains valid for the caller to use it. The code above leaks the string in an HGLOBAL.
I can't advise you definitively how to resolve the lifetime of the string. The interface you are coding to is not at all well designed. Only you with more knowledge of the interface are in a position to resolve that issue.

Categories