Link to home
Start Free TrialLog in
Avatar of cmrobertson
cmrobertsonFlag for United States of America

asked on

Passing C# parent Window handle to C/C++ DLL

I am trying to pass the parent window (current window) of a C# Form to a C/C++ DLL.
The C/C++ DLL needs the handle for Windows Messages. The DLL makes a call to the Windows function
RegisterDeviceNotification(parentHandle, .....)  to get Windows messages.
I am developing both the application and DLL and consequently
have the flexibility to change either one or both.
The DLL function prototype is: initConnection(CWnd *parentWnd);
In a C/C++ app I can call the DLL function initConnection(CWnd *parentWnd) using the dialog box handle.
I am not sure if it is possible within a C# application to use a "CWnd" type, however I have the flexibility to change the type of handle in the DLL to anything that may work with the C# app.
(I may have a cast it in the DLL, although that may lead to a new set of problems)
I also could attempt to get the parent window from the DLL, although past attempts to do this in the DLL when used with a C/C++ parent app were not successful. It seems like the best opportunity to use the correct parent handle is at the point where the DLL function is called.
Avatar of sarabande
sarabande
Flag of Luxembourg image

you can't pass a CWnd * to the dll but you need to pass a HWND (windows handle) instead.

the dll easily can create a CWnd * from the hwnd by

  CWnd* pWnd = CWnd::FromHandle( hwnd );

i don't know how you could get the HWND of a window in C# but as it is an ID of the os you surely have a function for it (like GetHWnd or similar).

Sara
Building on Sara's reponse then the c# side is simply

FormInstance.Handle to get the hwnd (returned as IntPtr).
Avatar of cmrobertson

ASKER

On the C/C++ DLL side the following compiles and may work.
I have not got the C# App to compile.

bool initUtilities(int parentHandle)
{

      CWnd *AppPtr;            


      AppPtr = CWnd::FromHandle((HWND)parentHandle);


      return(TRUE);

}


On the C# App Side

I did try this...


            IntPtr parentHandle;

            parentHandle = this.Handle.ToInt32;


          initUtilities((int)parentHandle);      

and this...

          int parentHandle;

            parentHandle = (int)this.Handle.ToInt32;


          initUtilities(parentHandle);      



I get the following error:

error CS0428: Cannot convert method group 'ToInt32' to non-delegate type 'System.IntPtr'. Did you intend to invoke the method?



I don't what "FormInstance.Handle" is.
I tried a google search with a few variations and did not come up with anything.
I also searched the VS2008 directories and did not find anything.


Thanks for your help thus far.
I think I am making progress.
Any further suggestions?
Thank you

 

Okay, by forminstance, all i meant was "whateveryourformiscalled".Handle

IntPtr parentHandle;
parentHandle = this.Handle;  //no ToInt32

--should work if you're expecting a void* or int* on the c++ side


int parentHandle;
parentHandle = (int)this.Handle.ToInt32();  //it's a method, needs parenthesis

--shoudl work if you're expecting an int


The IntPtr can be used to transfer the handle. it isn't a pointer to int as name may imply. msdn says:

The IntPtr type is designed to be an integer whose size is platform-specific. That is, an instance of this type is expected to be 32-bits on 32-bit hardware and operating systems, and 64-bits on 64-bit hardware and operating systems.

The IntPtr type can be used by languages that support pointers, and as a common means of referring to data between languages that do and do not support pointers.

IntPtr objects can also be used to hold handles. For example, instances of IntPtr are used extensively in the System.IO..::.FileStream class to hold file handles.

The IntPtr type is CLS-compliant, while the UIntPtr type is not. Only the IntPtr type is used in the common language runtime. The UIntPtr type is provided mostly to maintain architectural symmetry with the IntPtr type.

so, the code on the c++ side is correct beside that AppPtr should not be a local variable (and actually is not an 'App' but a dialog or form).

Sara
This DLL interfaces to a USB HID/MS device. I have been using this DLL and device for several years in a few different C/C++  applications. The USB DLL basically uses the MS HID driver provided with the Windows DDK.
 

At this point the C# application complies and runs without crashing.
But I can't seem to get a simple message through to the device using the C# App
(A simple C/C++ app works as expected)


Here are some more details...

C/C++ DLL side.... (C# App init call uses this function)

bool initUSBUtilities(int parentHandle)
{
      
bool status = FALSE;


      if(parentHandle != NULL)
      {

            AppPtr = CWnd::FromHandle((HWND)parentHandle);

            if(AppPtr != NULL)
            {
              status = TRUE;
            }
      }


      return(status);

}

Where CWnd *AppPtr is declared ourside the scope of init function
(it always was, the last code snippet showed it local only for simplicity)

The function initUSBUtilities() checks for non-NULL values. That may not mean they are valid.

On the C# App side....
private void connectUSBDevice_Click(object sender, EventArgs e)
        {
            bool status;  
           
            int parentHandle;

            parentHandle = this.Handle.ToInt32();

            status = initUSBUtilities((int)parentHandle);
            if (status == true)
            {
                connectResponse.Text = "Init OK";  
            }
            else
            {
                connectResponse.Text = "Init Fail";
            }

                       
        }

initUSBUtilities() always returns "true"

After calling initUSBUtilities() , a call is made to open the USB device, followed by a function that sends a short text message.  The message does not get through to the device using the C# App.

Using a C/C++ application, and the same "open device" and "send message" calls , the message gets to the USB device.
The only difference between the C/C++ App and the C# App is the initialization function.

Here is the C/C++ init function (less checking, more success)

bool initUSBUtilitiesCPP(CWnd *parentWindow)
{
      

      AppPtr = parentWindow;


      return(TRUE);

}



Thanks for your help.
I am new to C#. Sorry if I took a response too literally.
C# has a few features that I think will be useful in my next App, particulry the XML capabilities.
(it also seems simpler to get up and running, especially for someone that generally does embedded development.)


Any suggestions?
Is there more testing I can do to validate the handle beyond testing for a NULL in the C# DLL init call?

Thank you
 
Note that an int is always 32-bit (in C#, at least, I'm not particularly familiar with C++), whereas an IntPtr or void* is going to be 32- or 64-bits depending on the platform, so it's probably wiser to define your C++ DLL as: bool initUSBUtilities(HANDLE parentHandle), declare it in your C# code as: [DllImport(...)] bool initUSBUtilities(IntPtr parentHandle), and call it from C# with initUSBUtilities(this.Handle).

If you were running the code you had under 64-bit then casting the this.Handle in C# to an int would be a narrowing conversion, i.e. you might be making the handle invalid.

Of course that's all a moot point if your development and production platforms are all always 32-bit.
Very good point regarding 32 vs. 64 bit pointers. I implemented it as you suggested. It didn't fix this problem as discussed above, however I think it is probably better in the long run to use your approach.
Also I did not know that you could have different function (method) prototype (signatures) for the same function in C# (vs. the source C/C+ DLL).
I also tried the C/C++ function listed below for the initialization function.
This works with the C/C++ App using either GetActiveWindow() or GetForegroundWindow()

It does not work in either case with C# App.

bool initUSBUtilitiesNP(void)
{
      
bool status = FALSE;
HWND parentHandle;


      // parentHandle = GetActiveWindow();

      parentHandle = GetForegroundWindow();

      if(parentHandle != NULL)
      {

            AppPtr = CWnd::FromHandle(parentHandle);

            if(AppPtr != NULL)
            {
              status = TRUE;
            }
      }

      

      return(status);

}


More info that may help.

Any Suggestions?

Thank you
You recall that I mentioned I'm not particulary good with C++, so try not to laugh at my example. ;)

This silly test DLL works for me as expected, so I know at the least I'm able to get a valid CWnd.
extern "C" __declspec(dllexport) bool WINAPI TestFunction(HANDLE parent)
{
	CWnd* parentWindow = CWnd::FromHandle((HWND)parent);
	
	CString windowText;
	parentWindow->GetWindowTextW(windowText);
	AfxMessageBox(windowText);
	return true;
}

Open in new window


And I tested with this C# code:
public partial class Form1 : Form
{
	[DllImport("TestDll.dll")]
	private static extern bool TestFunction(IntPtr parent);

	public Form1()
	{
		InitializeComponent();
	}

	private void button1_Click(object sender, EventArgs e)
	{
		this.Text = "Hello World!";
		TestFunction(this.Handle);
	}
}

Open in new window

cmRobertson, the code you posted worked as expected. that's why the status return is true. after calling initUSBUtilities(int parentHandle) the global AppPtr (still a poor name for a window)  in the dll would contain a valid CWnd pointer where the window is that from the C# application. tgerbert has showed with his code that he can use that window to retrieve the window text.

you said 'After calling initUSBUtilities() , a call is made to open the USB device, followed by a function that sends a short text message' but didn't show that code. but that is crucial to decide whether the first initUSBUtilities(hwnd) has worked.

where are these calls made? from c# app? or in the c++ dll? how did you send the message? how did you define the AppPtr? was it available from each function in the dll?

did you try code like

if (AppPtr != NULL)
   AppPtr->PostMessage(WM_SETTEXT, 0, (LPARAM)_T("yes it worked"));

Open in new window


what should change the title of your form in the c# app.

note, in the dll you can't use functions like GetForegroundWindow cause the dll hasn't a proper mfc environment and knows nothing from the app beside of the AppPtr window. you should only call PostMessage so that the window must not immediately respond.

Sara
Is MFC necessary for GetForegroundWindow()? I was under the [perhaps mistaken] impression it was just a core Windows function (it's in Winuser.h/User32.dll, neither of which suggests anything MFC-specific)? Even MSDN examples include the use of GetForegroundWindow() from .Net (for which I presume there is zero support for MFC)...
::GetForegroundWindow is native winapi and would return an active top level window if attached to the current process. there is also CWnd::GetForegroundWindow which is mfc only and which is a wrapper function finally calling the native winapi function and returns a CWnd pointer. for both, the top level window must belong to the current process so that the active message loop can be used to retrieve the result. if there is no UI context the call will fail. that happens in my opinion when a c# application calls a c++ dll (with or without mfc). the c# application was not really an own executable but was driven by the .NET framework. that is a (the) main difference to the c++ app where a dll called would run in the context of the application and therefore  functions could be called back from the message queue currently run by the app (note, even in c++ you can have pure worker threads running in a dll or you can have pure ui threads which run only in the dll or you can have pseudo windows to run an own message queue in a thread).

so i am pretty sure the GetForegroundWindow called in initUSBUtility would return NULL and the only way for the dll to communicate with the app (beside of the return value)  was to post (or send) messages to the parent window handle. sending messages by calling SendMessage is dangerous cause the function SendMessage waits for message processing. so when the call into the dll was done asynchronously by the .NET you could break the active message processing and hang.

Sara

Thanks for the info - and sorry to drift off topic.

Anyway, it's occuring to me that I believe the default calling convention in C++ is __cdecl whereas the DllImport attribute in C# assumes __stdcall. If mis-matched calling conventions were the case I'd expect the C# application to throw a PInvokeStackImbalance exeption, so this may not be the case here, but figure it's worth mentioning...you can modify your C# code to explicitly specify the __cdecl convention:
[DllImport("YourLib.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern bool initUSBUtilities(IntPtr hParent);

Open in new window



>>initUSBUtilities() always returns "true" After calling initUSBUtilities() , a call is made to open the USB device, followed by a function that sends a short text message.  The message does not get through to the device using the C# App
If initUSBUtilities is returning true, but one or both of the other calls that follow it seem to not be working, I'm not sure why you'd assume initUSBUtilities is the problem and not one the functions that don't work. ;)

I'd wager initUSBUtilities() is working correctly, but perhaps your C# isn't properly passing arguments to one of the other functions. So what are the two other function calls you referred to - what do their prototypes look like, and how are they declared & used in your C# application?
You may also need to explicitly state whether your C++ functions expect ANSI or Unicode in your C# DllImport attributes (which could easily screw up sending text to your device).

e.g.:
[DllImport("YourLib.dll", CallingConention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern void SendMessageToDev([MarshalAs(UnmanagedType.LPWStr)] string message);

Open in new window

the initUSBUtilities has only one argument. so here it should be same whether the arguments are put from left to right or right to left to the stack. but for the other functions (???)  it can be a very useful information that you can control the __cdecl and __stdcall specifiers from c#.

what i would like to know is whether the pApp pointer was used after the call to initUSBUtilities and whether that global pointer was needed at all. i would think that it could be simpler to pass the window handle with all calls which might need to send messages back to the application window.

Sara

__cdecl and __stdcall both pass arguments from right to left, the difference being that __cdecl requires the caller to clean-up the stack, whereas __stdcall requires the callee to clean-up the stack. I assume that if arguments aren't popped back off the stack that could eventually lead to unexpected behavior or memory access violations in code following the bad P/Invoke call.

Given that the asker's DLL works correctly when consumed from a C++ application, and fails only when functions are invoked from a C# application, it's probably safe to say the more likely culprit is the manner in which the Common Language Runtime is interacting with the native library (e.g. use of the DllImport attribute) and not a flaw in the DLL's code.
Thanks for your feedback. I’ll try to catch-up with your responses here.

This DLL is used as interface to connect USB HID devices. This uses the Windows HID interface available in the Windows DDK (version 7600.16385.1).  DLL was written about 2004 based on the limited information available at the time from an article in EDN (see attached). Microsoft has since came out with a USB library (which I cannot find right now) that essentially uses the same interface to the DDK. There are also other samples available that use the same sequence of function calls as outlined in the EDN article to identify and connect to a specific USB HID device. I could provide the source code to the DLL, but I think that may be more confusing than useful.  The process of “opening” a device is as outlined in the EDN article. Messages are sent/received to the USB device HID control-endpoints using USB “Set/Get Report” provided by the Windows HID interface in the DDK (HidD_SetOutputReport() and HidD_GetInputReport()).  


If the initialization function was not working properly, it is highly unlikely the “open device” and “Set Report” functions did not work.  The initialization function may be working correctly, and the open and/or set report function may not be working. I have to dig deeper into the open function and set report functions to see if they are failing somewhere in the process.  One problem I have is that when debugging from the application, I cannot step into the DLL.  Conversely, when starting the debugger from the DLL, I can get the application to start, but the breakpoints in the DLL are disabled.  Any suggestions?

A useful tool for debugging USB device applications it is very helpful to look at the USB bus traffic, our USB bus analyzer was borrowed by another group, I expect it back by the middle of the week (there are free USB software tool,  I don’t find that any of them to be nearly as useful as a bus analyzer).   The device does appear in the Device Manager window (there will be some traffic).  In particular, this will be useful for looking at the Set Report traffic (assuming there is some)

The suggestions that I have received have been helpful.
tgerbert: I used the test code you created. It worked as expected, indicating that the DLL got a valid CWnd and parent handle. I also added the “CallingConvention.Cdecl” and “CharSet = CharSet.Unicode”,
Neither fixed the problem, although both are probably required.  The DLL was built not to use UNICODE.  
(the multi-byte option is selected in the C/C++ DLL)  

In the DLL App pointer (not its real name) is used as part of the process of opening the device. A call is made to the Windows function "RegisterDeviceNotification(parentHandle, .....)". The notifications are used to detect USB device connection and removal. This is the only explicit use the parent Window handle that I can find in the DLL.

tgerbert - “the more likely culprit is the manner in which the Common Language Runtime is interacting with the native library”. If this is the case, is there a way to identify the culprit?

Thank you for all the suggestions. Let me dig a little deeper (although if you have other suggestions, feel free to post them.)  If I have break through or further questions , I’ll post them as quickly as possible.

Here is link to the EDN article.
http://www.edn.com/article/481901-Using_the_HID_class_eases_the_job_of_writing_USB_device_drivers.php

Thank you


EDN-WindowUSB.pdf
Well, your initUSBUtilities() function (at least as defined above) is so simple and straight-forward it's almost not possible for it to not work and yet return true. That plus you mentioned that it everything does work when used from a C++ application, leads me to believe the problem lies in the C# code. So let's assume for the moment that all of your C++ is flawless.

I don't particularly want to get into a lengthy discussion of calling conventions, argument ordering and stack responsibility - both because I don't want to waste time re-hashing concepts you're probably already familiar with, and because I barely understand it myself - so suffice it to say that a mis-matched calling convention could possibly cause screwiness in such a way that your first calls into the DLL from C# may succeed, and later calls into the same DLL to fail.  And given the aforementioned simplicity of initUSBUtilities() I would tend to look more closely at the other two calls you referred to.

Keep in mind that a call into a DLL from a .Net application doesn't quite work the same way it does when you simply link to a library in C++ - the .Net Common Language Runtime is going to do some marshalling of data for you. Imagine you have a DLL function that takes a char* or LPCSTR.  So long as you include CharSet.Ansi in your DllImport and/or apply the MarshasAs(LPCSTR) attribute to the parameter itself, C# will let you pass a simple string type - and even though strings in C# are always Unicode, the Runtime will marshal that into an array of single-byte chars, make the function call for you passing a char*, and marshal the return value back into a Unicode C# string for you. The runtime has no way to know what types of data the DLL function is expecting, you have to explicitly tell C# what to pass. Consider you omit the CharSet.Ansi and pass a C# string to a DLL expecting a char* - your DLL is probably going to see that argument as a 1 character string (since on a little-endian system the second byte in the string's data will be \0 if the first character only requires 1 byte to represent it's code point).

So, if your send message routine's prototype looks like:
int SendMessageToUsbDev(char* lpszMessage, long nMessageLen);

Open in new window


And your C# looks like:
[DllImport("TheLib.dll", CharSet = CharSet.Unicode)]
private static extern int SendMessageToUsbDev(string lpszMessage, long nMessageLen;

Open in new window

Then you're going to have problems:
1) The C++ code doesn't specify a calling convention, thus the default of __cdecl will be used. The DllImport attribute also doesn't specify a calling convention, and it will default to CallingConvention.StdCall.

2) CharSet.Unicode is specified, so lpszMessage will be passed as a wchar_t* but the DLL is expecting a char*.

3) In Microsoft C++ compilers a long is 4 bytes, but in C# a long is 8 bytes.

The correct C# declaration for that C++ function would be:
[DllImport("TheLib.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl]
private static extern int SendMessageToUsbDev(string lpszMessage, int nMessageLen;

Open in new window


So, if you can tell me what the exact function prototypes are in C++, and what you've got for declarations in C#, I may be able to spot the error without seeing any other code (or maybe not, but it's worth a shot).
Here are the C/C++ prototypes and the DllImport declarations.
 
For debugging purposes the send message function ( USBSetReport() ) was changed such that
the DLL sent a fixed message. Here is the test send message function prototype:  void USBSetReport(void);
This suggests that the problem is likely with the "USB_OpenDevice()" function, this is the function
that eventually uses the parent handle sent in the initialization function.
The USBGetReport() is currently unused in the C# application.


C/C++ function prototypes:

extern "C" __declspec(dllexport)bool initUSBUtilities(HANDLE parentHandle);

extern "C" __declspec(dllexport)bool USB_OpenDevice( short vid,  short pid);


extern "C" __declspec(dllexport)void USBSetReport( char messageID,  char reportType, char *message);

extern "C" __declspec(dllexport)unsigned char USBGetReport(  short inputReportSize, char *reportBufPtr);



C# DllImport()

[DllImport("USB_MFC_Ext_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern bool initUSBUtilities(IntPtr parentHandle);

[DllImport("USB_MFC_Ext_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern bool USB_OpenDevice(short vid, short pid);

[DllImport("USB_MFC_Ext_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
unsafe public static extern void USBSetReport(char messageID, char reportType, char *message);
       
[DllImport("USB_MFC_Ext_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
unsafe public static extern char USBGetReport(short inputReportSize, char *reportBufPtr);



Thanks for your help.
SOLUTION
Avatar of sarabande
sarabande
Flag of Luxembourg image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
A char in C++ is 1 byte, but in C# it's 2 bytes.  Also, don't ever use the "unsafe" keyword - I have yet to ever see an actual need for it. There's a reason they gave that keyword the name that it has. ;)
[DllImport("USB_MFC_Ext_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void USBSetReport(byte messageID, byte reportType, string message);

[DllImport("USB_MFC_Ext_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern char USBGetReport(short inputReportSize, string reportBufPtr);

Open in new window


The .Net runtime will marshal a string to a char* for you.
...you can also use a StringBuilder where the external function expects a char* (really a C# string is good for const char *, and a StringBuilder good for char*).

You can use StringBuilder.EnsureCapacity(x) to mimic char* buffer = new char[x].
Oh, that second declaration might need to be:
[DllImport("USB_MFC_Ext_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern byte USBGetReport(short inputReportSize, string reportBufPtr);

Open in new window

ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
It Works!
The "Set Report" message is now be properly formed and getting through to the device.
I created a text box and can pass test messages to the device that the device then displays.
Thank you for your help. This is probably not a good first task for someone trying to learn C# ,
however if I could not get a simple C# application to talk to the device through my C/C+ DLL I would
have to choose another approach.
I think in the long run this effort will lead to a better solution.  
This was a very good dialog and learned many things I didn't realize I didn't know.

Both "tgerbert's" and "Sara's" feedback throughout this effort was helpful. I made several improvements based on their comments.

Thank you
So, to summarize for those who may be reading this in the future, the total solution was to use CWnd::FromHandle() in C++, passing it an IntPtr from C# using this.Handle (where "this" is something derived from Control, e.g. a Form) and to make sure the character encoding, calling convention and data types in the C# declarations correctly matched their corresponding C++ prototypes.

Glad you got it working - going back and forth between a .Net language and a native C++ DLL can sometimes be a bit of a pain in the neck.