c# unmanaged PInvoke AccessViolation - c#

I have C++ DLL.
When call method from this DLL in C# AccessViolation was been throwing.
What in my code is wrong? Can someone help me?
C++ Header part:
typedef PVOID X_HANDLE;
XREADER_API BOOL ReaderOpen(X_HANDLE *pxHandle);
XREADER_API BOOL ReaderReceiveW26(X_HANDLE xHandle, LPVOID pBuffer, DWORD nBufferSize);
Working Example part C++:
X_HANDLE hReader;
unsigned char xKeyBuffer[3];
ReaderOpen(&hReader);
ReaderReceiveW26(hReader,xKeyBuffer,sizeof(xKeyBuffer));
My C# Code:
[DllImport("reader.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
public static extern bool ReaderOpen(IntPtr reference);
[DllImport("reader.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
public static extern bool ReaderReceiveW26(IntPtr hReader, IntPtr pBuffer, uint xKeyBuffer);
static void Main(string[] args)
{
byte[] received = new byte[3];
IntPtr unmanagedPointer = Marshal.AllocHGlobal(received.Length);
Marshal.Copy(received, 0, unmanagedPointer, received.Length);
IntPtr hReader = Marshal.AllocHGlobal(sizeof(uint));
var qqq = uint.Parse((Marshal.SizeOf(typeof(byte)) * received.Length).ToString());
ReaderOpen(hReader);
while (true)
{
if (ReaderReceiveW26(hReader, unmanagedPointer, qqq))
{
Console.WriteLine("!");
}
}
}
AccessViolation throwing at ReaderReceiveW26(hReader, unmanagedPointer, qqq)
Thanks for your patience!

Thanks to Hast Passant comment!
If we want use IntPtr or smth which edited by unmanaged code we need use
out paramName
For PVOID we can pass real c# type.
Working example:
[DllImport("reader.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
public static extern bool ReaderOpen(out IntPtr reference);
[DllImport("reader.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
public static extern bool ReaderReceiveW26(IntPtr hReader, byte[] pBuffer, uint xKeyBuffer);

Related

Programmatically listing all supported locales in Windows 10

Windows 10 supports custom locales, locales that do not have an LCID like older locales from older versions of Windows. In each update to Windows 10, Microsoft has been adding lesser used locales for minority and indigenous languages.
A recent update to Windows 10 added the the Võro kiil (vro) locale. When using EnumSystemLocalesEx to enumerate all supported locales, vro does not appear in any form. However, in the system settings UI for adding a new language or keyboard, Võro kiil does appear.
However, if the user then enables this language, when you call EnumSystemLocalesEx, vro, vro-Latn and vro-Latn-001 are now listed. If the user then removes this locale from the UI, it no longer appears in the results of this function call.
The question: is there a way (supported or otherwise) to get a list of all the known locales to the operating system regardless of whether the user has enabled them or not?
I find it very bizarre that this output includes other minority languages like Skolt Sami without requiring the user to enable it in advance.
I will quite happily accept an answer that uses the .NET framework if the API does not exist in the C/C++ APIs, so long as I can actually get this data.
Example code to generate the locale output:
#include <cstdio>
#include "stdafx.h"
#include "windows.h"
BOOL CALLBACK Callback(LPWSTR pStr, DWORD dwFlags, LPARAM lparam)
{
wprintf(L"%ls\n", pStr);
return TRUE;
}
int main()
{
EnumSystemLocalesEx(Callback, 0, 0, 0);
return 0;
}
With Võro kiil enabled from the "Region and Language" screen in System Settings, the final three results are vro-Latn, vro-Latn-001 and vro. When not enabled, they do not appear in the output at all.
Using .NET APIs seems to have the same behaviour.
#include "stdafx.h"
using namespace System;
using namespace System::Globalization;
int main()
{
System::Collections::IEnumerator^ enum0 = CultureInfo::GetCultures(CultureTypes::AllCultures)->GetEnumerator();
while (enum0->MoveNext())
{
CultureInfo^ ci = safe_cast<CultureInfo^>(enum0->Current);
Console::WriteLine("{0}", ci->Name);
}
}
In effect, the new language model for Windows means that there is no "list" beyond those that have historical LCIDs.
The Windows 8.1 and 10 settings tools link to bcp47langs.dll and winlangdb.dll, which provide functions for enabling languages and input methods so long as the provided input is a valid ISO 639-3 language code.
In some cases, if you want your language to appear in the UI or via these APIs, you must provide at least the script and sometimes the region. An example is myv-Cyrl for the Erzya language.
Using these APIs
Using an MSIL disassembler on a cmdlet bundled with PowerShell, I found a p/invoke definition that has allowed me to successfully use these APIs from C# and Rust code.
Here it is, for posterity:
// Decompiled with JetBrains decompiler
// Type: Microsoft.InternationalSettings.Commands.LPAPIWrapper
// Assembly: Microsoft.InternationalSettings.Commands, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: E0B49792-544F-4FBD-8C35-D4DA177385AF
// Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.InternationalSettings.Commands\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.InternationalSettings.Commands.dll
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.InternationalSettings.Commands
{
internal class LPAPIWrapper
{
public static uint GEO_NATION = 1;
public static uint GEO_FRIENDLYNAME = 8;
public static uint GEOCLASS_NATION = 16;
public static uint GEOCLASS_REGION = 14;
[DllImport("kernelbase.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int NlsUpdateLocale(string LocaleName, int Flags);
[DllImport("intl.cpl", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int IntlUpdateSystemLocale(string LocaleName, int dwFlags);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SystemParametersInfo(
uint Action,
uint UnsignedParam,
IntPtr Param,
uint WinIni);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SendNotifyMessage(
IntPtr wWwnd,
uint Msg,
IntPtr wParam,
string lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetSystemDefaultLocaleName(
StringBuilder LocaleName,
int LocaleNameSize);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetUserGeoID(uint GeoClass);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SetUserGeoID(int GeoId);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetGeoInfo(
int Location,
uint GeoType,
StringBuilder GeoData,
int Length,
ushort LangID);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetUserLanguages(char Delimiter, [MarshalAs(UnmanagedType.HString)] ref string UserLanguages);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetUserLanguageInputMethods(
string Language,
char Delimiter,
[MarshalAs(UnmanagedType.HString)] ref string InputMethods);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int LcidFromBcp47([MarshalAs(UnmanagedType.HString)] string LanguageTag, ref int Lcid);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetPendingUserDisplayLanguage([MarshalAs(UnmanagedType.HString)] ref string language);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetUserDisplayLanguageOverride([MarshalAs(UnmanagedType.HString)] ref string language);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SetUserDisplayLanguageOverride(string LanguageTag);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int ClearUserDisplayLanguageOverride();
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetHttpAcceptLanguageOptOut(ref bool IsOptOut);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SetHttpAcceptLanguageOptOut();
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int ClearHttpAcceptLanguageOptOut();
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetUserLocaleFromLanguageProfileOptOut(ref bool IsOptOut);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SetUserLocaleFromLanguageProfileOptOut();
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int ClearUserLocaleFromLanguageProfileOptOut();
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int RemoveInputsForAllLanguagesInternal();
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SetInputMethodOverride(string TipString);
[DllImport("bcp47langs.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int Bcp47GetIsoLanguageCode([MarshalAs(UnmanagedType.HString)] string languageTag, [MarshalAs(UnmanagedType.HString)] ref string isoLanguageCode);
[DllImport("ext-ms-win-globalization-input-l1-1-2.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int WGIGetDefaultInputMethodForLanguage(
[MarshalAs(UnmanagedType.HString)] string Language,
[MarshalAs(UnmanagedType.HString)] ref string DefaultTipString);
[DllImport("ext-ms-win-globalization-input-l1-1-2.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int WGITransformInputMethodsForLanguage(
[MarshalAs(UnmanagedType.HString)] string TipString,
[MarshalAs(UnmanagedType.HString)] string Language,
[MarshalAs(UnmanagedType.HString)] ref string TransformedTipString);
[DllImport("winlangdb.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SetUserLanguages(char Delimiter, [MarshalAs(UnmanagedType.HString)] string UserLanguages);
[DllImport("winlangdb.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetLanguageNames(
string Language,
StringBuilder Autonym,
StringBuilder EnglishName,
StringBuilder LocalName,
StringBuilder ScriptName);
[DllImport("ext-ms-win-globalization-input-l1-1-2.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int WGIIsImeInputMethod([MarshalAs(UnmanagedType.HString)] string TipString, ref int result);
[DllImport("winlangdb.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int EnsureLanguageProfileExists();
[DllImport("input.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int InstallLayoutOrTip(string TipString, int Flags);
[DllImport("input.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SetDefaultLayoutOrTip(string TipString, int Flags);
[DllImport("input.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetLayoutDescription(
string LayoutId,
StringBuilder LayoutDescription,
ref int DescriptionLength);
private LPAPIWrapper()
{
}
}
}

C++ function to C#

I am totally new to C++ programming. I need to call a C++ function from C#.
C++ function is:
BOOL Usb_Init(HWND hwnd);
I've tried:
[DllImport("UsbComm.dll", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool Usb_Init( out IntPtr hwnd);
I got the Error message:
PInvoke signature does not match the unmanaged target signature.
How to call the above C++ method?
I see the following mistakes in the code in the question:
The C++ code uses cdecl and the C# code uses stdcall. That does not match.
The C++ code is passed an HWND by value. The C# code has an IntPtr passed as an out parameter. That does not match.
There are multiple spurious arguments to the DllImport attribute.
The correct C# declaration is:
[DllImport("UsbComm.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool Usb_Init(IntPtr hwnd);
You could set ExactSpelling to true but I see no compelling reason to do so. Feel free to add that if you prefer. There's no point in specifying CharSet since there is no text involved. And SetLastError = true is probably a mistake. It's unlikely in my judgement that the unmanaged function calls SetLastError. My expectation is that you added SetLastError = true whilst trying to get rid of the error.
BOOL is defined as int in <windef.h>
You'll need to use int in the export declaration in C#. Reminder: a value of 0 equals false; anything else is true.
public static extern int Usb_Init(out IntPtr hwnd);
But also, your calling convention could also be wrong. Try each enum of CallingConvention
EDIT: The working signature is
[DllImport("UsbComm.dll", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int Usb_Init(out IntPtr hwnd);
C++ code: Make sure definition of Usb_Init should look as shown below:
extern "C" __declspec(dllexport) BOOL __stdcall Usb_Init(HWND hwnd)
{
return TRUE;
}
C# code:
using System;
using System.Runtime.InteropServices;
namespace Win32DllClient
{
class Program
{
[DllImport("UsbComm.dll", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)]
public static extern bool Usb_Init(out IntPtr hwnd);
static void Main(string[] args)
{
IntPtr hwnd = new IntPtr(0);
var ret = Usb_Init(out hwnd);
}
}
}

External application receives message but no contained data using sharedmemory

I am using sharedmemory in my c# app with c++ interop. Currently I am marshalling a struct to a pointer and broadcasting the message. The program I am broadcasting to, opens up correctly with the debug message, but doesn't show/bring-in the data that I had in use within my struct.
Thanks!
The app I am trying to talk to was written in c++ and I am coding in c#. I am using all the DLLImports correctly (I think) and it compiles and runs error free.
using System.Runtime.InteropServices;
[DllImport("user32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public static extern uint RegisterWindowMessageW([In]string lpString);
[DllImport("user32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern uint RegisterWindowMessageA([In]string lpString);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
public static extern IntPtr OpenFileMapping(FileMapAccessRights dwDesiredAccess, int bInheritHandle, [In]String lpName);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, FileMapAccessRights dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, UIntPtr dwNumberOfBytesToMap);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall)]
public static extern int UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall)]
public static extern int CloseHandle(IntPtr hObject);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
uint WM_ZOOM_XYZ = RegisterWindowMessageA("WM_ZOOM_XYZ");
int i = Broadcast_Zoom_Message(10000, 10000, 0, WM_ZOOM_XYZ);
public int Broadcast_Zoom_Message(double dbX, double dbY, double dbZ, uint uMessage)
{
string smSharedMemory = "COORDINATES";
IntPtr hMem = OpenFileMapping(FileMapAccessRights.Write, FALSE, smSharedMemory);
if (IntPtr.Zero == hMem)
{
return 0;
}
IntPtr pvHead = MapViewOfFile(hMem, FileMapAccessRights.Write, 0, 0, UIntPtr.Zero);
if (IntPtr.Zero == pvHead)
{
CloseHandle(hMem);
MessageBox.Show(
"Unable to view " + smSharedMemory,
"Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return 0;
}
CoordinatesStruct structCoords = new CoordinatesStruct();
Marshal.PtrToStructure(pvHead, structCoords);
int bVersionOk = FALSE;
if (1 == structCoords.uMajorVersion)
{
if (WM_ZOOM_XYZ == uMessage)
{
structCoords.dbDesiredX = dbX;
structCoords.dbDesiredY = dbY;
structCoords.dbDesiredZ = dbZ;
}
bVersionOk = TRUE;
}
else
{
MessageBox.Show(
"Unrecognized shared memory: " +
structCoords.uMajorVersion.ToString() + "." + structCoords.uMinorVersion.ToString());
}
if (IntPtr.Zero != hMem)
{
CloseHandle(hMem);
}
UnmapViewOfFile(pvHead);
IntPtr HWND_BROADCAST = (IntPtr)0xffff;
if (bVersionOk == TRUE)
{
PostMessage(HWND_BROADCAST, uMessage, 0, 0);
return 1;
}
else
return 0;
}
I think your intention was to put the changed structCoords back to the mapped file. When we use Marshal.PtrToStructure() we receive a copy of the content of the unmanaged memory. The changes of the received object will not reflect in the unmanaged memory. When we are done with the data, we should put the changes back to the memory using Marshal.StructureToPtr.
Here is what I think it should be:
if (1 == structCoords.uMajorVersion)
{
if (WM_ZOOM_XYZ == uMessage)
{
structCoords.dbDesiredX = dbX;
structCoords.dbDesiredY = dbY;
structCoords.dbDesiredZ = dbZ;
}
bVersionOk = TRUE;
Marshal.StructureToPtr(structCoords , pvHead, false); // <-- this is what you (I) forgot!
}

c# BeginUpdateResource

I would like to add a string resource to an executable file programmatically. Just for example purposes, let's say I am trying to add a string named "String SO" which holds the value of "stringVal"
If this helps anyone - if I were to do this through VS.net I could just right click on my Project => Resources => Add New String Resource etc..
I am using the following Win32 API's:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool UpdateResource(IntPtr hUpdate, uint lpType, uint lpName, ushort wLanguage, byte[] lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
So, I have found a couple of pages online but none of them seem to help me in what I am trying to do. If any of you are able to find anything I would be very grateful.
Otherwise, I would greatly appreciate any snippets that may help.
Thank you,
Evan
There is a very helpful library for many resource-tasks at github.
Many classes and function do wrap those window-api-calls around UpdateResource(...), etc.
Hope that helps.
I'm injecting an application byte[] as a resource to execute it on runtime. Here's my piece of code, hope it helps:
class AddResource
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage, IntPtr lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
private static IntPtr ToPtr(object data)
{
GCHandle h = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr ptr;
try
{
ptr = h.AddrOfPinnedObject();
}
finally
{
h.Free();
}
return ptr;
}
public static bool InjectResource(string filename, byte[] bytes, string resourceName)
{
try
{
IntPtr handle = BeginUpdateResource(filename, false);
byte[] file1 = bytes;
IntPtr fileptr = ToPtr(file1);
bool res = UpdateResource(handle, resourceName,
//"RT_RCDATA",
"0", 0, fileptr, Convert.ToUInt32(file1.Length));
EndUpdateResource(handle, false);
}
catch
{
return false;
}
return true;
}
public static void CopyStream(Stream input, Stream output,long sz)
{
// Insert null checking here for production
byte[] buffer = new byte[sz];
int bytesRead;
while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, bytesRead);
}
}
}
Here is how I use it:
using (Stream input = Assembly.GetExecutingAssembly().GetManifestResourceStream("AppLicensing.Resources.SAppStarter.exe"))
using (Stream output = File.Create(outputFilePath))
{
long sz = input.Length;
AddResource.CopyStream(input, output, sz);
}
//inject crypted bytes
AddResource.InjectResource(outputFilePath, Encryptor.cryptedbytes, "RT_RCDATA");
And here is how I extract the resource (notice the "RT_RCDATA" -> that s the name of the resource):
class ReadResource
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);
[DllImport("Kernel32.dll", EntryPoint = "SizeofResource", SetLastError = true)]
private static extern uint SizeofResource(IntPtr hModule, IntPtr hResource);
[DllImport("Kernel32.dll", EntryPoint = "LoadResource", SetLastError = true)]
private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResource);
public static byte[] GetFromResource(String resourceName)
{
try
{
IntPtr hModule = GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
IntPtr loc = FindResource(hModule, "0", resourceName);
uint size = SizeofResource(hModule, loc);
IntPtr x = LoadResource(hModule, loc);
byte[] bPtr = new byte[size];
Marshal.Copy(x, bPtr, 0, (int)(size));
return bPtr;
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.ToString());
System.Environment.Exit(0);
return null;
}
}
}
byte[] encryptedData = ReadResource.GetFromResource("RT_RCDATA");
The code gets a bit messy... hope this helps.
Although the author is dealing with his own issue right now, the SO question UpdateResource function fails has code snippet for using these calls.
The code from Samson work with String lpType, that mean you can't actually add RT_RCDATA resource either reading from it, it's only create and read lpType named "RT_RCDATA" only. If you want it to read real RT data you'll need to modify lpType from string to uint and this is RT API table:
private const uint RT_CURSOR = 0x00000001;
private const uint RT_BITMAP = 0x00000002;
private const uint RT_ICON = 0x00000003;
private const uint RT_MENU = 0x00000004;
private const uint RT_DIALOG = 0x00000005;
private const uint RT_STRING = 0x00000006;
private const uint RT_FONTDIR = 0x00000007;
private const uint RT_FONT = 0x00000008;
private const uint RT_ACCELERATOR = 0x00000009;
private const uint RT_RCDATA = 0x0000000a;
private const uint RT_MESSAGETABLE = 0x0000000b;

P/Invoke function call problem

I am working on a system that requires interaction with a native C API using P/Invoke. Now I've (yet again) stumbled upon a problem which I cannot seem to solve in any way. The original function is designed to return 2 kinds of structures, based on a parameter that specifies which structure to use.
The C header file defines the structures and function as follows:
#pragma pack(1)
typedef struct {
DWORD JobId;
DWORD CardNum;
HANDLE hPrinter;
} CARDIDTYPE, FAR *LPCARDIDTYPE;
#pragma pack()
typedef struct {
BOOL bActive;
BOOL bSuccess;
} CARD_INFO_1, *PCARD_INFO_1, FAR *LPCARD_INFO_1;
typedef struct {
DWORD dwCopiesPrinted;
DWORD dwRemakeAttempts;
SYSTEMTIME TimeCompleted;
} CARD_INFO_2, *PCARD_INFO_2, FAR *LPCARD_INFO_2;
BOOL ICEAPI GetCardId(HDC hdc, LPCARDIDTYPE pCardId);
BOOL ICEAPI GetCardStatus(CARDIDTYPE CardId, DWORD level, LPBYTE pData, DWORD cbBuf, LPDWORD pcbNeeded );
I have attempted to implement P/Invoke wrappers like this:
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class CARDIDTYPE {
public UInt32 JobId;
public UInt32 CardNum;
public IntPtr hPrinter;
}
[StructLayout(LayoutKind.Sequential)]
public class CARD_INFO_1 {
public bool bActive;
public bool bSuccess;
}
[StructLayout(LayoutKind.Sequential)]
public class CARD_INFO_2 {
public UInt32 dwCopiesPrinted;
public UInt32 dwRemakeAttempts;
public Win32Util.SYSTEMTIME TimeCompleted;
}
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardId(HandleRef hDC, [Out]CARDIDTYPE pCardId);
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, [Out] byte[] pData, UInt32 cbBuf, out UInt32 pcbNeeded);
Calling the "GetCardId" seems to work fine. I get plausible data in CARDIDTYPE instance after calling it. However when I call "GetCardStatus" the problems start. The type of structure that should be returned is defined by the "level" param, and a value of 1 should result in a CARD_INFO_1 structure to be returnes in "pData".
The documentation contains the following C example:
CARD_INFO_1 ci1;
DWORD cbNeeded;
ci1.bActive = TRUE;
if (GetCardStatus(*lpCardID, 1, (LPBYTE)&ci1, sizeof(ci1), &cbNeeded )) { /* success */ }
My equivalent C# implementation is like this:
uint needed;
byte[] byteArray = new byte[Marshal.SizeOf(typeof(CARD_INFO_1))];
if (GetCardStatus(cardId, 1, byteArray, (uint)byteArray.Length, out needed)) { /* success */ }
When I execute this C# code, the method returns false and Marshal.GetLastWin32Error() return -1073741737 (which does not make much sense to me). I see no reason why this call should fail, and definitely not with this error code. So I suspect I have got something wrong in my P/Invoke wrapper.
I know that using "byte[]" as the type of pData is probably not correct, but according to some googling a "LPBYTE" translates to "[Out] byte[]". I guess the correct way to do this is to have pData as an IntPtr, and create the structure using Marshal.PtrToStructure(...). I have tried this, but the result is the same. Here is the code for this scenario:
[DllImport(#"ICE_API.DLL", CharSet = CharSet.Auto, EntryPoint = "_GetCardStatus#28", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, IntPtr pData, UInt32 cbBuf, out UInt32 pcbNeeded);
uint needed;
int memSize = Marshal.SizeOf(typeof(CARD_INFO_1));
IntPtr memPtr = Marshal.AllocHGlobal(memSize);
if (!GetCardStatus(cardId, 1, memPtr, (uint)memSize, out needed)) {
int lastError = Marshal.GetLastWin32Error();
// error code is -1073741737
}
CARD_INFO_1 info = (CARD_INFO_1)Marshal.PtrToStructure(memPtr, typeof(CARD_INFO_1));
Marshal.FreeHGlobal(memPtr);
Edit:
One thing I forgot to mention is that for some reason the GetCardStatus call fails with an unknown entry point exception if I do not specify EntryPoint = "_GetCardStatus#28". This has not happened to any other function I have wrapped, so it got me wondering a bit.
_GetCardStatus#28 gave me an idea. Unless you are running on 64-bit Windows, you've got the number of arguments wrong. Your P/Invoke for GetCardStatus would be _GetCardStatus#20, because it has 5 32-bit arguments. Your C declaration of GetCardStatus seems to accept the cardId by value rather than by reference. Since CARDIDTYPE is 12 bytes long, this would give the correct length of the argument list (28). Moreover, this would explain both your receiving an error code of -1073741737 (C0000057, STATUS_INVALID_PARAMETER) — since you're not passing a valid cardId — and the access violation — GetCardStatus tries to write to pcbNeeded, which is garbage because the marshaler hasn't even pushed it!
Ergo:
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus (
IntPtr hPrinter, UInt32 cardNum, UInt32 jobId, UInt32 level,
[In, Out] CARD_INFO_1 data, UInt32 cbBuf, out UInt32 pcbNeeded) ;
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus (
IntPtr hPrinter, UInt32 cardNum, UInt32 jobId, UInt32 level,
[In, Out] CARD_INFO_2 data, UInt32 cbBuf, out UInt32 pcbNeeded) ;
Note reverse order of the three CARDIDTYPE members: stdcall pushes the parameters left-to-right (i.e. towards lower addresses), and my guess is that a struct is "pushed" as a unit.
Also, if you later close the printer handle with CloseHandle, I'd suggest receiving the handle in CARDIDTYPE into an appropriate SafeHandle, not into a bare IntPtr, and declaring GetCardStatus to receive the safe handle.
As Anton suggests the problem lies in the parameters passed to the function. I did not notice this yesterday but the CARDIDTYPE structure is passed by pointer in the GetCardID function, and by value in the GetCardStatus function. In my calls I passed the CARDIDTYPE by pointer to the GetCardStatus also, forcing the P/Invoke framework to locate the correct function by specifying the exact function name as found in Dependecy Walker.
I solved this by defining the CARDIDTYPE as a struct instead of a class, and pass it by reference to the GetCardId function. Further the CARDIDTYPE is marshaled as a Struct when passed to the GetCardStatus function. This in addition to Antons technique of using two function definitions with different pData types (CARD_INFO_1 and CARD_INFO_2) now works correctly. Here is the final definitions:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CARDIDTYPE {
public UInt32 JobId;
public UInt32 CardNum;
public IntPtr hPrinter;
}
[StructLayout(LayoutKind.Sequential)]
public class CARD_INFO_1 {
public bool bActive;
public bool bSuccess;
}
[StructLayout(LayoutKind.Sequential)]
public class CARD_INFO_2 {
public UInt32 dwCopiesPrinted;
public UInt32 dwRemakeAttempts;
public Win32Util.SYSTEMTIME TimeCompleted;
}
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardId(HandleRef hDC, ref CARDIDTYPE pCardId);
[DllImport(#"ICE_API.DLL", EntryPoint = "GetCardStatus", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus([MarshalAs(UnmanagedType.Struct)]CARDIDTYPE CardId, UInt32 level,
[In, Out] CARD_INFO_1 pData, UInt32 cbBuf, out UInt32 pcbNeeded);
[DllImport(#"ICE_API.DLL", EntryPoint = "GetCardStatus", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus([MarshalAs(UnmanagedType.Struct)]CARDIDTYPE CardId, UInt32 level,
[In, Out] CARD_INFO_2 pData, UInt32 cbBuf, out UInt32 pcbNeeded);
Thank you both for your contribution to solving this problem :-)
The problem is you are using [Out] where you should be using nothing
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, byte[] pData, UInt32 cbBuf, out UInt32 pcbNeeded);
The Out/In attributes tell the CLR Marshaller in which direction the immediate variable will be marshalled. In the case of the byte[], the parameter is really doing nothing. One of it's sub elements is being moved.
Marshalling arrays is a tricky business though, especially when used directly in a signature vs. a structure. It may be better for you to use an IntPtr, allocate memory there and manually marshal the array out of the IntPtr.
[DllImport("ICE_API.DLL", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool GetCardStatus(CARDIDTYPE CardId, UInt32 level, IntPtr pData, UInt32 cbBuf, out UInt32 pcbNeeded);
public void Example(uint size) {
// Get other params
var ptr = Marshal.AllocHGlobal(size);
GetCardStatus(cardId, level, ptr, size, out needed);
// Marshal back the byte array here
Marshal.FreeHGlobal(ptr);
}

Categories