I have this code in a c++ file, with compiles to a dll.
#include "stdafx.h"
#include "WHUU.h"
#include "stdafx.h"
typedef int (__stdcall * Callback)(const int text);
static int x , y= 0;
Callback Handler = 0;
int getX()
{
return x;
}
void setX(int i)
{
x = i;
}
void setY(int i)
{
y = i;
}
int getY()
{
return y;
}
extern "C" __declspec(dllexport)
void __stdcall SetCallback(Callback handler) {
Handler = handler;
}
extern "C" __declspec(dllexport)
void __stdcall addX(int x) {
setX(x);
}
extern "C" __declspec(dllexport)
void __stdcall addY(int y) {
setY(y);
}
extern "C" __declspec(dllexport)
void __stdcall TestCallback() {
int z = getX() + getY();
int retval = Handler(z);
}
My c# application now has to load this dll on runtime. Add to the callback and call the functions. I dont want to use a class. I could load the class and with
Type[] types = moduleAssembly.GetTypes();
But this overkill! Also c++ is not managed.
I mean its so tiny! (and yes this is an example , but the "real" is just as big as this example).
How do i do that?
Thanks you for your help!
add:
i dont like frameworks (like pinvoke / assembly)
the function names / types are fixed and will never change (think of a driver.dll read write)
this dll is written by customers so it should be as easy as possible!
You could also do it over p/invoke, as example:
[DllImport("unmanaged.dll", CharSet = CharSet.Ansi)]
private extern static int yourFunction(int var1, int var2);
i dont like frameworks (like pinvoke / assembly)
I'll suggest P/Invoke anyway. I don't think there are any reasonable alternatives. Perhaps writing a managed wrapper in managed C++, but really?
When you use P/Invoke the .NET runtime will dynamically load the DLL's that you have specified (and this is done when you first call the function). There is no reason to use P/Invoke to call LoadLibrary first.
You can either use P/Invoke to call the dll directly, or you can create a C++/CLI wrapper for it.
Here's how you can do it using P/Invoke:
Add the compiled dll to your C# project, set its 'Copy to Output Directory' property to 'Copy Always'. and call it like this:
class Program
{
[DllImport("cppdll.dll")]
private static extern void addX(int x);
[DllImport("cppdll.dll")]
private static extern void addY(int y);
static void Main(string[] args)
{
addX(5);
addY(7);
}
}
One way to do this would be through API. Find below an example for the setX and getX method from your sample above.
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
internal static extern IntPtr LoadLibraryEx(string libraryPath, IntPtr fileHandle, int actionFlag);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
internal static extern bool FreeLibrary(IntPtr libraryHandle);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
internal static extern IntPtr GetProcAddress(IntPtr dllPointer, string functionName);
IntPtr ptr = IntPtr.Zero;
private delegate void setX (int i);
private delegate int getX();
private setX setXDel;
private getX getXDel;
public Constructor()
{
loadLib();
}
public void YourMethod()
{
setXDel(100);
int y = getXDel();
Console.WriteLine(y.ToString());
}
private void loadLib()
{
string path = "your dll path";
ptr = LoadLibraryEx(path, IntPtr.Zero, 0);
if (ptr == IntPtr.Zero)
throw new Exception("Cannot load dll.");
IntPtr addressPtr = GetProcAddress(ptr, "setX");
setXDel = (setX)Marshal.GetDelegateForFunctionPointer(addressPtr, typeof(setX));
addressPtr = GetProcAddress(unrarPtr, "getX");
getXDel = (getX)Marshal.GetDelegateForFunctionPointer(addressPtr, typeof(getX));
}
public void Dispose()
{
if (ptr != IntPtr.Zero)
{
FreeLibrary(ptr);
}
}
I used P/Invoke, post my own cause the callback was not included by the others. But they were good answers! The C# Applicationhas use this GUI.
The Code for the Application is this : (mind it is a prototype to answer a technical question)
public partial class Form1 : Form
{
private delegate int Callback(int text);
private Callback mInstance; // Ensure it doesn't get garbage collected
[DllImport(#"C:\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]
private static extern void SetCallback(Callback fn);
[DllImport(#"C:\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]
private static extern void addX(int x);
[DllImport(#"C:\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]
private static extern void addY(int y);
[DllImport(#"C:\\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]
private static extern void TestCallback();
private int Handler(int text)
{
textBox3.Text = text.ToString();
return 42;
}
private void button1_Click(object sender, System.EventArgs e)
{
mInstance = new Callback(Handler); // to set the callback in lib
SetCallback(mInstance); // could also be withhin constructor!
addX(int.Parse(textBox1.Text)); // other people in this thread posted this correct answer
addY(int.Parse(textBox2.Text));
TestCallback();
}
public Form1()
{
InitializeComponent();
}
So if you want to use a c++ lib with functions and callbacks (like in the question posted) you can use this.
But how to change the lib?
The Path to the lib is hardcoded (see [dllImport ......] ). But you
can swop the lib in filesystem. This can happen AFTER you build this
application.
If the lib is not on the given path an exception is thrown. So if
your program wants to use this lib first check the lib is present on the filesystem.
(not included in this simple prototype)
So to switch functionality (swop a driver lib ect) you copy a different lib with the
same name into the given path. (Copy paste - maybe change name)
This solution has the least overhead and is good if you never change the interface of an dll after the application was build!
Related
I'm working on a .NET 5 project to bind the AWS-Encryption-SDK from C and C++ to C# using PInvoke, type marshalling, and the DllImportAttribute. When attempting to call this C++ function
aws_cryptosdk_keyring *Build(const Aws::String &generator_key_id, const Aws::Vector<Aws::String> &additional_key_ids = {}) const;
using
[DllImport("aws-encryption-sdk-cpp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern aws_cryptosdk_keyring Build(string generator_key_id, string[] additional_key_ids = null);
I get this error
System.EntryPointNotFoundException : Unable to find an entry point named 'Build' in DLL 'aws-encryption-sdk-cpp.dll'.
The dll signature for this function is
?Build#Builder#KmsKeyring#Cryptosdk#Aws##QEBAPEAUaws_cryptosdk_keyring##AEBV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##AEBV?$vector#V?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##V?$allocator#V?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std###2##7##Z
So far nothing that I have tried as the EntryPoint has been successful. Does anyone have any ideas?
Expanding on #jwezorek's comment, C# does not know how to call C++ (class) functions, only C functions. There are several reasons why this is so, but I won't get into them here.
You need to create (in C++) a wrapper that converts each C++ call to a 'flattened' C call. For example:
extern "C" void *createClass() {
return (void*) new myClass();
}
extern "C" void destroyClass(void* self) {
delete (myClass*) self;
}
extern "C" int callFunction1(void* self, int x, int y) {
return ((myClass*) self)->function1(x, y);
}
You'll need to write a function for each class function (plus the constructor and destructor). Then compile, linking it to that original C++ DLL's library. Then in C#, write a wrapper to call your flattened class:
public class myClass : IDisposable {
[DllImport("myFlattenedDll", EntryPoint="createClass")]
public static extern IntPtr createClass();
[DllImport("myFlattenedDll", EntryPoint="destroyClass")]
public static extern void destroyClass(IntPtr self);
[DllImport("myFlattenedDll", EntryPoint="callFunction1")]
public static extern int callFunction1(IntPtr self, int x, int y);
private IntPtr self;
public myClass() {
self = createClass();
}
~myClass() {
Dispose();
}
public virtual void Dispose() {
lock (this) {
destroyClass(self);
GC.SuppressFinalize(this);
}
}
public int callFunction1(int x, int y) {
return callFunction1(self, x, y);
}
}
You should also look into a tool called SWIG that can automate a good deal of the wrapper coding for you.
I'm looking for exchange a string from my c++ code with my c# form.
Here is my code in my C# program:
[DllImport("libDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern string valeurExpr(IntPtr pImg);
public unsafe string objetLibValeurCarteExpr()
{
return valeurExpr(ClPtr);
}
And my code in my C++ program :
Class IHM
class IHM {
private:
std::string card_number_str;
std::string card_expr_str;
std::string card_porteur_str;
My extern
extern "C" _declspec(dllexport) std::string valeurExpr(IHM* pImg) { return pImg->lireExpr(); }
Function lireExpr()
_declspec(dllexport) std::string lireExpr() const {
return card_expr_str;
}
When i execute Visual said that i try to access to a protected part of memory.
First of all you cannot have std::string nor std::wstring in dll that you want to be accessible from other languages. So these fellows have to be changed to char * or wchar_t * <--- this is the real string - array of chars.
So, how to get string from C++ to C#?
C++
void foo(char *str, int len)
{
//write here content of string
}
C#
[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(StringBuilder str, int len);
and then you have to call it somehow:
void callFoo()
{
StringBuilder sb = new StringBuilder(10); //allocate memory for string
foo(sb, sb.Capacity);
}
Notice that in C# you have to use StringBuilder to get string from c++.
If you want to pass a string in the other way - from C# to C++ is simpler:
C++
void foo(const char *str)
{
//do something with this str
}
C#
[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(string str);
and then just:
void callFoo(string str)
{
foo(str);
}
You have to remember about codepage. So if you're using unicodes, you will have to give additional attribute to DllImport: CharSet=CharSet.Unicode
Now classes. There is NO simple way to pass a class defined in C++ to C#. The simples way is to do some magic. So for every member function in C++ create non member function that will be exported to dll. Something like that:
//class in C++
class Foo
{
public:
int Bar();
};
//now you will have to define non member function to create an instance of this class:
Foo* Foo_Create()
{
return new Foo();
}
//and now you will have to create non member function that will call Bar() method from a object:
int Foo_Bar(Foo* pFoo)
{
return pFoo->Bar();
}
//in the end you will have to create a non member function to delete your object:
void Foo_Delete(Foo* pFoo)
{
delete pFoo;
}
And then you can use it in C#:
[DllImport("Foo.dll")]
public static extern IntPtr Foo_Create();
[DllImport("Foo.dll")]
public static extern int Foo_Bar(IntPtr value);
[DllImport("Foo.dll")]
public static extern void Foo_Delete(IntPtr value);
You can also use a C# class in C++, but it's a little more complicated and requires use of C++/CLI
I register a function pointer that takes a string as parameter in the C++ dll that is used to send text back to the C# application. I tried to marshal the string parameter as IntPtr and BStr in addition to others. With the debug build I have no crash. With the release build the data are returned correctly (tested with log to a txt file). However, the C# program crashes with AccessViolationException.
Here is the code:
A: using BStr
C++:
typedef void(*callbackFunction)(BSTR resultDataJson);
extern "C" DLLEXPORT int setReceiveFunction(callbackFunction funcPointer)
{
//function pointer is stored
return 0;
}
//send the data back with function pointer
void MyClass::sendCallback(const QByteArray& msg)
{
if (this->m_callbackFunctionForReceivedData != nullptr)
{
BSTR resultMsg = this->ANSItoBSTR(msg.data());
this->m_callbackFunctionForReceivedData(resultMsg);
}
}
C#:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void receiveCallbackData([MarshalAs(UnmanagedType.BStr)]string resultDataJson);
[DllImport("./myDll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static public extern int setReceiveFunction([MarshalAs(UnmanagedType.FunctionPtr)] receiveCallbackData functionCallback);
//callback function where data are received
private void functionCallback(string receivedJsonData)
{
//The received string can be logged here and looks correct.
}
B. using IntPtr
typedef void (*callbackFunction)(const char* resultDataJson);
extern "C" DLLEXPORT int setReceiveFunction(callbackFunction funcPointer)
{
//function pointer is stored
return 0;
}
//send the data back with function pointer
void MyClass::sendCallback(const QByteArray& msg)
{
if (this->m_callbackFunctionForReceivedData != nullptr)
{
this->m_callbackFunctionForReceivedData(msg.data());
}
}
C#:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void receiveCallbackData(IntPtr resultDataJson);
[DllImport("./MyDll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static public extern int setReceiveFunction([MarshalAs(UnmanagedType.FunctionPtr)] receiveCallbackData functionCallback);
private void functionCallback(IntPtr receivedJsonDataInt)
{
string receivedJsonData = Marshal.PtrToStringAnsi(receivedJsonDataInt);
//The received string can be logged here and looks correct.
}
Both work in Debug but crash in Release. Why? What is the solution to this problem? Thanks for your help!
Well I'm a bit lost in all this pinvoke stuff. Maybe someone can help me.
Got this in my header-file:
class A
{
public:
virtual B* Foo() const = 0;
};
cpp-file:
extern "C" __declspec( dllexport ) B* A_1(A* aObj)
{
if(aObj)
return aObj->Foo();
else
return 0;
}
I tried to import this in my c# file like this:
[DllImport("Wrapper.dll")]
private static extern IntPtr A_1(IntPtr aObj);
and then
public IntPtr Foo()
{
return A_1(aObj);
}
but this didn't work. Later I have to use this B-object to call a method from it.
How do we change the assembly path in DLLImport attribute inside an if conditional statement?
e.g. I want to do something like this:
string serverName = GetServerName();
if (serverName == "LIVE")
{
DLLImportString = "ABC.dll";
}
else
{
DLLImportString = "EFG.dll";
}
DllImport[DLLImportString]
You can't set attribute's value wich is calculated during runtime
You can define two methods with diff DllImports and call them in your if statement
DllImport["ABC.dll"]
public static extern void CallABCMethod();
DllImport["EFG.dll"]
public static extern void CallEFGMethod();
string serverName = GetServerName();
if (serverName == "LIVE")
{
CallABCMethod();
}
else
{
CallEFGMethod();
}
Or you can try to Load dll dynamicaly with winapi LoadLibrary
[DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
static extern int LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
static extern IntPtr GetProcAddress( int hModule,[MarshalAs(UnmanagedType.LPStr)] string lpProcName);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
static extern bool FreeLibrary(int hModule);
Create delegate that fits method in dll
delegate void CallMethod();
And then try to use something like that
int hModule = LoadLibrary(path_to_your_dll); // you can build it dynamically
if (hModule == 0) return;
IntPtr intPtr = GetProcAddress(hModule, method_name);
CallMethod action = (CallMethod)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(CallMethod));
action.Invoke();
You need to manually load the dll via LoadLibrary/GetProcAddress.
I had the same need for a small application and used c++/cli.
In c# it will look something like:
delegate int MyFunc(int arg1, [MarshalAs(UnmanagedType.LPStr)]String arg2);
public static void Main(String[] args)
{
IntPtr mydll = LoadLibrary("mydll.dll");
IntPtr procaddr = GetProcAddress(mydll, "Somfunction");
MyFunc myfunc = Marshal.GetDelegateForFunctionPointer(procaddr, typeof(MyFunc));
myfunc(1, "txt");
}
Edit: Here is complete example
May be you could differentiate your builds using conditional compilation?
If you can /define that a build is for server A, eg. compiling with /define serverA, then you could have
#if serverA
DllImport["ABC.dll"]
#else
DllImport["EFG.dll"]
#endif
More #if info