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.
Related
Recently I have been trying to get some Point Cloud Library functionality going in my .NET framework application, and considering that there is no completely functional wrapper for PCL for C#, I made my own for a few functions as a test. Something like this:
[DllImport(DllFilePath, CallingConvention = CallingConvention.Cdecl)]
public extern static IntPtr StatisticalOutlierFilter(IntPtr data, int length, int meanK = 50, float mulThresh = 1.0f);
Which calls a function from a C++ library, such as this:
EXPORT VectorXYZ* StatisticalOutlierFilter(VectorXYZ* data, int length, int meanK, float mulThresh) {
auto processedCloud = process.StatisticalOutlierFilter(data, length, meanK, mulThresh);
auto processedVector = convert.ToVectorXYZ(processedCloud);
return processedVector;
}
Where EXPORT is defined such for gcc:
#define EXPORT extern "C" __attribute__ ((visibility ("default")))
And relevant processing function from PCL is implemented such in a class (note that the returned is a boost shared pointer):
PointCloud<PointXYZ>::Ptr Processors::StatisticalOutlierFilter(VectorXYZ* data, int length, int meanK, float mulThresh) {
auto cloud = PrepareCloud(data, length);
PointCloud<PointXYZ>::Ptr cloud_filtered(new PointCloud<PointXYZ>);
StatisticalOutlierRemoval<PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(meanK);
sor.setStddevMulThresh(mulThresh);
sor.filter(*cloud_filtered);
return cloud_filtered;
}
This procedure works well with a dll built w/MSVC and running the whole thing on Windows, though the final target is gcc/Linux/Mono, where I get several errors of the following type (this is from mono debug):
'libavpcl_dll.so': '/usr/lib/libavpcl_dll.so: undefined symbol: _ZN3pcl7PCLBaseINS_8PointXYZEE13setInputCloudERKN5boost10shared_ptrIKNS_10PointCloudIS1_EEEE'.
I have investigated quite a bit so far, and have set my CmakeLists.txt to set(CMAKE_CXX_VISIBILITY_PRESET hidden) , therefore, I imagine, only functions I defined as EXPORT should be visible and imported - however, that is not the case, and I get the aforementioned errors. PCL was installed on Windows via vcpkg and on Xubuntu via apt. I am somewhat stumped as to what is the error source, considering the code runs well on windows, and builds without issue on Linux. Thanks.
I've been running into the same issue as you. I solved it by adding each reference library into the CMakeLists.txt file (I was missing the reference files which gave me the similar missing symbol issues).
I'm at the 'I don't know why this worked' stage but I can give you step by step implementation (I'm also trying to use DllImport into .NET on Linux).
Started with this:
https://medium.com/#xaviergeerinck/how-to-bind-c-code-with-dotnet-core-157a121c0aa6
Then added my in-scope files thanks to the main comment here: How to create a shared library with cmake?:
add_library(mylib SHARED
sources/animation.cpp
sources/buffers.cpp
[...]
)
run cmake .
run make -j$(grep -c ^processor /proc/cpuinfo)
copy path to .so file
DllImport path from above to my c# app
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)
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.
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.
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()