I am trying to call a function from a c# dll in a flutter desktop app,
In the C# dll I have,
using System;
using System.Runtime.InteropServices;
namespace MathLibrary
{
// NOTE: I have set set [assembly: ComVisible(true)] in AssemblyInfo.cs
[ComVisible(true)]
[Guid("66DE2FB9-7A3B-4C33-AF26-9AD5EDD4C71F")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMathLibrary
{
[DispId(1)]
string multiply(int a, int b);
};
[ComVisible(true)]
[Guid("021E950E-3612-4FAD-9F15-F61632A95BD8")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MathLibrary.MathCalc")]
public class MathCalc : IMathLibrary
{
public string multiply(int a, int b)
{
return "Product is " + (a * b).ToString();
}
}
}
I used this repo as the base flutter app.
In the app I have used platform channel to communicate between dart and c++ code. In c++ code I am trying to call the c# function (in file windows/runner/custom_channel.cpp).
After some googling, I came up with the following
First I added an import to the tlb file (had to add import to generated tlh file for IntelliSense to work)
#import "bin/MathLibrary.tlb"
using namespace MathLibrary;
And the following function is supposed to call the c# function
CoInitialize(NULL);
MathLibrary::IMathLibraryPtr IcalcPtr;
HRESULT hr = ::CoCreateInstance(__uuidof(MathLibrary::MathCalc), NULL,
CLSCTX_INPROC_SERVER,
__uuidof(MathLibrary::IMathLibrary),
(void**)&IcalcPtr);
_bstr_t calcVal;
if (FAILED(hr) || IcalcPtr == nullptr) {
// CoCreateInstance failed
// THIS CONDITION IS MET
(*resPointer)->Error("Cannot Create COM Object");
return;
}
//IcalcPtr->multiply(a, b, &calcVal);
calcVal = IcalcPtr->multiply(a, b);
// not sure how to convert bstr to std::string
const char* calcStr((const char*) calcVal.GetBSTR());
c.assign(calcStr);
CoUninitialize();
The CoCreateInstance fails.
Since I have no experience with c++, I am confused,
what is IMathLibraryPtr(I didn't define in c#)
Intellisense showed that a and b in IcalcPtr->multiply(a, b) are long but I thought it would be int
When I make a release build do I need to include the tlb or dll
what is tlh file, it got generated during build and I got Intellisense support only if I add an import to that file
I would like to understand in general how to interact with c# com-interface from c++ and also how to make it work in my case. Sample code and document links would be helpful
The problem was with the way the c# dll was built and not with the way it was called from c++. I had built the dll for "Any CPU" once I built it for x64 it worked.
Related
I wanna call a uwp app with Uri in a win32 C++ console application.The first thing I thought is using LaunchUriAsync, but I couldn't find Windows.System.Launcher.LaunchUriAsync in win32 C++. So I wanna create a uwp class library to call LaunchUriAsync and win32 call this library. I find an example and now I can load the library sucessfully, but GetProcAddress always returns null. Not sure whether it is feasible calling uwp class library in win32 console. Pls help me out. The project is at https://github.com/vincent1000/win32CallUwpLibrary
The code is very simple:
UWP Classy Library:
namespace ExportedCodeSolution
{
public class Class1
{
[DllExport(ExportName = "callUri", CallingConvention = CallingConvention.StdCall)]
static public async void callUri()
{
Console.WriteLine("call URI start");
await Launcher.LaunchUriAsync(new Uri("www.bing.com"));
}
}
}
And Win32 Console:
using CallUriFn = void(__stdcall*) ();
int main()
{
HMODULE mod = LoadLibraryA("ExportedCodeSolution.dll");
CallUriFn fn = reinterpret_cast<CallUriFn>(GetProcAddress(mod, "callUri"));
fn();
}
Also, is any other method to call LaunchUriAsync in win32? I have searched some methods but none works for me.
The solution is trivial: Simply call Launcher.LaunchUriAsync from your console application. The easiest route is to use C++/WinRT. Assuming that you are using Visual Studio with the C++/WinRT extension installed, create a "Windows Console Application (C++/WinRT)", and replace the wizard generated code with this:
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.System.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::System;
int main()
{
init_apartment();
Uri uri(L"www.bing.com");
Launcher::LaunchUriAsync(uri).get();
}
This will compile, but fail at runtime due to "www.bing.com" not being a valid URI. This needs to be replaced with a valid URI (e.g. "https://www.bing.com").
How can I call C# functions (a DLL) from D?
I have tried or am looking at the following:
Using the Derelict Mono package
(https://github.com/kubasz/derelict-mono)
Using Unmanaged Exports (See
Calling C# from C), D
to C to C#, eventually maybe eliminating the C.
The Derelict Mono approach works well for Hello World programs, however a larger DLL (with references to lots of other assemblies, each of which may or may not use genuine Windows API calls) fails as the DLL is not properly loaded.
Initial experiments with Unmanaged Exports result in errors with MSBUILD.
Try the following. First the D code:
module main;
import std.stdio;
import std.conv;
extern (C++) ulong receiveMe(ulong i);
extern (C++) ulong freeMe(ulong i);
void main() {
ulong l = receiveMe(0);
char* p = cast(char*)l;
char[] s = to!(char[])(p);
byte[] b = cast(byte[])(s.dup);
writeln("The answer is " ~ to!string(s));
ulong m = freeMe(0);
}
Then a C++/CLI shim:
#include "stdafx.h"
#using "...\CS-Interop\bin\x64\Debug\netstandard2.0\CS-Interop.dll"
using namespace System;
UInt64 sendMe(UInt64 arg) {
return CS_Interop::Test::receiveMe(42);
}
UInt64 freeMe(UInt64 arg) {
return CS_Interop::Test::freeMe(42);
}
Lastly the C#:
using System.Runtime.InteropServices;
using System.Text;
namespace CS_Interop {
public class Test {
public static byte[] buffer;
public static GCHandle gcbuf;
public static ulong receiveMe(ulong arg) {
string s = "I was a string " + arg;
s = (s.Length + 2) + s;
buffer = Encoding.ASCII.GetBytes(s);
gcbuf = GCHandle.Alloc(buffer, GCHandleType.Pinned);
ulong l = (ulong)gcbuf.AddrOfPinnedObject();
return l;
}
public static ulong freeMe(ulong arg) {
gcbuf.Free();
return 42;
}
}
}
I'm still looking at ways to get rid of that C++/CLI shim.
This code is written in such a way that you can poke around with the VS debugger.
This is very simple to set up and test in Visual Studio. With Visual D installed, first set up a C++/CLI project (NOT a Visual D project) and park the D and C++ code there. Then setup a C# DLL project under the D project.
It is one thing to call C# code from D, but another thing to get data back unless you are using only simple scalar types like int. The key lines of C# are
gcbuf = GCHandle.Alloc(buffer, GCHandleType.Pinned);
ulong l = (ulong)gcbuf.AddrOfPinnedObject();
where you first need to pin the thing you are sending back then send the address back to D. There is no tedious mucking about with marshaling in the C++ part, your D code just needs to be able to deal with whatever sits behind the pointer.
Be sure also to free the pinned pointer once you're done with it. Comment out the freeMe line in the D code and watch the memory usage grow (and grow) in VS.
Personally, I find the pin process a bit fickle as GCHandle.Alloc will only work when its first argument, be it a byte array or structure, contains blittable items.
See also https://learn.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types
I have a preliminary string passing solution from D to (a small amount of) C++ to C# based on the following articles: (I gave up on unmanaged exports from Robert Giesecke)
C# "Unmanaged Exports" (tutorial from Hans Passant)
Calling C# function from C++/CLI - Convert Return C# string to C String
The D to C++ integration with Visual D just works.
https://rainers.github.io/visuald/visuald/vcxproject.html (See Visual C/C++ Project Integration)
https://dlang.org/spec/cpp_interface.html
You can use Unmanaged Exports to call C# from D. I've done it without problems.
See https://sites.google.com/site/robertgiesecke/Home/uploads/unmanagedexports
However, when I tried Unmanaged Exports with Visual Studio 2017, I also could not get it to work. Unmanaged Exports worked well with VS2015. Considering the link is from July 2009, other aspects could have become stale.
Be sure to read the instructions carefully, and most importantly make sure you are building for x86 or x64, and not "any CPU". Marshalling the data will be another challenge.
I think this might be more generic, but in this case I try to create a c++ CLR to Microsoft Dynamic NAV.
If I do this in C# it works fine. C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VisualCSharp
{
public class MyTest
{
public MyTest()
{
}
public int AddTest(int a, int b)
{
return a + b;
}
}
}
When I try to add that to Dynamic NAV, I find it:
Select and press OK:
So far so good.
Then I like to do the same in C++ with a CLR class.
I start with creating a C++ CLR Class library and add the code for the test: (I did it inline here, just to keep code simple, but it does not matter if I separate it.)
#pragma once
using namespace System;
namespace VisualCPP
{
public ref class MyTest
{
public:
MyTest()
{
};
int AddTest(int a, int b)
{
return a + b;
};
};
}
Do the same with this. In Assembly list it come up identical to the VisualCSharp addin, but it shows up as Processor "x86", but thats ok. I have C# that also do that (COM wrappers) and they also work fine.
However, when I press "OK" i do get an error telling me that it can not load the type "VisualCPP".
So, question is: What am I missing? I know that the CLR code is different as it is not pure managed (even using the depricated /pure compile option does not work), but is there some kind of setting or declaration that had to be done to make my class visible?
As far is I know you cannot use unmanaged code in NAV.
You assembly also need to be signed - in you example the Public Key Token is null therefore it's not gonna work.
Situations like that I always recommend to create a wrapper dll to handle the unmanaged code though it (for example this is what Microsoft was done with the Office SDK)
I hope it helps.
Cheers!
I have a C++ COM dll and I have register it with regsvr32. I want tho use the functions and class of the dll Inside my code. Unfortunatly I dont possess any .h and it doesnt come with a .tlb file. I have the documentation how to use the functions and class but there is no information about how to link the dll to my project so I could use it. I am new with using external COM interface so i'm not quite sur where I could find this information.
I have tried #import "example.dll" (dll inserted in the project folder but it looks like it doesn't work I have an unable to load dll error. My program is mixed CLR / unmanaged C++.
Any suggestions?
Thanks in advance
If enough information is provided, you can define the interfaces in a header file yourself. I would recommend using #import to import an existing COM type library and investigate the generated .tlh file for ideas. For a simple interface with functions, for example, the code looks something like this:
struct __declspec(uuid("Interface-Guid-with-Dashes")) IInterfaceName : IUnknown
{
virtual HRESULT __stdcall get_Value (/*[out,retval]*/ long * result) = 0;
virtual HRESULT __stdcall Execute (/*[in]*/ int value) = 0;
};
HI I have a application developed in VC++6.0 ,now I want use some new features from .NET and developed a library, to develop this library I have to use the CLR-VC++ now I packaged this in a DLL.Now I need to call the routine of this DLL in my MFC application.
I tried to write a small MFC application to load this DLL, All the time the LoadLibrary() call is failing #err =126, module not found.I check the the dll with dependency walker everthig is fine there. Please Help me in this regard.
If possible provide me a sample code or link.
Thanks in advance
-Sachin
Use ClrCreateManagedInstance to create a COM-Callable-Wrapper for the object you want to call. Then use it like any other COM type.
you have to go to property page -> Common properties ->Add New reference and include you
CLR Address there .
I have a native C++ application which uses a managed C++ assembly and loads it with LoadLibrary() without problems. I had to do two things, however, before LoadLibrary() worked:
Make sure that the current directory is the one where the managed assembly resides (use chdir() to change directory)
In the managed assembly, the first function invoked by native code only defines the handler for AppDomain::CurrentDomain->AssemblyResolve event which explicitly loads assemblies from the folder of the managed application. It then invokes another managed function to do the rest of the initialization.
The reason for the last point is that CLR attempts to load an assembly dependency only if a function uses it. So I had to ensure that types in non-system assemblies are not referenced before the AssemblyResolve handler has been defined.
ref class AssemblyResolver
{
public:
/// The path where the assemblies are searched
property String^ Path
{
String^ get()
{ return path_; }
}
explicit AssemblyResolver(String^ path)
: path_(path)
{ /* Void */ }
Assembly^ ResolveHandler(Object^ sender, ResolveEventArgs^ args)
{
// The name passed here contains other information as well
String^ dll_name = args->Name->Substring(0, args->Name->IndexOf(','));
String^ path = System::IO::Path::Combine(path_, dll_name+".dll");
if ( File::Exists(path) )
return Assembly::LoadFile(path);
return nullptr;
}
private:
String^ path_;
};
extern "C" __declspec(dllexport) void Initialize()
{
String^ path = "The path where the managed code resides";
AssemblyResolver^ resolver = gcnew AssemblyResolver(path);
AppDomain::CurrentDomain->AssemblyResolve += gcnew ResolveEventHandler(
resolver,
&AssemblyResolver::ResolveHandler
);
FunctionWhichUsesOtherManagedTypes();
}