Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how can i call dotnet function from c #34535

Closed
Wahitler opened this issue Apr 4, 2020 · 18 comments
Closed

how can i call dotnet function from c #34535

Wahitler opened this issue Apr 4, 2020 · 18 comments
Labels
area-Host question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@Wahitler
Copy link

Wahitler commented Apr 4, 2020

halo,have a good nice day.
i want to call dotnet function from c , not c++ ,but i can't find any solution.

I check dotnet Interoperability docs,i cant find anything about my need.Here are all about how call unmanaged code from dotnet context.

i found java language have JNI,and there is jni.h,Its aim is to make calling Java from C/C++ or C/C++ from Java easily.

Please reply me thanks

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-Meta untriaged New issue has not been triaged by the area owner labels Apr 4, 2020
@ghost
Copy link

ghost commented Apr 4, 2020

Tagging @vitek-karas, @swaroop-sridhar as an area owner

@jkotas jkotas removed the area-Meta label Apr 4, 2020
@chrisnas
Copy link
Contributor

chrisnas commented Apr 4, 2020

You could use COM Callable Wrapper @Wahitler : .NET classes can be seen as COM classes from C.
Read https://docs.microsoft.com/en-us/dotnet/standard/native-interop/com-callable-wrapper for more details.
I hope it helps

@Joe4evr
Copy link
Contributor

Joe4evr commented Apr 4, 2020

This is what NativeCallableAttribute should be for. A version of that type in the framework was only made public since last Febuary, but according to the design doc for function pointers, you should be able to make your own if it's not available:

The attribute is not special to mscorlib; the runtime will treat any attribute with this name with the same semantics.

@am11
Copy link
Member

am11 commented Apr 4, 2020

i want to call dotnet function from c , not c++

It seems that sometimes when docs are referencing to C++, they also mean C. See this doc https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting for the case of hostfxr.h and nethost.h etc. Although some of these are also valid C headers, but doc only talks about C++. It would be good if docs call C out explicitly for the cases where "library written in C++ with C API header" to meet the common expectation (of scenarios which are sensitive to bagginess of C++).

@jkotas jkotas added the question Answer questions and provide assistance, not an issue with source code or documentation. label Apr 5, 2020
@vitek-karas
Copy link
Member

@am11 is correct - the approach described by https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting should work for C just fine.

Side note: That doc only says that the sample it's using is written in C++, but it states that any native code should be able to so this. I do agree that maybe spelling out C explicitly would be good though.

If you can share: What is the scenario you need to call managed code from native app?

@jeffschwMSFT jeffschwMSFT removed the untriaged New issue has not been triaged by the area owner label Apr 6, 2020
@jeffschwMSFT jeffschwMSFT added this to the Future milestone Apr 6, 2020
@Wahitler
Copy link
Author

Wahitler commented Apr 7, 2020

thanks all,this is my scenario, and i still work at windows and .Net framework 4.6
image
I think netcore host is not suittble for me. do you have another solution?not include COM

@chrisnas
Copy link
Contributor

chrisnas commented Apr 7, 2020

You have no other option than passing COM interfaces back and forth.
This is not a problem when coding in C# and C; no need to use C++.
Look at https://www.amazon.com/NET-COM-Complete-Interoperability-Guide/dp/067232170X/ that covers all interop scenario including your case.

@vitek-karas
Copy link
Member

You should not need any special hosting interfaces. The initial PInvoke can pass a function pointer to the native library. Managed code can marshal delegates to native function pointers relatively easily. The native code would then call the function pointer to interact with the C# host.

The same can be done with COM, but it doesn't have to be COM, pure function pointers would work as well.

@g101519
Copy link

g101519 commented Apr 11, 2020

I have an unmanaged "plain" C application that places a GUI around the SQLite database machine.
The GUI then adds a scripting language "broadly" based on VB. To make it more flexible I added the ability to call COM objects using CreateObject, GetIDsofNames and Invoke.
(Totally independent of type libraries!)
Throw in DLL access and the ability to dynamically add GUI objects (Windows, edit fields, buttons,...) and I was quite happy for a long time.
But recently I wanted to write a script to upload/download file to the Web.
So I accessed the WinSCP application via COM.
Then I found out that some WinSCP functionality is NOT exposed via COM (I beg forgiveness if I have misunderstood WinSCP capabilities), but IS available to PowerShell.
i.e. exposed, I presume, via dynamic access to the EXE via the CLR and "hosting".
WinSCP is not the problem, but it did cause me to decide that I want to extend my "plain" C application with the ability to load a .Net assembly and access the capabilities offered by such assemblies.
Again, I ask for forgiveness, this time that an old programmer (first program written back in 1971) does not know all of the correct terminology.
Whatever, I have tried to follow https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting but am getting in to a mess.

The assembly I use ( my first venture into C#)is based on a Console Application.

using System;

namespace DotNetConsole
{
class Program
{
static void Main( )
{
Console.WriteLine("Hello World!");
}
}
}

w.r.t. https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting
I can get through the load_hostfxr Okay.

I got through get_dotnet_load_assembly okay, but it was a fight to get a runtimeconfig.json file out of the build process.
Am still not sure how I did it.

A critical parameter was specifing the config_path parameter:
LPCWSTR config_path = L"C:\Users\gds\DotNet\DotNetConsole\bin\Debug\netcoreapp3.1\publish\DotNetConsole.runtimeconfig.json";

BUT I can not get through the load_assembly_and_get_function_pointer call.
I can not tell if the .Net assembly that I have is correctly referenced or not.

I understand (?) that a .Net Exe and .Net DLL are almost the same.
How should I populate the first three parameters of the load_assembly_and_get_function_pointer function call.

int rc = load_assembly_and_get_function_pointer(
dotnetlib_path , // is this really a path or a path + file name, what should it be in this particular case
dotnet_type , // should it work with DLLs and EXEs, what should it be in this particular case
dotnet_type_method, // I presume L"Main" would be correct in this particular case
delegate_type_name , // now I am really lost.
nullptr,
(void**) & hello // will hold the address of the target functionality
);

  1. Where or where can I find documentation on the various capabilities referenced in https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting ?
  2. Does anyone have a COMPILED assembly that can be used for test purposes?
  3. Do I really need a json file, I would have thought that a DLL or EXE would hold everything?
    I know that once I have overcome the issues of load_assembly_and_get_function_pointer I will simply fal-lover at the next step.

But I shall keeping on plodding on.

@vitek-karas
Copy link
Member

I suggest you look at the official sample for this: https://github.com/dotnet/samples/tree/master/core/hosting/HostWithHostFxr

You should be able to build that from scratch and get that to run (please let us know if there are issues with that - follow the readme there for details on how to do it). Once you have that working you can start by tweaking it to match your needs. That's probably a faster way to get started than writing it from scratch on your own.

To answer your questions:

  1. What capabilities do you have in mind? The doc you reference describes several methods to do the hosting (mostly due to historical reasons). I strongly suggest you use the first one (hostfxr based). This doc alongside the sample acts as the documentation. If there are missing bits please let us know.
  2. We don't have compiled samples, as that sort of defeats the purpose of the sample. But you should be able to easily create a compiled sample out of https://github.com/dotnet/samples/tree/master/core/hosting/HostWithHostFxr
  3. The JSON file is necessary. Unlike .NET Framework, .NET Core can install many versions of the runtime/framework side-by-side. As such the compiled code (the entire application/component) needs to be able to specify which framework and what version it needs to run. This if what the .runtimecofig.json is for. The sample shows how to tell the SDK to produce it for you even if you're building a class library (and not an executable project).

As a side note: .NET Core doesn't really have the duality of .dll and .exe being almost the same. In .NET Core all managed code (C#) is always compiled to a .dll. The SDK can create an executable for an application (on Windows that would be .exe), but that file is a simple "bootstrapper" which just loads the right runtime and executes the .dll which is next to it (which contains the compiled C#).

Debugging hints: The load_assembly_and_get_function_pointer can fail during "hosting" layers execution, in which case it should return one of the error codes listed in: https://github.com/dotnet/runtime/blob/master/docs/design/features/host-error-codes.md

If that's not enough details make sure you can access the stderr output of the process (if this a UI app on windows this can be a bit tricky to do, but you should be able to start it from console and redirect the error output to a file. Typically this can be done by adding 2> output.err to the command).

If the error which should be printed out to stderr is still not enough, you can try enabling diagnostic tracing from the host by setting COREHOST_TRACE=1 and COREHOST_TRACEFILE=hosttrace.txt. This will produce very detailed log of all the hosting code into the specified file.

If the problem occurs during the managed portion you can also attach a debugger. Attach VS to the process and when attaching select both "native" and "Managed .NET Core" debuggers. This will enable so-called "Mixed mode" debugging where the debugger will be able to see both native and managed code at the same time and you will be able to debug both (breakpoints and so on). Make sure to enable stopping on all managed exceptions which should help with diagnosing the failures.

@g101519
Copy link

g101519 commented Apr 14, 2020

Cheers.
I will follow the suggestions.
I strongly suspect that I have taken on more than I can handle.
I, and a very small number of work colleagues, use my plain C unmanaged "interpreter" primarily for scripting the embedded SQLite engine.
But most of the colleagues are Excel orientated, so I added the ability to use COM (from the plain C interpreter) and thus made it possible to use COM objects and exchange information with the Excel colleagues..
The COM ability opened up all sorts of possibilities such as consolidating multiple Excel Files/sheets into a database, and then generating customer specific letters in Word.
Just to show my donkey headedness I should add that the plain C program implements a reverse Polish notation (FORTH) interpreter. Sitting on top of the FORTH, written in FORTH, is a Dijkstra Shunting Yard Algorithm to convert script/user input from infix to postfix notation, and to provide access to the embedded SQLite engine, COM functionality...
The FORTH application is then compacted, encoded and physically inserted in to the FORTH interpreter, giving me a single executable that provides the target scripting environment. Even I would not expect anyone (except absolute FORTH devotees) to write scripts in FORTH ;-)

The interpreter visible to the user follows a broadly VB / VBA syntax.
So well formed user/script input can be interpreted on-the-fly, such as:

  Dim myExcel = CreateObject ("excel.application")
  myExcel.visible = 1
  Dim myWb = myExcel.Workbooks.Add

The interpreter is augmented with built-in commands so that information can be exchanged between the SQLite database and Excel.
Such as
xlwrite ( " select * from myTable where not myColumn = '' ")
This will insert the results of a database query in to the opened Excel.
It was a really pain-in-the-posterior developing the parser to accept COM syntax.
Purely for devilment, for the joy of coding, and because I like learning, I keep on looking for "more" things to add to the interpreter.
So I added access to DLLs, the ability to script in GUI components such as windows, buttons,...
Then of course COM makes it possible to include XML and HTML functionality ( e.g. via CreateObject ("Microsoft.XMLHTTP") ).
But playing around with the Internet Protocol got me interested in uploading/downloading files, which eventually took me to WINSCP.
I could use my interpreter to create a WINSCP script file, and then use ShellExecuteW (via DLL access) to trigger WINSCP.
But I stumbled on the following in the WINSCP documentation:

Using scripting interface directly is recommended for simple tasks not requiring any control structures. For complex tasks, using WinSCP .NET assembly is preferred

So it was time to start learning (again, again,...).
So just under 2 weeks ago I started trying to understand CLR and having my native-code/unmanaged application act as a host to managed code.

The first success came with MetaHost.h ,mscoree.lib and CorBindToRuntimeEx and ExecuteInDefaultAppDomain.
But I understand that CorBindToRuntimeEx is depreciated, and ExecuteInDefaultAppDomain can only handle hosted interfaces that conform to the signature: static int pwzMethodName (String pwzArgument).
Surely (?) there is a way to use CorBindToRuntimeEx and access hosted interfaces with other signatures?

So I moved on to MetaHost.h ,mscoree.lib and CLRCreateInstance and ExecuteInDefaultAppDomain.
That avoids the depreciation issues, but the interface signature is still limited.

But at least, by including a global variable in my target test EXE/DLL I was able to verify that :

  1. Main is not automatically called when the EXE is loaded into the CLR
  2. Successive calls to ExecuteInDefaultAppDomain (same Exe/same interface): the global variable is maintained. So ExecuteInDefaultAppDomain does not unload the target EXE(/DLL) and its context.

Next I tried a coreclrhost.h based approach.
Here I am still fighting to understand coreclr_initialize .
For example in the interpreter I can not know where the user's target managed code resides, so I need to be able to on-the-fly add the target's path to the TPA of the CoreCLR host.
But it looks as if I can only call coreclr_initialize once (to provide the path via property Keys/values).

Due to my impatience, I actually started with the nethost approach, but success has not yet been forth coming.
In order to get the json file I decide to download .NET Core, since I was having problems in VS2019.

This is the slightly reduced source code for my test assembly:

using System;
using System.Windows.Forms;

namespace InjectExample
{
    public class Program
    {
        public static int myInt;
 
        static int EntryPoint(String pwzArgument)
        {
            // code to    display the String   AND   myInt
            myInt = myInt + 1;
            return 0;
        }

        static void Main(string[] args)
        {  
            myInt = 1; 
            EntryPoint("hello world");  // so that if Main is automatically called I will see it 
        }
    }
    // a second Class   so I can check that multiple classes are supported
    public class ProgramA
    {
        // same name as in class Program   so I can check that I can access duplicate procedure names in different classes
        static int EntryPointA(String pwzArgument)
        {
            // code to    display the String , but does not display the (unreachable)  myInt
            return 0;
        }
    }
}

But it fails in my C host

  LPCWSTR  dotnetlib_path = L"C:\\Users\\gds\\Documents\\Visual Studio 2019\\InjectExample\\bin\\Debug\\netcoreapp3.1\\publish\\InjectExample.exe";

    LPCWSTR  dotnet_type = L"InjectExample.Program";

    LPCWSTR  dotnet_type_method = L"EntryPoint";
    component_entry_point_fn hello = NULL;
    int rc = load_assembly_and_get_function_pointer( 
        dotnetlib_path ,
        dotnet_type,
        dotnet_type_method,
        nullptr   /*delegate_type_name*/,
        L"Hello me",
        (void**) & hello
        );

with rc = COR_E_ARGUMENTOUTOFRANGE

Now that I look at it, I wonder whether delegate_type_name is my problem.
Something for me to look at tomorrow.

My goal is to enable a user to write a script that accesses the functionality resident in a .NET assembly, for example WINSCP.
Just as for my COM interface, I do not intend to provide the user with method or type information.
My COM handling does not access any type libraries and only supports function calls with parameter-by-position. Maybe one day when I am feeling motivated I shall access type libraries, but not today!

Q) if the nethost approach needs a json file, does it mean that for managed code that is not .NET Core, that I must use the coreclrhost.h approach?

Your side note on the duality of .dll and .exe was very enlightening.

my thanks, and once more, I will follow the suggestions that you made.

Oh, one last thing. Why don't I just use say PowerShell?

  1. no embedded database
  2. it is fun to have a challenge, even more fun to master the challenge, and writing my own code is safer than chasing women (I am married), safer than motorbikes (already have broken a leg), is less likely to destroy as many brain cells as alcohol, and keeps me indoors in this period of Corona lock-down.

Cheers

There is still a long plod ahead.

@vitek-karas
Copy link
Member

In your C host code point it to the .dll file (not the .exe).
Ideally you would actually build the managed project as classlib (exclude <OutputType>exe</OutputType> in the project file) and the other changes as per the sample project.
You have the type name and method name right. But you will need the delegate type.
Just add delegate int MyDelegateType(string arg) to the InjectExample namespace and then specify InjectExample.MyDelegateType as the delegate type from the C code.

Note that in this case you get back a function point which is directly calling the managed method. So you can call it multiple times without going through the hosting layers again.

@Wahitler
Copy link
Author

Wahitler commented May 7, 2020

thank u, function pointer is greate solution.

but i have some difficulties how to marshal data

C++ code
image

image

image

C# Code

image

image

image

image

image

that's right? help me please, or give me some doc like this

@vitek-karas
Copy link
Member

/cc @AaronRobinsonMSFT @elinor-fung

Can you please be more specific in what exactly you have difficulty with?

@AaronRobinsonMSFT
Copy link
Member

@Wahitler As @vitek-karas said having specific examples of what is happening would help us to offer next steps. Some observations on the interop though.

  1. ByValTStr with ANSI marshalling is only UTF8 encoding on Unix, on Windows it is ANSI. This means if the data being passed is a MBCS the string will be broken on Windows. There are also other details that sometimes trip users up. See Use PtrToStructure with UnmanagedType.ByValTStr will lose data, is it a BUG? #13273 and Unix: Interop does not handle invalid UTF8 characters correctly #9884.

  2. It seems there are size_t types in the native signature, but using int in managed. What is the bitness of the target runtime? Note that sizeof(size_t) is bitness dependent in native whereas sizeof(int) in C# is always 4.

Providing the output of dotnet --info would also be helpful.

/cc @jkoritzinsky

@Wahitler
Copy link
Author

Wahitler commented May 8, 2020

image

image

@AaronRobinsonMSFT
Copy link
Member

@Wahitler Is there any chance you could post text instead of screenshots for the native and managed definitions? trying to build a local repro is very annoying and error prone when one needs to transcribe from an image. If you have a small repro already existing that would be idea though.

@vitek-karas vitek-karas added the untriaged New issue has not been triaged by the area owner label May 15, 2020
@vitek-karas vitek-karas removed this from the Future milestone May 15, 2020
@agocke agocke removed the untriaged New issue has not been triaged by the area owner label Jul 6, 2020
@agocke
Copy link
Member

agocke commented Jul 6, 2020

Closing due to inactivity. Let us know if this is still a problem.

@agocke agocke closed this as completed Jul 6, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Host question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests