|
Hello Nishant!
I'm a novice programmer and I'm trying to use a C# class in a MFC project. First I did this via an assembly generated from my C# library class, and now I'm wondering if there exist a way to do this without using a library or DLL reference, calling the code from the C# class and using it on my MFC classes. Is that posible? If so I'd like to know what's the technique or technology to use for it.
Thanks in advance.
Jorge Andres
|
|
|
|
|
Hi Nishant
Nice trick. I am not able to understand one thing here. The CEnumWindows::GetClass() is a static method which returns the static member m_pclass. And m_pclass is assigned this on every creation of CEnumWindows. So m_pclass will hold the last created instance. Later, _CEnumWindows::EnumWindowsProc uses that instance to invoke the EnumProc delegate. Even though the code is for demonstration purpose, I guess there is some confusion here. Does the code mena the right thing ? Or am I missing something ? Please explain.
Regards
Vivek Ragunathan
|
|
|
|
|
Hi there!
You are complicating yourself too much, aren't you? why don't you use a mixed mode dll, declare the __gc class and the __nogc (templated) class inside, declare a pointer to the __nogc class inside the __gc one and then pass a pointer to a __gc function as a delegate? this way, you receive in the __nogc class only a function as its delegate and not the whole class!! it's only an idea, i have to try, but it appears to be a working solution, i will tell you!
regards,
villalvilla
|
|
|
|
|
I am sorry that I just began code in .NET
But is there anything wrong with DLLImport?
Like, performance penalty or anything?
Hei, I don't know!
Thanks for the answer.
|
|
|
|
|
For C#, DllImport is the only way to invoke API.
But for C++/CLI, it provides an easy way to invoke API function directly.
|
|
|
|
|
Following on from my previous post (see earlier thread) where I was attempting to use managed events in preference to the delegate / callback mechanism in the article. I wanted to be able to hold a pointer in the unmanaged inner class to the managed outer class in order to invoke the callbacks. The article has CEnumWindows as a singleton and uses a static member GetClass() to return a pointer to the outer object for the same purpose.
The stumbling block is that the compiler won't allow unmanaged classes to contain attributes that are pointers to managed classes.
The way around that is actually fairly simple (although not all that obvious). What is required is a helper template class called gcroot:
gcroot<CEnumWindows*> _pOuter
This wraps the managed pointer and allows it to be used within the unmanaged class (the pointer does not need to be pinned).
The steps to convert the sample (taking my previous example as a base) are:
Include the following to get gcroot<>...
#include <vcclr.h>
Add this attribute to _CEnumWindows...
gcroot<CEnumWindows*> _pOuter;
Initialise this attribute via the constructor for _CEnumWindows...
_CEnumWindows(gcroot<CEnumWindows*> pOuter)
{
_pOuter = pOuter;
}
Remove the GetClass() impliementation from CEnumWindows and create it in the NForm object using new.
Pass the pointer to the inner class as the state parameter of the call to EnumWindows...
::EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc, (LPARAM) this);
and cast it back to its original type in the callback...
_CEnumWindows* pMe = (_CEnumWindows*) param;
then can be used to invoke the callback via the delegate as before...
pMe->_pOuter->m_EnumProc->Invoke(hwnd, NULL);
There is no great value in doing this for this particular example except as means to demonstrate the mechanism. However I can see a use for it when wrapping unmanaged APIs where a singleton wrapper is inappropriate.
Full listing below...
#include "stdafx.h"
#include <vcclr.h>
#using <mscorlib.dll>
#using <System.dll>
#using <System.Drawing.dll>
#using <System.Windows.Forms.dll>
#include <tchar.h>
#include <windows.h>
using namespace System;
using namespace System::ComponentModel;
using namespace System::Drawing;
using namespace System::Windows::Forms;
__gc class CEnumWindows
{
private:
__nogc class _CEnumWindows
{
private:
// pointer to outer class to invoke callbacks
gcroot<CEnumWindows*> _pOuter;
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param)
{
_CEnumWindows* pMe = (_CEnumWindows*) param;
if (pMe->_pOuter!=NULL)
{
pMe->_pOuter->m_EnumProc->Invoke(hwnd, NULL);
}
return TRUE;
}
public:
// Constructor takes pointer to outer class - use gcroot<CEnumWindows*> as
// type-safe wrapper to point to managed object
_CEnumWindows(gcroot<CEnumWindows*> pOuter)
{
_pOuter = pOuter;
}
void StartFinding()
{
::EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc, (LPARAM) this);
}
};
private:
_CEnumWindows* m_ew;
void OnEnumProc(IntPtr hwnd, IntPtr lParam)
{
__raise this->EnumProc(hwnd, lParam);
}
public:
__delegate void EnumProcDelegate(IntPtr hwnd, IntPtr lParam);
__event bool EnumProc(IntPtr hwnd, IntPtr lParam);
CEnumWindows()
{
m_ew = new _CEnumWindows(this);
this->m_EnumProc = new CEnumWindows::EnumProcDelegate(this, &CEnumWindows::OnEnumProc);
}
~CEnumWindows()
{
delete m_ew;
}
void StartFinding()
{
m_ew->StartFinding();
}
EnumProcDelegate* m_EnumProc;
};
public __gc class NForm : public Form
{
public:
bool EWHandler(IntPtr hwnd, IntPtr lParam)
{
char buff[512];
if(GetWindowText((HWND)hwnd.ToInt32(),buff,511))
{
String* s = String::Format("{0}{1}",
__box(hwnd.ToInt32())->ToString("X8")->PadRight(30),
Convert::ToString(buff));
lbox->Items->Add(s);
}
return false;
}
NForm()
{
StartPosition = FormStartPosition::CenterScreen;
Text = "Callbacks with IJW - Nish for CodeProject - Avoid DllImport";
Size = Drawing::Size(750,550);
FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog;
MaximizeBox = false;
lbox = new ListBox();
lbox->Location=Point(5,5);
lbox->Size=Drawing::Size(730,500);
lbox->Sorted = true;
Controls->Add(lbox);
CEnumWindows* p = new CEnumWindows();
__hook(&CEnumWindows::EnumProc, p, &NForm::EWHandler);
p->StartFinding();
}
ListBox* lbox;
};
int __stdcall WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
Application::Run(new NForm());
return 0;
}
|
|
|
|
|
I found this article when I was looking to design a IJW wrapper for a legacy C++ API. In particular I wanted to understand how I could include events within my wrapper that could be easily consumed by the APIs client (a C# application).
I have tried to update this example to replace the delegate / callback mechanism with an event.
Initially I created the event in CEnumWindows as follows:
__event bool EnumProc(IntPtr hwnd, IntPtr lParam);
I then replaced the class to instatiate the delegate in NForm with a __hook call as follows:
//p->m_EnumProc = new CEnumWindows::EnumProc(this,&NForm::EWHandler);
__hook(&CEnumWindows::EnumProc, p, &NForm::EWHandler);
I then replaced the call to invoke the delegate pointer:
pew->m_EnumProc->Invoke(hwnd, NULL);
with call to raise the event:
__raise pew->EnumProc(hwnd, NULL);
This proved to be a false start. The sample compiled OK but failed to run. The call to EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc,NULL); threw the exception System.MethodAccessException. I guess because the framework does not like managed events to be raised within an unmanaged call.
So...
I then modified the sample again to put the delegated callback back in. This time it is contained in the class CEnumWindows using the procedure:
void OnEnumProc(IntPtr hwnd, IntPtr lParam)
{
__raise this->EnumProc(hwnd, lParam);
}
This procedure raises the managed event when called and all is well. It is unfortunate that this extra level of indirection is required but is not too onerous. If anybody can see a simpler way of doing this then I'd be glad of the advice.
Full listing below...
#include "stdafx.h"
#using <mscorlib.dll>
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>
#include <tchar.h>
#include <windows.h>
using namespace System;
using namespace System::ComponentModel;
using namespace System::Drawing;
using namespace System::Windows::Forms;
__gc class CEnumWindows
{
private:
__nogc class _CEnumWindows
{
private:
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM)
{
CEnumWindows* pew = CEnumWindows::GetClass();
pew->m_EnumProc->Invoke(hwnd, NULL);
return TRUE;
}
public:
void StartFinding()
{
::EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc,NULL);
}
};
private:
_CEnumWindows* m_ew;
void OnEnumProc(IntPtr hwnd, IntPtr lParam)
{
__raise this->EnumProc(hwnd, lParam);
}
public:
__delegate void EnumProcDelegate(IntPtr hwnd, IntPtr lParam);
__event bool EnumProc(IntPtr hwnd, IntPtr lParam);
static CEnumWindows* GetClass()
{
if (m_pclass==NULL)
{
m_pclass = new CEnumWindows();
}
return m_pclass;
}
static CEnumWindows* m_pclass = NULL;
CEnumWindows()
{
m_ew = new _CEnumWindows();
this->m_EnumProc = new CEnumWindows::EnumProcDelegate(this, &CEnumWindows::OnEnumProc);
}
~CEnumWindows()
{
delete m_ew;
}
void StartFinding()
{
m_ew->StartFinding();
}
EnumProcDelegate* m_EnumProc;
};
public __gc class NForm : public Form
{
public:
bool EWHandler(IntPtr hwnd, IntPtr lParam)
{
char buff[512];
if(GetWindowText((HWND)hwnd.ToInt32(),buff,511))
{
String* s = String::Format("{0}{1}",
__box(hwnd.ToInt32())->ToString("X8")->PadRight(30),
Convert::ToString(buff));
lbox->Items->Add(s);
}
return false;
}
NForm()
{
StartPosition = FormStartPosition::CenterScreen;
Text = "Callbacks with IJW - Nish for CodeProject - Avoid DllImport";
Size = Drawing::Size(750,550);
FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog;
MaximizeBox = false;
lbox = new ListBox();
lbox->Location=Point(5,5);
lbox->Size=Drawing::Size(730,500);
lbox->Sorted = true;
Controls->Add(lbox);
CEnumWindows* p = CEnumWindows::GetClass();
__hook(&CEnumWindows::EnumProc, p, &NForm::EWHandler);
p->StartFinding();
}
ListBox* lbox;
};
int __stdcall WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
Application::Run(new NForm());
return 0;
}
|
|
|
|
|
Hi, I am having some linkage issues. Here is the code, very similar to the example:
__nogc class _EthernetIPCommunication
{
private:
// the callback method that will be invoked from the
// ethernetIP dll
static BOOL CALLBACK EthernetIPCallback( INT32 nEvent, INT32 nParam)
{
EthernetIPCommunication* p_EthernetIPCommunication = EthernetIPCommunication::GetClass();
p_EthernetIPCommunication->EthernetIPManagedCallback(nEvent,nParam);
return TRUE;
}
public:
// the method that will register the callback
void RegisterCallback()
{
// this method will register the internal static callback method with the eml library so that
// there is a non-managed way to get at and handle callbacks
LogEventCallbackType* callback = (LogEventCallbackType*)&_EthernetIPCommunication::EthernetIPCallback;
EtIPRegisterEventCallBack( callback );
}
};
And then i construct an instance as follows:
_EthernetIPCommunication* eip = new _EthernetIPCommunication;
eip->RegisterCallback();
Then i get this link error:
LNK2001: unresolved external symbol "void * __cdecl operator new(unsigned int)" (??2@$$FYAPAXI@Z)
Any ideas?
Mark
|
|
|
|
|
Fixed it:
See ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vcmex/html/vcconConvertingManagedExtensionsForCProjectsFromPureIntermediateLanguageToMixedMode.htm
|
|
|
|
|
|
External names are mangled. Probably your program is in C++, not in C.
If your program is in C, you should change settings in ide to tell the
compiler about that, or use the extension '.c' rather than '.cpp'.
If your program is in C++, you should use the following construction:
extern "C" {
#include "glpk.h"
}
because all glpk headers are written in C, not in C++.
|
|
|
|
|
hi Nish,
thanks in advance for your cool articles.
assume i want to use some native function like:
WSAASyncGetServByName(hwnd,msg,"",buf,len);
my interface windows are all managed .net .
but this function require me to pass a pointer to
windows by hwnd .
what is the solution here .
|
|
|
|
|
Please, clarify for me:
What exactly you mean and what is the value of this statement:
That's when this idea hit me out of the blue. Inner classes. We could use inner classes, see! All we had to do was to have an __gc class with an inner __nogc class...
Does it have to be inner class? For example small change and your example still works:
__nogc class _CEnumWindows<br />
{<br />
private:<br />
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM);<br />
public: <br />
void StartFinding()<br />
{<br />
EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc,NULL);<br />
}<br />
};<br />
<br />
__gc class CEnumWindows<br />
{<br />
private:<br />
private: <br />
_CEnumWindows* m_ew;<br />
public:<br />
__delegate bool EnumProc(IntPtr hwnd, IntPtr lParam);<br />
static CEnumWindows* GetClass()<br />
{ <br />
return m_pclass; <br />
}<br />
static CEnumWindows* m_pclass=NULL;<br />
CEnumWindows()<br />
{<br />
m_pclass = this;<br />
m_ew = new _CEnumWindows();<br />
}<br />
~CEnumWindows()<br />
{<br />
delete m_ew;<br />
}<br />
void StartFinding()<br />
{ <br />
m_ew->StartFinding(); <br />
}<br />
EnumProc* m_EnumProc;<br />
};<br />
<br />
BOOL _CEnumWindows::EnumWindowsProc(HWND hwnd, LPARAM)<br />
{ <br />
CEnumWindows* pew = CEnumWindows::GetClass();<br />
pew->m_EnumProc->Invoke(hwnd, NULL);<br />
return TRUE;<br />
}
As you can see _CEnumWindows is not inner class anymore.
So, what is the value of your experiment?...
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
Terrific article !! Thank you very much !! I've been tearing my hair out trying to figure out how to do this without DllImport.
Now, could you explain how to invoke the delegate asynchronously (with BeginInvoke etc.) using this pattern ? I have Ansi C++ libraries which constitute the "Worker Threads" of my application, and I want to be able to callback from them asynchronously to my C# GUI controls.
Using your pattern, I have finally been able to get a synchronous callback working. What do I need to do to make it work asynchronously ?
Thanks !!
|
|
|
|
|
Hi Yoni,
I am wondering if you found a solution how to make asynchronous callbacks from Unmanaged C++ libraries to C# GUI controls. I am researching that for a awhile, and still could not find the right way of doing that.
Thanks,
Eugene
|
|
|
|
|
Eugene -
I was able to get the method described in Nishant's article to to work with asynchronous delegates too, using BeginInvoke() and EndInvoke() etc.
However, in the final analysis, I decided that this method is too "tricky", and I ditched the whole idea altogether. Instead, my unmanaged C++ code just sends regular Windows messages to the C# GUI Controls, which processes them using WndProc().
Good luck !!
Yoni
|
|
|
|
|
Right way is to use Marshal.GetUnmanagedThunkForManagedMethodPtr.
[EDIT] Found the right way to use from Commandprompt explorerbar article
|
|
|
|
|
Hi,
can you give some more details about this? Thanks.
|
|
|
|
|
Nice work!
However, we don't have to use delegates under all circumstances, just because they are there
Another (and much more generic) solution is to go the pure C++ way and redirect the callback via adapter objects into the member function. This technique is shown in my article Use member functions for C-style callbacks and threads - a general solution.
It might also be a good idea to use templates to generalize your technique, so it is not necessary to write the same code again and again for EnumFonts(), EnumDesktops(), ...
--
Daniel Lohmann
http://www.losoft.de
|
|
|
|
|
Hi Daniel
Daniel Lohmann wrote:
However, we don't have to use delegates under all circumstances, just because they are there
Heheh. Just wanted to do it the managed way, that's all
Daniel Lohmann wrote:
It might also be a good idea to use templates to generalize your technique, so it is not necessary to write the same code again and again for EnumFonts(), EnumDesktops(),
Thanks, that's a very good idea
Regards,
Nish
Author of the romantic comedy
Summer Love and Some more Cricket [New Win]
Review by Shog9
Click here for review[NW]
|
|
|
|
|
In this case the adapter should be an unmanaged class ( __nogc ) while a member function to call is a in managed class ( __gc ). But you cannot declare a pointer to managed class A in unmanaged class, thus the [ A* m_pObj ] in adaptor will not work if A is a managed class. Casting to managed class pointer is not working either. So, some other technique is needed for managed-unmanged callbacks to get the pointer to managed class instance. The one is shown in this article is to use static member that would hold the pointer to one [last created] instance.
|
|
|
|
|
I thought only underscores where ugly in MC++
Maybe you can write another article or update this one to be thread safe.
Keep it up.
regards
Kannan
|
|
|
|
|
Kannan Kalyanaraman wrote:
Maybe you can write another article or update this one to be thread safe.
Thanks Kanna. Yeah, perhaps the next update should show how to make this thread safe and perhaps more generic in nature.
Nish
Author of the romantic comedy
Summer Love and Some more Cricket [New Win]
Review by Shog9
Click here for review[NW]
|
|
|
|
|
Only one thread can call EnumWindows in your case. DllImport requires less lines of code and is also thread safe.
You should use TLS or Context class to store m_pClass variable.
Step back, rub your eyes, take a deep breath, stretch a bit, and reflect on the relative importance of CP, CG, the age / travel time sustained by supposedly 'fresh' cheese curds, and Life in General. - Shog<sup<9< sup="">
|
|
|
|
|
Hello Rama
The class I have shown is *not* intended to be used directly. I simply wanted to demonstrate my way of using native API calls that required callbacks from MC++ without the use of DllImport attribute.
For actual use of the class, you'd have to write more code to make it thread safe and more OOPish and implement other safety checks etc...
That's why I have subjected it as "Implementing Callback functions using IJW" instead of as "CEnumWindows - an MC++ class that etc..."
I hope you got my point?
Warm regards,
Nish
Author of the romantic comedy
Summer Love and Some more Cricket [New Win]
Review by Shog9
Click here for review[NW]
|
|
|
|
|