From a c# program I want to use WM_COPYDATA with SendMessage to communicate with a legacy c++/cli MFC application.
I want to pass a managed struct containing string objects.
I can find the handle to the c++ application for use with SendMessage fine.
The bit I don't know about is how the struct and it's strings can be marshalled and read at the other end. Especially as it contains non-blittables.
Do people think this is feasible?
I'll continue to work on it, but would apprecite someone who's done this sort of thing telling me if it just isn't going to work.
Here is some demo code if it was a c++/cli program and it's not difficult to get it working.
However, I'd like this to be in a .Net class library so it can easily be re-used.
//Quick demonstation code only, not correctly styled
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
struct MessageInfo
{
int nVersion;
char szTest[ 10 ];
};
MessageInfo sMessageInfo;
sMessageInfo.nVersion = 100;
strcpy( sMessageInfo.szTest, "TEST");
COPYDATASTRUCT CDS;
CDS.dwData = 1; //just for test
CDS.cbData = sizeof( sMessageInfo );
CDS.lpData = &sMessageInfo;
//find running processes and send them a message
//can't just search for "MYAPP.exe" as will be called "MYAPP.exe *32" on a 64bit machine
array<System::Diagnostics::Process^>^allProcesses = System::Diagnostics::Process::GetProcesses();
for each (System::Diagnostics::Process^ targetProcess in allProcesses)
{
if (targetProcess->ProcessName->StartsWith("MYAPP", System::StringComparison::OrdinalIgnoreCase))
{
HWND handle = static_cast<HWND>(targetProcess->MainWindowHandle.ToPointer());
BOOL bReturnValue = SendMessage( handle, WM_COPYDATA, (WPARAM)0, (LPARAM)&CDS ) == TRUE;
}
}
return 0;
}
I have it working.
A simple approach is to serialize the struct to a single string and transfer a string.
The swhistlesoft blog was helpful http://www.swhistlesoft.com/blog/2011/11/19/1636-wm_copydata-with-net-and-c
This may be enough to provide the simple messaging.
The struct can be re-constructed at the other end if necessary.
If a struct with any number of strings is to be marshalled as-is then it must be a fixed size, that's the main thing I wasn't getting.
The
MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)
basically sets the size to match the c++ size which in our case is a TCHAR szTest[ 9 ];
In order to transfer a .Net struct via WM_COPYDATA from c# to c++(/cli) I had to do as follows:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern bool SetForegroundWindow(IntPtr hWnd);
public static uint WM_COPYDATA = 74;
//from swhistlesoft
public static IntPtr IntPtrAlloc<T>(T param)
{
IntPtr retval = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(param));
System.Runtime.InteropServices.Marshal.StructureToPtr(param, retval, false);
return (retval);
}
//from swhistlesoft
public static void IntPtrFree(IntPtr preAllocated)
{
if (IntPtr.Zero == preAllocated) throw (new Exception("Go Home"));
System.Runtime.InteropServices.Marshal.FreeHGlobal(preAllocated);
preAllocated = IntPtr.Zero;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public uint dwData;
public int cbData;
public IntPtr lpData;
}
/// <summary>
/// Dot net version of AppInfo structure. Any changes to the structure needs reflecting here.
/// struct must be a fixed size for marshalling to work, hence the SizeConst entries
/// </summary>
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
struct AppInfoDotNet
{
public int nVersion;
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 9)]
public string test;
};
To send a string:
COPYDATASTRUCT cd = new COPYDATASTRUCT();
cd.dwData = 2;
cd.cbData = parameters.Length + 1;
cd.lpData = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(parameters);
IntPtr cdBuffer = IntPtrAlloc(cd);
messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, cdBuffer)) != 0;
To receive string in C++:
else if(pCDS->dwData == 2)
{
//copydata message
CString csMessage = (LPCTSTR)pCDS->lpData;
OutputDebugString("Copydata message received: " + csMessage);
}
To send struct:
AppInfoDotNet appInfo = new AppInfoDotNet();
appInfo.test = "a test";
COPYDATASTRUCT cds3;
cds3.dwData = 1;
cds3.cbData = System.Runtime.InteropServices.Marshal.SizeOf(appInfo);
IntPtr structPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(appInfo));
System.Runtime.InteropServices.Marshal.StructureToPtr(appInfo, structPtr, false);
cds3.lpData = structPtr;
IntPtr iPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(System.Runtime.InteropServices.Marshal.SizeOf(cds3));
System.Runtime.InteropServices.Marshal.StructureToPtr(cds3, iPtr, false);
messageReceived = ((int)SendMessage(targetProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, iPtr)) != 0;
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(iPtr);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(structPtr);
To receive struct in C++:
LRESULT CMainFrame::OnCopyData( WPARAM wParam, LPARAM lParam )
{
LRESULT lResult = FALSE;
COPYDATASTRUCT *pCDS = (COPYDATASTRUCT*)lParam;
//Matching message type for struct
if(pCDS->dwData == 1)
{
AppInfo *pAppInfo = (AppInfo*)pCDS->lpData
lResult = true;
}
Please note this is demo code and needs work in terms of styling, exception handling etc, etc...
From the documentation:
The data being passed must not contain pointers or other references to objects not accessible to the application receiving the data.
So you need to pack your string into COPYDATASTRUCT.lpData. If you have a max length for each string then you can embed it in a fixed length structure
typedef struct tagMYDATA
{
char s1[80];
char s2[120];
} MYDATA;
If you have only one variable length string you can put the string at the end and use a header followed by string data
typedef struct tagMYDATA
{
int value1;
float value2;
int stringLen;
} MYDATAHEADER;
MyCDS.cbData = sizeof(MYDATAHEADER)+(int)stringData.size();
MyCDS.lpData = new BYTE[MyCDS.cbData];
memcpy(MyCDS.lpData,&dataHeader,sizeof*(MYDATAHEADER);
StringCbCopyA (
((BYTE*)MyCDS.lpData)+sizeof*(MYDATAHEADER)
,stringData.size()
,stringData.c_str());
If you have multiple variable length strings you can still use a header and allocate more spaces for every strings plus a double null terminator, or serialize everything into one XML string.
Related
I am sending a window message from a C# application to a C++ Win32 application. I am using a message via the RegisterWindowMessage() API.
The string value transfers from C# to C++, but on the C++ side I could not convert it back to a string.
C#
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
_sendMessageID = RegisterWindowMessage("WM_MSG_TEST");
public void SendMessage()
{
IntPtr buffer = Marshal.StringToBSTR("Hello");
SendMessage((IntPtr)0xffff, (int)_sendMessageID, IntPtr.Zero, buffer);
}
C++
UINT WM_MSG_AA = RegisterWindowMessage("WM_MSG_TEST");
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_MSG_TEST)
{
BSTR* pcds = (BSTR*)lParam;
}
}
Please let me know how to fix this issue?
I also refer below link for fix issue but it could't help.
C# SendMessage to C++ WinProc
Send message to a hwnd in C#
// WindowCreationCode
BOOL ProcessNextMessage()
{
MSG msg;
GetMessage(&(msg), _hWnd, 0, 0);
TranslateMessage(&(msg));
DispatchMessage(&(msg));
return TRUE;
}
int Create(){
CoInitialize(NULL);
_hInst = GetModuleHandle(NULL);
WNDCLASS wcex = { 0 };
wcex.lpfnWndProc = WndProc;
wcex.hInstance = _hInst;
wcex.lpszClassName = c_szClassName;
if (!GetClassInfo(wcex.hInstance, wcex.lpszClassName, &wcex))
{
if (!RegisterClass(&wcex))
{
return HRESULT_FROM_WIN32(GetLastError());
}
else
{
return S_OK;
}
}
else
{
return S_OK;
}
_hWnd = CreateWindowEx(
WS_EX_TOPMOST,
c_szClassName,
"ACTXAUTODRIVER",
WS_DLGFRAME ,
1, 1, 1, 1,
NULL,
NULL, _hInst, NULL);
ShowWindow(_hWnd, SW_HIDE);
while (ProcessNextMessage())
{
}
CoUninitialize();
}
You cannot send raw memory pointers across process boundaries like you are attempting to do. Even though you are converting your C# string data to a BSTR allocated by the OS, the allocated memory is still only valid in the address space of the process that allocated it.
Your string data must be marshaled from one process's address space to the other process's address space. COM handles that for you automatically when it passes BSTR values over process boundaries. But with window messages, only certain messages are marshaled automatically by the OS, and messages created with RegisterWindowMessage() are not marshaled.
For what you are attempting, use WM_COPYDATA instead, which is marshaled. However, you should NEVER broadcast (use (IntPtr)0xffff aka HWND_BROADCAST as the target window) a WM_COPYDATA message! Bad things can happen if unsuspecting apps receive WM_COPYDATA messages they are not prepared to handle correctly.
Have your C# code find the specific window it is actually interested in for your C++ app (from FindWindow/Ex(), etc) and then send WM_COPYDATA to only that window and no others. You can use RegisterWindowMessage() to create a unique value for use in the COPYDATASTRUCT::dwData field to differentiate your use of WM_COPYDATA from other people's use of WM_COPYDATA. Make your C++ code ignore any WM_COPYDATA message whose dwData value is not recognized.
Try something more like this:
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
const int WM_COPYDATA = 0x004A;
_cdsDataID = RegisterWindowMessage("WM_MSG_TEST");
public void SendMessage() {
if (_cdsDataID == IntPtr.Zero) return;
IntPtr TargetWnd = ...; // FindWindow(), etc
if (TargetWnd == IntPtr.Zero) return;
string s = "Hello";
COPYDATASTRUCT copyData = new COPYDATASTRUCT();
copyData.lpData = Marshal.StringToHGlobalUni(s);
if (copyData.lpData != IntPtr.Zero)
{
copyData.dwData = _cdsDataID;
copyData.cbData = (s.Length + 1) * 2;
IntPtr copyDataBuff = Marshal.AllocHGlobal(Marshal.SizeOf(copyData));
if (copyDataBuff != IntPtr.Zero)
{
Marshal.StructureToPtr(copyData, copyDataBuff, false);
SendMessage(TargetWnd, WM_COPYDATA, IntPtr.Zero, copyDataBuff);
Marshal.FreeHGlobal(copyDataBuff);
}
Marshal.FreeHGlobal(copyData.lpData);
}
}
const UINT uiDataID = RegisterWindowMessage("WM_MSG_TEST");
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_COPYDATA)
{
LPCOPYDATASTRUCT pcds = (LPCOPYDATASTRUCT) lParam;
if ((pcds->dwData == WM_MSG_TEST) && (WM_MSG_TEST != 0))
{
WCHAR* pstr = (WCHAR*) pcds->lpData;
...
return 0;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
You can transfer string with your custom message with global atom - see GlobalAddAtom API. You get integer value from GlobalAddAtom (atom number) and give the string as input parameter. Then SendMessage transmits the atom as WPARAM or LPARAM. The other side decodes the atom to string with GlobalGetAtomName. Finally call GlobalDeleteAtom to free the resource.
The search term RpcMgmtEpEltInqNext on both Stackoverflow and PInvoke.net yields zero results, so this should be worth asking. I've been fiddling around on MSDN and looking through Win SDK *.h files all day, and feeling a bit out of my element.
I'm basically attempting to query the MSRPC endpoint mapper with managed code. Here is what I have so far:
[DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
public static extern int RpcBindingFromStringBinding(string StringBinding, out IntPtr Binding);
[DllImport("Rpcrt4.dll")]
public static extern int RpcBindingFree(ref IntPtr Binding);
[DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
public static extern int RpcMgmtEpEltInqBegin(IntPtr EpBinding,
int InquiryType, // 0x00000000 = RPC_C_EP_ALL_ELTS
int IfId,
int VersOption,
string ObjectUuid,
out IntPtr InquiryContext);
[DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
public static extern int RpcMgmtEpEltInqNext(ref IntPtr InquiryContext,
out int IfId,
out IntPtr Binding,
out string ObjectUuid,
out string Annotation);
public static List<int> QueryEPM(string host)
{
List<int> ports = new List<int>();
int retCode = 0; // RPC_S_OK
IntPtr bindingHandle = IntPtr.Zero;
IntPtr inquiryContext = IntPtr.Zero;
IntPtr elementBindingHandle = IntPtr.Zero;
int elementIfId = 0;
string elementUuid = string.Empty;
string elementAnnotation = string.Empty;
try
{
retCode = RpcBindingFromStringBinding("ncacn_ip_tcp:" + host, out bindingHandle);
if (retCode != 0)
throw new Exception("RpcBindingFromStringBinding: " + retCode);
retCode = RpcMgmtEpEltInqBegin(bindingHandle, 0, 0, 0, string.Empty, out inquiryContext);
if (retCode != 0)
throw new Exception("RpcMgmtEpEltInqBegin: " + retCode);
retCode = RpcMgmtEpEltInqNext (ref inquiryContext, out elementIfId, out elementBindingHandle, out elementUuid, out elementAnnotation);
if (retCode != 0)
throw new Exception("RpcMgmtEpEltInqNext: " + retCode);
}
The above code isn't complete, but it illustrates my point. The code consistently returns:
RpcMgmtEpEltIngNext: 87
87 meaning Parameter is Incorrect according to here.
So I'm basically stumped at this point, and I'm sure it's because of my extremely crappy PInvoke code.
The p/invoke declaration is wrong. It needs to be:
[DllImport("Rpcrt4.dll", CharSet = CharSet.Auto)]
public static extern int RpcMgmtEpEltInqNext(
IntPtr InquiryContext,
out RPC_IF_ID IfId,
out IntPtr Binding,
out Guid ObjectUuid,
out IntPtr Annotation
);
The first parameter must be passed by value.
You'll need to translate the RPC_IF_ID struct. It's quite a simple one.
public struct RPC_IF_ID
{
public Guid Uuid;
public ushort VersMajor;
public ushort VersMinor;
}
The two string parameters you used were just wrong. They lead to the marshaller calling CoTaskMemFree on the returned value for Annotation. You don't want that. You will need to use Marshal.PtrToStringAnsi to read the value.
Don't forget to call RpcBindingFree and RpcStringFree as per the docs.
I need to invoke a native DLL from C# code. As I am not very familiar with C/C++, I can't figure out how a structure defined in C should be declared in C# so it can be invoked. The problem is that two parameters seems to be an array of structs, which I don't know how to declare this in C# (see last code block):
c++ header file:
typedef enum
{
OK = 0,
//others
} RES
typedef struct
{
unsigned char* pData;
unsigned int length;
} Buffer;
RES SendReceive(uint32 deviceIndex
Buffer* pReq,
Buffer* pResp,
unsigned int* pReceivedLen,
unsigned int* pStatus);
c# declaration:
enum
{
OK = 0,
//others
} RES
struct Buffer
{
public uint Length;
public ??? Data; // <-- I guess it's byte[]
}
[DllImport("somemodule.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint SendReceive(
uint hsmIndex,
uint originatorId,
ushort fmNumber,
??? pReq, // <-- should this be ref Buffer[] ?
uint reserved,
??? pResp, // <-- should this be ref Buffer[] ?
ref uint pReceivedLen,
ref uint pFmStatus);
in an equivalent java client, i found that the parameter is not just one Buffer but an array of Buffers. In C# it would look like this:
var pReq = new Buffer[]
{
new Buffer { Data = new byte[] { 1, 0 }, Length = (uint)2 },
new Buffer {Data = requestStream.ToArray(), Length = (uint)requestStream.ToArray().Length },
//according to the header file, the last item must be {NULL, 0}
new Buffer { Data = null, Length = 0 }
};
var pResp = new Buffer[]
{
new Buffer { Data = new byte[0x1000], Length = 0x1000 },
//according to the header file, the last item must be {NULL, 0}
new Buffer { Data = null, Length = 0x0 }
};
This seems strange to me because the extern C method does have a pointer to a Buffer struct (Buffer*) and not a pointer to a Buffer array (Buffer[]*).
How do I need to define the Struct in C# and the parameter types of the extern method?
Any help appreciated, Thanks.
Firstly your struct has the parameters in the wrong order. And the byte array needs to be declared as IntPtr with manual marshalling:
struct Buffer
{
public IntPtr Data;
public uint Length;
}
The p/invoke should be:
[DllImport("MyNativeDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern RES SendReceive(
uint deviceIndex,
[In] Buffer[] pReq,
[In, Out] Buffer[] pResp,
out uint pReceivedLen,
out uint pStatus
);
The byte array needs to be IntPtr so that the struct is blittable. And that's needed so that the array parameters can be declared as Buffer[].
It's going to be a bit of a pain doing the marshalling of the byte arrays. You'll want to use GCHandle to pin the managed byte arrays, and call AddrOfPinnedObject() to get the address of the pinned array for each struct in your arrays of structs. It will be worth your while writing some helper functions to make that task less painful.
Your method signature in c# should be something like:
[DllImport("MyNativeDll.dll")]
public static extern RES SendReceive (uint32 deviceIndex, ref Buffer pReq, ref Buffer pResp, ref uint pReceivedLen, ref uint pStatus);
See this project, it might hel you in the future so generate native calls from .net
http://clrinterop.codeplex.com/releases/view/14120
Based on the C++ header but without testing, have a look at the following code:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace WindowsFormsApplication1
{
public class Class1
{
public struct Buffer
{
[MarshalAs(UnmanagedType.LPStr)]
public StringBuilder pData;
public uint length;
}
[DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
static extern int LoadLibrary(string lpLibFileName);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
static extern IntPtr GetProcAddress(int hModule, string lpProcName);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
static extern bool FreeLibrary(int hModule);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal delegate IntPtr SendReceive(
uint deviceIndex,
ref Buffer pReq,
ref Buffer pResp,
uint pReceivedLen,
uint pStatus);
public void ExecuteExternalDllFunction()
{
int dll = 0;
try
{
dll = LoadLibrary(#"somemodule.dll");
IntPtr address = GetProcAddress(dll, "SendReceive");
uint deviceIndex = 0;
Buffer pReq = new Buffer() { length = 0, pData = new StringBuilder() };
Buffer pResp = new Buffer() { length = 0, pData = new StringBuilder() };
uint pReceivedLen = 0;
uint pStatus = 0;
if (address != IntPtr.Zero)
{
SendReceive sendReceive = (SendReceive)Marshal.GetDelegateForFunctionPointer(address, typeof(SendReceive));
IntPtr ret = sendReceive(deviceIndex, ref pReq, ref pResp, pReceivedLen, pStatus);
}
}
catch (Exception Ex)
{
//handle exception...
}
finally
{
if (dll > 0)
{
FreeLibrary(dll);
}
}
}
}
}
I am trying to PInvoke this function (GetPackageId) from kernel32.dll:
http://msdn.microsoft.com/en-us/library/windows/desktop/hh446607(v=vs.85).aspx
I defined the structs and imports as follows:
[StructLayout(LayoutKind.Sequential)]
public struct PACKAGE_ID
{
uint reserved;
uint processorArchitecture;
PACKAGE_VERSION version;
String name;
String publisher;
String resourceId;
String publisherId;
}
[StructLayout(LayoutKind.Explicit)]
public struct PACKAGE_VERSION
{
[FieldOffset(0)] public UInt64 Version;
[FieldOffset(0)] public ushort Revision;
[FieldOffset(2)] public ushort Build;
[FieldOffset(4)] public ushort Minor;
[FieldOffset(6)] public ushort Major;
}
[DllImport("kernel32.dll", EntryPoint = "GetPackageId", SetLastError = true)]
static extern int GetPackageId(IntPtr hProcess,out uint bufferLength,out PACKAGE_ID pBuffer);
And calling it like this:
PACKAGE_ID buffer = new PACKAGE_ID();
result = GetPackageId(hProcess, out bufferLength, out buffer);
However I get a return value of 122 (ERROR_INSUFFICIENT_BUFFER). I am rather new to PInvoke and am not quite sure how to proceed from here. Do I need to initialize the strings before calling the function?
You are going to need to change the p/invoke:
[DllImport("kernel32.dll", SetLastError=true)]
static extern int GetPackageId(
IntPtr hProcess,
ref int bufferLength,
IntPtr pBuffer
);
You call it once passing 0 for the length:
int len = 0;
int retval = GetPackageId(hProcess, ref len, IntPtr.Zero);
Then you need to check that retval equals ERROR_INSUFFICIENT_BUFFER. If it does not then you have an error.
if (retval != ERROR_INSUFFICIENT_BUFFER)
throw new Win32Exception();
Otherwise you can continue.
IntPtr buffer = Marshal.AllocHGlobal(len);
retval = GetPackageId(hProcess, ref len, buffer);
Now you can check retval against ERROR_SUCCESS.
if (retval != ERROR_SUCCESS)
throw new Win32Exception();
And finally we can convert the buffer to a PACKAGE_ID.
PACKAGE_ID packageID = (PACKAGE_ID)Marshal.PtrToStructure(buffer,
typeof(PACKAGE_ID));
Put it all together and it looks like this:
int len = 0;
int retval = GetPackageId(hProcess, ref len, IntPtr.Zero);
if (retval != ERROR_INSUFFICIENT_BUFFER)
throw new Win32Exception();
IntPtr buffer = Marshal.AllocHGlobal((int)len);
try
{
retval = GetPackageId(hProcess, ref len, buffer);
if (retval != ERROR_SUCCESS)
throw new Win32Exception();
PACKAGE_ID packageID = (PACKAGE_ID)Marshal.PtrToStructure(buffer,
typeof(PACKAGE_ID));
}
finally
{
Marshal.FreeHGlobal(buffer);
}
From the comments it appears that we also need to make changes to the way the PACKAGE_ID struct is marshalled.
I suggest the following:
[StructLayout(LayoutKind.Sequential)]
public struct PACKAGE_ID
{
uint reserved;
uint processorArchitecture;
PACKAGE_VERSION version;
IntPtr name;
IntPtr publisher;
IntPtr resourceId;
IntPtr publisherId;
}
followed by calls to Marshal.PtrToStringUni to convert the IntPtr string fields into C# strings. Naturally this conversion needs to happen before the call to FreeHGlobal.
My guess is that the API actually allocates the string buffers in the space beyond the end of PACKAGE_ID. Which is why you have to ask how much memory to allocate. I don't have Windows 8 at hand to test this hypothesis.
From the docs for GetPackageId it seems you should send the size of the buffer as argument when calling, i.e. bufferLength should be initialized with the size of the passed buffer.
On return the bufferLength will tell you the size of the returned buffer.
Or did misread the docs?
I would like to programmatically check the value of, and be able to toggle num-lock. What's the simplest way to do that in C#?
The reason is that I want to verify num-lock is "ON" at program start.
Thanks
Check How to programmatically turn on the Numlock Key
using System;
using System.Runtime.InteropServices;
class SetNumlockKeyOn
{
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
internal int type;
internal short wVk;
internal short wScan;
internal int dwFlags;
internal int time;
internal IntPtr dwExtraInfo;
int dummy1;
int dummy2;
internal int type1;
internal short wVk1;
internal short wScan1;
internal int dwFlags1;
internal int time1;
internal IntPtr dwExtraInfo1;
int dummy3;
int dummy4;
}
[DllImport(“user32.dll”)]
static extern int SendInput(uint nInputs, IntPtr pInputs, int cbSize);
public static void SetNumlockOn()
{
const int mouseInpSize = 28;//Hardcoded size of the MOUSEINPUT tag !!!
INPUT input = new INPUT();
input.type = 0x01; //INPUT_KEYBOARD
input.wVk = 0x90; //VK_NUMLOCK
input.wScan = 0;
input.dwFlags = 0; //key-down
input.time = 0;
input.dwExtraInfo = IntPtr.Zero;
input.type1 = 0x01;
input.wVk1 = 0x90;
input.wScan1 = 0;
input.dwFlags1 = 2; //key-up
input.time1 = 0;
input.dwExtraInfo1 = IntPtr.Zero;
IntPtr pI = Marshal.AllocHGlobal(mouseInpSize * 2);
Marshal.StructureToPtr(input, pI, false);
int result = SendInput(2, pI, mouseInpSize); //Hardcoded size of the MOUSEINPUT tag !!!
//if (result == 0 || Marshal.GetLastWin32Error() != 0)
// Console.WriteLine(Marshal.GetLastWin32Error());
Marshal.FreeHGlobal(pI);
}
You can do this via P/Invoke with GetKeyboardState and keybd_event.
The MSDN page for keybd_event shows exactly how to toggle num-lock, as well as get it's state (in C++).
There are P/Invoke signitures available on pinvoke.net for keybd_event and GetKeyboardState.
In addition to the answer given by Arsen:
There are problems with heap corruption in 64-bit builds. Programs using this code may crash at any point. To see this, enable the debug option "Enable Windows debug heap allocator". The debugger stops on calling FreeHGlobal.
It helps to calculate the size of the INPUT structure as follows.
int mouseInpSize = Marshal.SizeOf(input);
IntPtr pI = Marshal.AllocHGlobal(mouseInpSize);
Marshal.StructureToPtr(input, pI, false);
int result = SendInput(2, pI, mouseInpSize);
Marshal.FreeHGlobal(pI);