Related
I am trying to directly access video memory for a framebuffer video device on a Raspberry Pi using C# code running via Mono. I have a C program that works fine, but when I port it to C# it consistently fails on the "map to memory" step.
The working C program (courtesy of tasanakorn) looks like this:
#include <stdio.h>
#include <syslog.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <bcm_host.h>
int process() {
DISPMANX_DISPLAY_HANDLE_T display;
DISPMANX_MODEINFO_T display_info;
DISPMANX_RESOURCE_HANDLE_T screen_resource;
VC_IMAGE_TRANSFORM_T transform;
uint32_t image_prt;
VC_RECT_T rect1;
int ret;
int fbfd = 0;
char *fbp = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
bcm_host_init();
display = vc_dispmanx_display_open(0);
if (!display) {
syslog(LOG_ERR, "Unable to open primary display");
return -1;
}
ret = vc_dispmanx_display_get_info(display, &display_info);
if (ret) {
syslog(LOG_ERR, "Unable to get primary display information");
return -1;
}
syslog(LOG_INFO, "Primary display is %d x %d", display_info.width, display_info.height);
fbfd = open("/dev/fb1", O_RDWR);
if (fbfd == -1) {
syslog(LOG_ERR, "Unable to open secondary display");
return -1;
}
if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
syslog(LOG_ERR, "Unable to get secondary display information");
return -1;
}
if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
syslog(LOG_ERR, "Unable to get secondary display information");
return -1;
}
syslog(LOG_INFO, "Second display is %d x %d %dbps\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
screen_resource = vc_dispmanx_resource_create(VC_IMAGE_RGB565, vinfo.xres, vinfo.yres, &image_prt);
if (!screen_resource) {
syslog(LOG_ERR, "Unable to create screen buffer");
close(fbfd);
vc_dispmanx_display_close(display);
return -1;
}
fbp = (char*) mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
if (fbp <= 0) {
syslog(LOG_ERR, "Unable to create mamory mapping");
close(fbfd);
ret = vc_dispmanx_resource_delete(screen_resource);
vc_dispmanx_display_close(display);
return -1;
}
vc_dispmanx_rect_set(&rect1, 0, 0, vinfo.xres, vinfo.yres);
while (1) {
ret = vc_dispmanx_snapshot(display, screen_resource, 0);
vc_dispmanx_resource_read_data(screen_resource, &rect1, fbp, vinfo.xres * vinfo.bits_per_pixel / 8);
usleep(25 * 1000);
}
munmap(fbp, finfo.smem_len);
close(fbfd);
ret = vc_dispmanx_resource_delete(screen_resource);
vc_dispmanx_display_close(display);
}
int main(int argc, char **argv) {
setlogmask(LOG_UPTO(LOG_DEBUG));
openlog("fbcp", LOG_NDELAY | LOG_PID, LOG_USER);
return process();
}
The C# code (in which I've tried to port all of the C code related to /dev/fb1) is:
using System;
using System.Runtime.InteropServices;
namespace MainProgram {
class MainClass {
static void Main(string[] args) {
int fbfd = -1; // file descriptor for framebuffer device
int fbp = -1; // pointer to mapped framebuffer memory
uint fbs = 0; // size of mapped framebuffer memory
int result = 0; // utility result variable
try {
// Initialize (not sure if this is needed, but...).
Libc.bcm_host_init();
// Open the device. (Command line param is device, e.g. "/dev/fb0" or "/dev/fb1".)
fbfd = Libc.open(args[0], Libc.O_RDWR);
if (fbfd < 0)
throw new Exception("open: error " + Marshal.GetLastWin32Error());
Console.WriteLine("fbfd=" + fbfd);
// Get fixed screen info.
Libc.fb_fix_screeninfo fixInfo = new Libc.fb_fix_screeninfo();
result = Libc.ioctl1(fbfd, Libc.FBIOGET_FSCREENINFO, ref fixInfo);
if (result < 0)
throw new Exception("ioctl1: error " + Marshal.GetLastWin32Error());
Console.WriteLine("fbfix: mem start=" + fixInfo.smem_start.ToString("X8") + ", len=" + fixInfo.smem_len);
// Get variable screen info.
Libc.fb_var_screeninfo varInfo = new Libc.fb_var_screeninfo();
result = Libc.ioctl2(fbfd, Libc.FBIOGET_VSCREENINFO, ref varInfo);
if (result < 0)
throw new Exception("ioctl2: error " + Marshal.GetLastWin32Error());
Console.WriteLine("fbvar: res=" + varInfo.xres + "x" + varInfo.yres + ", bpp=" + varInfo.bits_per_pixel);
// Map framebuffer memory to virtual space.
fbs = fixInfo.smem_len;
Console.WriteLine("Confirm non-zero size: fbs=" + fbs);
fbp = Libc.mmap(0, fbs, Libc.PROT_READ | Libc.PROT_WRITE, Libc.MAP_SHARED, fbfd, 0);
if (fbp < 0)
throw new Exception("mmap: error " + Marshal.GetLastWin32Error());
Console.WriteLine("mmap: location=" + fbp.ToString("X8"));
}
catch (Exception ex) {
Console.WriteLine("*** Error: " + ex.Message);
}
finally {
if (fbp >= 0)
result = Libc.munmap(fbp, fbs);
if (fbfd >= 0)
result = Libc.close(fbfd);
};
}
public static class Libc {
public const int O_RDWR = 0x0002;
public const int PROT_READ = 0x04;
public const int PROT_WRITE = 0x02;
public const int MAP_FILE = 0x0001;
public const int MAP_SHARED = 0x0010;
public const int FBIOGET_VSCREENINFO = 0x4600;
public const int FBIOGET_FSCREENINFO = 0x4602;
[DllImport("libbcm_host.so", EntryPoint = "bcm_host_init")]
public static extern void bcm_host_init();
[DllImport("libc", EntryPoint="open", SetLastError = true)]
public static extern int open(
[MarshalAs(UnmanagedType.LPStr)] string filename,
[MarshalAs(UnmanagedType.I4)] int flags
);
[DllImport("libc", EntryPoint="close", SetLastError = true)]
public static extern int close(
[MarshalAs(UnmanagedType.I4)] int filedes
);
[DllImport("libc", EntryPoint="ioctl", SetLastError = true)]
public static extern int ioctl1(
[MarshalAs(UnmanagedType.I4)] int filedes,
[MarshalAs(UnmanagedType.I4)] int command,
ref fb_fix_screeninfo data
);
[DllImport("libc", EntryPoint="ioctl", SetLastError = true)]
public static extern int ioctl2(
[MarshalAs(UnmanagedType.I4)] int filedes,
[MarshalAs(UnmanagedType.I4)] int command,
ref fb_var_screeninfo data
);
[DllImport("libc", EntryPoint = "mmap", SetLastError = true)]
public static extern int mmap(
[MarshalAs(UnmanagedType.U4)] uint addr,
[MarshalAs(UnmanagedType.U4)] uint length,
[MarshalAs(UnmanagedType.I4)] int prot,
[MarshalAs(UnmanagedType.I4)] int flags,
[MarshalAs(UnmanagedType.I4)] int fdes,
[MarshalAs(UnmanagedType.I4)] int offset
);
[DllImport("libc", EntryPoint = "munmap", SetLastError = true)]
public static extern int munmap(
[MarshalAs(UnmanagedType.I4)] int addr,
[MarshalAs(UnmanagedType.U4)] uint length
);
[StructLayout(LayoutKind.Sequential)]
public struct fb_fix_screeninfo {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] id;
[MarshalAs(UnmanagedType.U4)] public uint smem_start;
[MarshalAs(UnmanagedType.U4)] public uint smem_len;
[MarshalAs(UnmanagedType.U4)] public uint type;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)] public byte[] stuff;
};
[StructLayout(LayoutKind.Sequential)]
public struct fb_var_screeninfo {
[MarshalAs(UnmanagedType.U4)] public uint xres;
[MarshalAs(UnmanagedType.U4)] public uint yres;
[MarshalAs(UnmanagedType.U4)] public uint xres_virtual;
[MarshalAs(UnmanagedType.U4)] public uint yres_virtual;
[MarshalAs(UnmanagedType.U4)] public uint xoffset;
[MarshalAs(UnmanagedType.U4)] public uint yoffset;
[MarshalAs(UnmanagedType.U4)] public uint bits_per_pixel;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 132)] public byte[] stuff;
};
}
}
}
When I try to run the compiled code on the Pi, I get this following:
$ sudo mono ConsoleApp6.exe /dev/fb1
fbfd=4
fbfix: mem start=00000000, len=307200
fbvar: res=480x320, bpp=16
Confirm non-zero size: fbs=307200
*** Error: mmap: error 22
(I tried the same thing with the main monitor, also a framebuffer device, and got the following -- same error.)
$ sudo mono ConsoleApp6.exe /dev/fb0
fbfd=4
fbfix: mem start=1E876000, len=3686400
fbvar: res=1280x720, bpp=32
Confirm non-zero size: fbs=3686400
*** Error: mmap: error 22
Error 22 is EINVAL, which the mmap() documentation describes as, "We don't like addr, length, or offset (e.g., they are too large, or not aligned on a page boundary)." [It could also mean that: a) the length parameter is zero (which it isn't), or b) that both MAP_SHARED and MAP_PRIVATE are set (which they aren't)]. However, the values I'm passing in should be the same values used by the C code.
Does anyone have any idea what I'm doing wrong?
Solved. The problem is the constants PROT_READ, PROT_WRITE, MAP_SHARED, ... on the Raspberry Pi are not the same as the values I uncovered via Google searches (headslap). I found the correct values by looking at the .h files in /usr/include. Also, I discovered that a negative value for mmap is not necessarily an error; I needed to be looking specifically for MAP_FAILED (-1).
Here is the working code if anyone can use it:
using System;
using System.Runtime.InteropServices;
namespace MainProgram {
class MainClass {
static void Main(string[] args) {
int fbfd = -1; // file descriptor for framebuffer device
int fbp = -1; // pointer to mapped framebuffer memory
uint fbs = 0; // size of mapped framebuffer memory
int result = 0; // utility result variable
try {
// Initialize (not sure if this is needed, but...).
Libc.bcm_host_init();
// Open the device. (Command line param is device, e.g. "/dev/fb0" or "/dev/fb1".)
fbfd = Libc.open(args[0], Libc.O_RDWR);
if (fbfd == -1)
throw new Exception("open: result=" + fbfd + ", error=" + Marshal.GetLastWin32Error());
Console.WriteLine("fbfd=" + fbfd);
// Get fixed screen info.
Libc.fb_fix_screeninfo fixInfo = new Libc.fb_fix_screeninfo();
result = Libc.ioctl1(fbfd, Libc.FBIOGET_FSCREENINFO, ref fixInfo);
if (result == -1)
throw new Exception("ioctl1: result=" + result + ", error " + Marshal.GetLastWin32Error());
Console.WriteLine("fbfix: mem start=" + fixInfo.smem_start.ToString("X8") + ", len=" + fixInfo.smem_len);
// Get variable screen info.
Libc.fb_var_screeninfo varInfo = new Libc.fb_var_screeninfo();
result = Libc.ioctl2(fbfd, Libc.FBIOGET_VSCREENINFO, ref varInfo);
if (result == -1)
throw new Exception("ioctl2: result=" + result + ", error " + Marshal.GetLastWin32Error());
Console.WriteLine("fbvar: res=" + varInfo.xres + "x" + varInfo.yres + ", bpp=" + varInfo.bits_per_pixel);
// Map framebuffer memory to virtual space.
fbp = Libc.mmap(0, fixInfo.smem_len, Libc.PROT_READ | Libc.PROT_WRITE, Libc.MAP_SHARED, fbfd, 0);
if (fbp == Libc.MAP_FAILED)
throw new Exception("mmap: result=" + fbp + ", error " + Marshal.GetLastWin32Error());
Console.WriteLine("mmap: location=" + fbp.ToString("X8"));
}
catch (Exception ex) {
Console.WriteLine("*** Error: " + ex.Message);
}
finally {
if (fbp != -1)
result = Libc.munmap(fbp, fbs);
if (fbfd != -1)
result = Libc.close(fbfd);
};
}
public static class Libc {
public const int O_RDWR = 0x0002;
public const int PROT_READ = 0x1;
public const int PROT_WRITE = 0x2;
public const int MAP_SHARED = 0x01;
public const int MAP_FAILED = -1;
public const int FBIOGET_VSCREENINFO = 0x4600;
public const int FBIOGET_FSCREENINFO = 0x4602;
[DllImport("libbcm_host.so", EntryPoint = "bcm_host_init")]
public static extern void bcm_host_init();
[DllImport("libc", EntryPoint="open", SetLastError = true)]
public static extern int open(
[MarshalAs(UnmanagedType.LPStr)] string filename,
[MarshalAs(UnmanagedType.I4)] int flags
);
[DllImport("libc", EntryPoint="close", SetLastError = true)]
public static extern int close(
[MarshalAs(UnmanagedType.I4)] int filedes
);
[DllImport("libc", EntryPoint="ioctl", SetLastError = true)]
public static extern int ioctl1(
[MarshalAs(UnmanagedType.I4)] int filedes,
[MarshalAs(UnmanagedType.I4)] int command,
ref fb_fix_screeninfo data
);
[DllImport("libc", EntryPoint="ioctl", SetLastError = true)]
public static extern int ioctl2(
[MarshalAs(UnmanagedType.I4)] int filedes,
[MarshalAs(UnmanagedType.I4)] int command,
ref fb_var_screeninfo data
);
[DllImport("libc", EntryPoint = "mmap", SetLastError = true)]
public static extern int mmap(
[MarshalAs(UnmanagedType.U4)] uint addr,
[MarshalAs(UnmanagedType.U4)] uint length,
[MarshalAs(UnmanagedType.I4)] int prot,
[MarshalAs(UnmanagedType.I4)] int flags,
[MarshalAs(UnmanagedType.I4)] int fdes,
[MarshalAs(UnmanagedType.I4)] int offset
);
[DllImport("libc", EntryPoint = "munmap", SetLastError = true)]
public static extern int munmap(
[MarshalAs(UnmanagedType.I4)] int addr,
[MarshalAs(UnmanagedType.U4)] uint length
);
[StructLayout(LayoutKind.Sequential)]
public struct fb_fix_screeninfo {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] id;
[MarshalAs(UnmanagedType.U4)] public uint smem_start;
[MarshalAs(UnmanagedType.U4)] public uint smem_len;
[MarshalAs(UnmanagedType.U4)] public uint type;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)] public byte[] stuff;
};
[StructLayout(LayoutKind.Sequential)]
public struct fb_var_screeninfo {
[MarshalAs(UnmanagedType.U4)] public uint xres;
[MarshalAs(UnmanagedType.U4)] public uint yres;
[MarshalAs(UnmanagedType.U4)] public uint xres_virtual;
[MarshalAs(UnmanagedType.U4)] public uint yres_virtual;
[MarshalAs(UnmanagedType.U4)] public uint xoffset;
[MarshalAs(UnmanagedType.U4)] public uint yoffset;
[MarshalAs(UnmanagedType.U4)] public uint bits_per_pixel;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 132)] public byte[] stuff;
};
}
}
}
I'm working with SafeCom (print server) and figured out that they have a Administrator DLL Programmer’s Manual. As a proof of concept I wanted to make a small C# application that sends and retrieves data using this library. I've managed to insert a user object by calling LoginByLogon() followed by AddUser().
However, I'm not able to retrieve/return a user struct by calling LoginByLogon() followed by GetUser. I can see that ptrTemp is set (not null), but all properties in newStructure are still null. I don't fully understand PInvoke yet, so I hope someone here can enlighten me a little. :)
I'm for now importing the following:
[DllImport("scAPI.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int sc_LoginByLogon(string szIpAddr, string szUserName, string szPassword);
[DllImport("scAPI.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int sc_Logoff();
[DllImport("scAPI.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
private static extern int sc_GetUserInfoByUserLogon(string szUserLogon, out IntPtr ppUserInfo);
[DllImport("scAPI.dll", CallingConvention = CallingConvention.Cdecl)]
static extern short sc_AddUser(ref sUserInfoApiV4 ppUserInfo, double fAccount, double FLowLimit, ref int pNewId);
Define my structs:
struct SYSTEMTIME
{
short wYear;
short wMonth;
// short wDayOfWeek;
short wDay;
short wHour;
short wMinute;
short wSecond;
short wMilliseconds;
public static implicit operator SYSTEMTIME(DateTime time)
{
return new SYSTEMTIME
{
wYear = (short)time.Year,
wMonth = (short)time.Month,
wDay = (short)time.Day,
// wDayOfWeek = (short)time.DayOfWeek,
wHour = (short)time.Hour,
wMinute = (short)time.Minute,
wSecond = (short)time.Second,
wMilliseconds = (short)time.Millisecond
};
}
public static implicit operator DateTime(SYSTEMTIME time)
{
return new DateTime(time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Card
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
public string m_szCardNo; //Card number
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
public string m_szPINCode; //PIN code - zero terminated
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string m_szPUKCode; //PUK code - zero terminated
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
public struct sUserInfoApiV4
{
[MarshalAs(UnmanagedType.I4)]
public Int32 m_nStructLength; //Length of the struct including this field
[MarshalAs(UnmanagedType.I2)]
public short m_nVersion; //Version Number (set to 4)
[MarshalAs(UnmanagedType.I2)]
public short m_nSubVersion; //Version of Reserved (set to 0)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public byte[] m_achReserved; // Get room for m_nAccessRights
[MarshalAs(UnmanagedType.I2)]
public short m_nUserType; //USER_STRUCT (uses as UserType - BitFields)
[MarshalAs(UnmanagedType.I4)]
public int m_nUserId; //Unique id identifying the user
public int m_nSubRights; // Sub version 2
[MarshalAs(UnmanagedType.I4)]
public int m_nNotUsed1; // Sub version 5
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 101)]
public string m_wzFullName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 101)]
public string m_wzDescription;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 101)]
public string m_wzEMail;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
public string m_wzUserLogon; //Asc-ii string identifying the user
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]
public string m_wzPassword; //Asc-ii string password
public Card card;
[MarshalAs(UnmanagedType.I2)]
public short m_nLogonFails; //Number of failed logins
[MarshalAs(UnmanagedType.I2)]
public short m_bUserLocked; //Unlocked(0), Locked(1)
[MarshalAs(UnmanagedType.I2)]
public short m_bUserDisabled; //Not Disable(0), Open(1)
[MarshalAs(UnmanagedType.I2)]
public short m_bAvoidPin; //Pin Enabled(0), Pin disabled (1)
[MarshalAs(UnmanagedType.I2)]
public short m_bPrintAll; //Print All when card is slid - Yes(1) No(0)
[MarshalAs(UnmanagedType.I2)]
public short m_bCardOpen; //PIN code assigned(0), Awaiting pin assignment/Outstanding PUK(1)
[MarshalAs(UnmanagedType.I2)]
public short m_nBillingModel; //None(0), BillingDialog(1)
[MarshalAs(UnmanagedType.I2)]
public short m_nAccountingModel; //None(0), Tracking(1), Pay(2)
[MarshalAs(UnmanagedType.I4)]
public int m_nUserRights; // None(0)
[MarshalAs(UnmanagedType.I2)]
public short m_bAllowEncryption; // The user uses encryption YES(1) NO(0)
[MarshalAs(UnmanagedType.U1)]
bool m_bAllowCheckPrinting; // The user is allowed to receives checks YES(1) NO(0)
[MarshalAs(UnmanagedType.U1)]
bool m_bAllowPmail; // The user is allowed distribution YES(1) NO(0)
[MarshalAs(UnmanagedType.U1)]
bool m_bDenyRetain; // User allowed to retain his documents. New in SubVersion 3
public int m_lCreationDate;
public int m_lLastLogin;
[MarshalAs(UnmanagedType.I4)]
public int m_nServerId; // HomeServer of this user.
[MarshalAs(UnmanagedType.I4)]
public int m_nDomainId; // New in Subversion 1 - Reference to the windows domain the user are located in
[MarshalAs(UnmanagedType.I4)]
public int m_nTreeNodeId;
[MarshalAs(UnmanagedType.I4)]
public int m_nNid;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 51)]
public string m_wzCostCode; // Cost center, new in SubVersion 3
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct SCardInfoApiV4
{
[MarshalAs(UnmanagedType.I4)]
public Int32 m_nStructLength; // Length of entire struct
[MarshalAs(UnmanagedType.I2)]
public short m_nVersion; // Version (short)
[MarshalAs(UnmanagedType.I2)]
public short m_nSubVersion; // Subversion of reserved, set to 1 (short)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public byte[] m_achReserved; // Reserved for protocol expansion (fixed char)
[MarshalAs(UnmanagedType.I4)]
public int m_nCardId;
[MarshalAs(UnmanagedType.I4)]
public int m_nUserId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
public string m_achCardNo; // originalt fixed char [40]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
public string m_achReservedPinCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string m_achReservedPukCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 51)]
public string m_achDescription;
[MarshalAs(UnmanagedType.I4)]
int m_nCreatorId;
SYSTEMTIME m_sCreated;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
public string m_nUseLifeTime; //originalt char
SYSTEMTIME m_sLifeStart;
SYSTEMTIME m_sLifeEnd;
[MarshalAs(UnmanagedType.I4)]
int m_nSourceId; // Sub version 1
}
And finally my methods:
public int LoginByLogon(string ipAddr, string userLogon, string password)
{
return sc_LoginByLogon(ipAddr, userLogon, password);
}
public int Logoff()
{
return sc_Logoff();
}
public sUserInfoApiV4 GetUser(string userLogon)
{
sUserInfoApiV4 tmpUserInfo = new sUserInfoApiV4();
IntPtr ptrTemp = Marshal.AllocHGlobal(Marshal.SizeOf(tmpUserInfo));
sc_GetUserInfoByUserLogon(userLogon, out ptrTemp);
Marshal.StructureToPtr(tmpUserInfo, ptrTemp, false);
sUserInfoApiV4 newStructure;
newStructure = (sUserInfoApiV4)Marshal.PtrToStructure(ptrTemp, typeof(sUserInfoApiV4));
return newStructure;
}
public unsafe int AddUser(string usercode, string email, string pinCode, string fullName)
{
sUserInfoApiV4 sUserstruct = new sUserInfoApiV4();
int sizeOfstruct = Marshal.SizeOf(typeof(sUserInfoApiV4));
IntPtr pSUserstruct = Marshal.AllocHGlobal(sizeOfstruct); //Make note of memory used
Marshal.StructureToPtr(sUserstruct, pSUserstruct, false);
sUserstruct.m_wzUserLogon = usercode;
sUserstruct.card.m_szCardNo = usercode; //Card number same as logon
sUserstruct.card.m_szPINCode = pinCode;
sUserstruct.m_wzFullName = fullName;
sUserstruct.m_wzEMail = email;
sUserstruct.m_nUserRights = 3; //Standard user rights
sUserstruct.m_nAccountingModel = 2; //Pay user
sUserstruct.m_nVersion = 4; //Safecom version
sUserstruct.m_nStructLength = sizeOfstruct;
sUserstruct.m_bAvoidPin = 1; // Disable PIN
int userId = 0; //Value of variable not used, just to initialize the variable. Used as pointer for return value. Index number for user in db
int* userIndex = &userId;
int nRes = sc_AddUser(ref sUserstruct, 0, 0, ref *userIndex);
Marshal.FreeHGlobal(pSUserstruct); // Release memory
pSUserstruct = IntPtr.Zero; // used by pointer.
return nRes;
}
I'm trying to catch VIP and PID from USB device:
public const int WM_DEVICECHANGE = 0x219;
public const int DBT_DEVTYP_VOLUME = 0x00000002;
public const int DBT_DEVICEARRIVAL = 0x8000;
[StructLayout(LayoutKind.Sequential)]
internal class DEV_BROADCAST_HDR
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
public byte[] dbcc_classguid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public char[] dbcc_name;
}
public void WndProc(ref Message m)
{
if (m.Msg == WM_DEVICECHANGE) //Device state has changed
{
switch (m.WParam.ToInt32())
{
case DBT_DEVICEARRIVAL: //New device arrives
DEV_BROADCAST_HDR hdr;
hdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
if (hdr.dbch_devicetype == DBT_DEVTYP_VOLUME) //If it is a USB Mass Storage or Hard Drive
{
//Save Device name
DEV_BROADCAST_DEVICEINTERFACE deviceInterface;
string deviceName = "";
deviceInterface = (DEV_BROADCAST_DEVICEINTERFACE)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_DEVICEINTERFACE));
deviceName = new string(deviceInterface.dbcc_name).Trim();
}
}
}
}
But deviceName always returns a string with non sense characters. I have change CharSet in DEV_BROADCAST_DEVICEINTERFACE structure and declare dbcc.name as string but the result is the same.
I would like to avoid reading from registry, and among all I have read, I have seen that it is possible to cast a DEV_BROADCAST_HEADER to a DEV_BROADCAST_DEVICEINTERFACE only if dbch_devicetype==DBT_DEVTYP_DEVICEINTERFACE. In my case, dbch_devicetype is 2, not 5, and I am using some common USB Mass Storage devices. What am I doing wrong? Thanks in advance!
Maybe, there is a more elegant way to resolve this question, but at least, and after a long search, this seems to be working.
On one hand, I register the app in order to receive the correct info to cast it as DEV_BROADCAST_DEVICEINTERFACE structure (DEV_BROADCAST_HEADER.dbch_devicetype is 5 in this case). So, I am able to retrieve VID and PID info. On the other hand, I keep first Windows message WndProc receives to retrieve the volume that Windows assings when I connect USB devices (DEV_BROADCAST_HEADER.dbch_devicetype is 2). Then, I receive two messages.
In code:
public const int WM_DEVICECHANGE = 0x219;
public const int DBT_DEVTYP_VOLUME = 0x00000002;
public const int DBT_DEVICEARRIVAL = 0x8000;
public const int DBT_DEVTYP_DEVICEINTERFACE = 0x00000005;
private IntPtr notificationHandle;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);
[StructLayout(LayoutKind.Sequential)]
internal class DEV_BROADCAST_HDR
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
public Guid dbcc_classguid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public char[] dbcc_name;
}
public void WndProc(ref Message m)
{
if (m.Msg == WM_DEVICECHANGE) //Device state has changed
{
switch (m.WParam.ToInt32())
{
case DBT_DEVICEARRIVAL: //New device arrives
DEV_BROADCAST_HDR hdr;
hdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
if (hdr.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) //If it is a USB Mass Storage or Hard Drive
{
//Save Device name
DEV_BROADCAST_DEVICEINTERFACE deviceInterface = (DEV_BROADCAST_DEVICEINTERFACE)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_DEVICEINTERFACE));
deviceName = new string(deviceInterface.dbcc_name);
deviceNameFiltered = deviceName.Substring(0, deviceName.IndexOf('{'));
vid = GetVid(deviceName);
pid = GetPid(deviceName);
}
if (hdr.dbch_devicetype == DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME volume;
volume = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
//Translate mask to device letter
driveLetter = DriveMaskToLetter(volume.dbcv_unitmask);
}
}
}
To register app in order to receive the correct info to cast it as DEV_BROADCAST_DEVICEINTERFACE structure, it is necessary to call to this last RegisterUsbDeviceNotification method from Form class with its window handler as argument.
public void RegisterUsbDeviceNotification(IntPtr windowHandle)
{
DEV_BROADCAST_DEVICEINTERFACE deviceInterface = new DEV_BROADCAST_DEVICEINTERFACE
{
dbcc_classguid = GuidDevinterfaceUSBDevice,
dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE,
dbcc_reserved = 0,
};
deviceInterface.dbcc_size = Marshal.SizeOf(deviceInterface);
IntPtr buffer = Marshal.AllocHGlobal(deviceInterface.dbcc_size);
Marshal.StructureToPtr(deviceInterface, buffer, true);
notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
}
I've been searching for a c# library that gets the icon of a given path with many sizes, finally when I got exactly the class that I need, It has a problem:
This method gets icon of a given path:
public static BitmapSource GetIcon(string FileName, bool small, bool checkDisk, bool addOverlay)
{
SHFILEINFO shinfo = new SHFILEINFO();
uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
uint SHGFI_LINKOVERLAY = 0x000008000;
uint flags;
if (small)
{
flags = SHGFI_ICON | SHGFI_SMALLICON;
}
else
{
flags = SHGFI_ICON | SHGFI_LARGEICON;
}
if (!checkDisk)
flags |= SHGFI_USEFILEATTRIBUTES;
if (addOverlay)
flags |= SHGFI_LINKOVERLAY;
var res = SHGetFileInfo(FileName, 0, ref shinfo, Marshal.SizeOf(shinfo), flags);
if (res == 0)
{
throw (new System.IO.FileNotFoundException());
}
var ico = System.Drawing.Icon.FromHandle(shinfo.hIcon); //**Here**
var bs = BitmapFromIcon(ico);
ico.Dispose();
bs.Freeze();
DestroyIcon(shinfo.hIcon);
// CloseHandle(shinfo.hIcon); it always give exception
return bs;
}
public static extern Boolean CloseHandle(IntPtr handle);
The previous code as it is in this question works as it suppose to, however AFTER getting the icons of file paths in a directory successfully, it gives an exception on this line :
var ico = System.Drawing.Icon.FromHandle(shinfo.hIcon);
An exception of type 'System.IO.FileNotFoundException' occurred in WPF_REMOTE.exe but was not handled in user code
Additional information: Unable to find the specified file.
So Why is this happening?
Update: I found out that it happened because there were a path that contains unicode characters and i need to use SHFILEINFOW instead, still can't figure how to change SHFILEINFO to SHFILEINFOW
another question about the line CloseHandle(shinfo.hIcon); always give an exception :
An exception of type 'System.Runtime.InteropServices.SEHException' occurred in WPF_REMOTE.exe but was not handled in user code
Additional information: External component has thrown an exception.
I'm wondering why it's not working! and why should I use it if the method is already working without it.
also if you have any improvement I could use in this class tell me, Thanks in Advance.
After editing some code I get it, you can get the icons easily from this library, in my case i needed the icon as byte[]
public class IconHelper
{
// Constants that we need in the function call
private const int SHGFI_ICON = 0x100;
private const int SHGFI_SMALLICON = 0x1;
private const int SHGFI_LARGEICON = 0x0;
private const int SHIL_JUMBO = 0x4;
private const int SHIL_EXTRALARGE = 0x2;
// This structure will contain information about the file
public struct SHFILEINFO
{
// Handle to the icon representing the file
public IntPtr hIcon;
// Index of the icon within the image list
public int iIcon;
// Various attributes of the file
public uint dwAttributes;
// Path to the file
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szDisplayName;
// File type
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
public static extern Boolean CloseHandle(IntPtr handle);
private struct IMAGELISTDRAWPARAMS
{
public int cbSize;
public IntPtr himl;
public int i;
public IntPtr hdcDst;
public int x;
public int y;
public int cx;
public int cy;
public int xBitmap; // x offest from the upperleft of bitmap
public int yBitmap; // y offset from the upperleft of bitmap
public int rgbBk;
public int rgbFg;
public int fStyle;
public int dwRop;
public int fState;
public int Frame;
public int crEffect;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGEINFO
{
public IntPtr hbmImage;
public IntPtr hbmMask;
public int Unused1;
public int Unused2;
public Rect rcImage;
}
[DllImport("shell32.dll", EntryPoint = "#727")]
private extern static int SHGetImageList(
int iImageList,
ref Guid riid,
out IImageList ppv
);
// The signature of SHGetFileInfo (located in Shell32.dll)
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
public static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, uint uFlags);
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
public static extern int SHGetFileInfo(IntPtr pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, uint uFlags);
[DllImport("shell32.dll", SetLastError = true)]
static extern int SHGetSpecialFolderLocation(IntPtr hwndOwner, Int32 nFolder,
ref IntPtr ppidl);
[DllImport("user32")]
public static extern int DestroyIcon(IntPtr hIcon);
public struct pair
{
public System.Drawing.Icon icon { get; set; }
public IntPtr iconHandleToDestroy { set; get; }
}
private static byte[] ByteFromIcon(System.Drawing.Icon ic)
{
var icon = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon(ic.Handle,
System.Windows.Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
icon.Freeze();
byte[] data;
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(icon));
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
data = ms.ToArray();
}
return data;
}
private static byte[] GetSmallIcon(string FileName, IconSize iconSize)
{
SHFILEINFO shinfo = new SHFILEINFO();
uint flags;
if (iconSize == IconSize.Small)
{
flags = SHGFI_ICON | SHGFI_SMALLICON;
}
else
{
flags = SHGFI_ICON | SHGFI_LARGEICON;
}
var res = SHGetFileInfo(FileName, 0, ref shinfo, Marshal.SizeOf(shinfo), flags);
if (res == 0)
{
throw (new System.IO.FileNotFoundException());
}
var ico = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shinfo.hIcon);
var bs = ByteFromIcon(ico);
ico.Dispose();
DestroyIcon(shinfo.hIcon);
return bs;
}
private static byte[] GetLargeIcon(string FileName)
{
SHFILEINFO shinfo = new SHFILEINFO();
uint SHGFI_SYSICONINDEX = 0x4000;
int FILE_ATTRIBUTE_NORMAL = 0x80;
uint flags;
flags = SHGFI_SYSICONINDEX;
var res = SHGetFileInfo(FileName, FILE_ATTRIBUTE_NORMAL, ref shinfo, Marshal.SizeOf(shinfo), flags);
if (res == 0)
{
throw (new System.IO.FileNotFoundException());
}
var iconIndex = shinfo.iIcon;
Guid iidImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
IImageList iml;
int size = SHIL_EXTRALARGE;
var hres = SHGetImageList(size, ref iidImageList, out iml); // writes iml
IntPtr hIcon = IntPtr.Zero;
int ILD_TRANSPARENT = 1;
hres = iml.GetIcon(iconIndex, ILD_TRANSPARENT, ref hIcon);
var ico = System.Drawing.Icon.FromHandle(hIcon);
var bs = ByteFromIcon(ico);
ico.Dispose();
DestroyIcon(hIcon);
return bs;
}
}
and you can get four different sizes for the icon
I'm trying to use the Windows API to set the primary monitor. It doesn't seem to work - my screen just flicks and nothing happens.
public const int DM_ORIENTATION = 0x00000001;
public const int DM_PAPERSIZE = 0x00000002;
public const int DM_PAPERLENGTH = 0x00000004;
public const int DM_PAPERWIDTH = 0x00000008;
public const int DM_SCALE = 0x00000010;
public const int DM_POSITION = 0x00000020;
public const int DM_NUP = 0x00000040;
public const int DM_DISPLAYORIENTATION = 0x00000080;
public const int DM_COPIES = 0x00000100;
public const int DM_DEFAULTSOURCE = 0x00000200;
public const int DM_PRINTQUALITY = 0x00000400;
public const int DM_COLOR = 0x00000800;
public const int DM_DUPLEX = 0x00001000;
public const int DM_YRESOLUTION = 0x00002000;
public const int DM_TTOPTION = 0x00004000;
public const int DM_COLLATE = 0x00008000;
public const int DM_FORMNAME = 0x00010000;
public const int DM_LOGPIXELS = 0x00020000;
public const int DM_BITSPERPEL = 0x00040000;
public const int DM_PELSWIDTH = 0x00080000;
public const int DM_PELSHEIGHT = 0x00100000;
public const int DM_DISPLAYFLAGS = 0x00200000;
public const int DM_DISPLAYFREQUENCY = 0x00400000;
public const int DM_ICMMETHOD = 0x00800000;
public const int DM_ICMINTENT = 0x01000000;
public const int DM_MEDIATYPE = 0x02000000;
public const int DM_DITHERTYPE = 0x04000000;
public const int DM_PANNINGWIDTH = 0x08000000;
public const int DM_PANNINGHEIGHT = 0x10000000;
public const int DM_DISPLAYFIXEDOUTPUT = 0x20000000;
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int CDS_TEST = 0x02;
public const int CDS_SET_PRIMARY = 0x00000010;
public const long DISP_CHANGE_SUCCESSFUL = 0;
public const long DISP_CHANGE_RESTART = 1;
public const long DISP_CHANGE_FAILED = -1;
public const long DISP_CHANGE_BADMODE = -2;
public const long DISP_CHANGE_NOTUPDATED = -3;
public const long DISP_CHANGE_BADFLAGS = -4;
public const long DISP_CHANGE_BADPARAM = -5;
public const long DISP_CHANGE_BADDUALVIEW = -6;
public static void SetPrimary(Screen screen)
{
DISPLAY_DEVICE d = new DISPLAY_DEVICE();
DEVMODE dm = new DEVMODE();
d.cb = Marshal.SizeOf(d);
uint deviceID = 1;
User_32.EnumDisplayDevices(null, deviceID, ref d, 0); //
User_32.EnumDisplaySettings(d.DeviceName, 0, ref dm);
dm.dmPelsWidth = 2560;
dm.dmPelsHeight = 1600;
dm.dmPositionX = screen.Bounds.Right;
dm.dmFields = DM_POSITION | DM_PELSWIDTH | DM_PELSHEIGHT;
User_32.ChangeDisplaySettingsEx(d.DeviceName, ref dm, IntPtr.Zero, CDS_SET_PRIMARY, IntPtr.Zero);
}
I call the method like this:
SetPrimary(Screen.AllScreens[1])
Any ideas?
Here is the full code based on ADBailey's solution:
public class MonitorChanger
{
public static void SetAsPrimaryMonitor(uint id)
{
var device = new DISPLAY_DEVICE();
var deviceMode = new DEVMODE();
device.cb = Marshal.SizeOf(device);
NativeMethods.EnumDisplayDevices(null, id, ref device, 0);
NativeMethods.EnumDisplaySettings(device.DeviceName, -1, ref deviceMode);
var offsetx = deviceMode.dmPosition.x;
var offsety = deviceMode.dmPosition.y;
deviceMode.dmPosition.x = 0;
deviceMode.dmPosition.y = 0;
NativeMethods.ChangeDisplaySettingsEx(
device.DeviceName,
ref deviceMode,
(IntPtr)null,
(ChangeDisplaySettingsFlags.CDS_SET_PRIMARY | ChangeDisplaySettingsFlags.CDS_UPDATEREGISTRY | ChangeDisplaySettingsFlags.CDS_NORESET),
IntPtr.Zero);
device = new DISPLAY_DEVICE();
device.cb = Marshal.SizeOf(device);
// Update remaining devices
for (uint otherid = 0; NativeMethods.EnumDisplayDevices(null, otherid, ref device, 0); otherid++)
{
if (device.StateFlags.HasFlag(DisplayDeviceStateFlags.AttachedToDesktop) && otherid != id)
{
device.cb = Marshal.SizeOf(device);
var otherDeviceMode = new DEVMODE();
NativeMethods.EnumDisplaySettings(device.DeviceName, -1, ref otherDeviceMode);
otherDeviceMode.dmPosition.x -= offsetx;
otherDeviceMode.dmPosition.y -= offsety;
NativeMethods.ChangeDisplaySettingsEx(
device.DeviceName,
ref otherDeviceMode,
(IntPtr)null,
(ChangeDisplaySettingsFlags.CDS_UPDATEREGISTRY | ChangeDisplaySettingsFlags.CDS_NORESET),
IntPtr.Zero);
}
device.cb = Marshal.SizeOf(device);
}
// Apply settings
NativeMethods.ChangeDisplaySettingsEx(null, IntPtr.Zero, (IntPtr)null, ChangeDisplaySettingsFlags.CDS_NONE, (IntPtr)null);
}
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public struct DEVMODE
{
public const int CCHDEVICENAME = 32;
public const int CCHFORMNAME = 32;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
[System.Runtime.InteropServices.FieldOffset(0)]
public string dmDeviceName;
[System.Runtime.InteropServices.FieldOffset(32)]
public Int16 dmSpecVersion;
[System.Runtime.InteropServices.FieldOffset(34)]
public Int16 dmDriverVersion;
[System.Runtime.InteropServices.FieldOffset(36)]
public Int16 dmSize;
[System.Runtime.InteropServices.FieldOffset(38)]
public Int16 dmDriverExtra;
[System.Runtime.InteropServices.FieldOffset(40)]
public UInt32 dmFields;
[System.Runtime.InteropServices.FieldOffset(44)]
Int16 dmOrientation;
[System.Runtime.InteropServices.FieldOffset(46)]
Int16 dmPaperSize;
[System.Runtime.InteropServices.FieldOffset(48)]
Int16 dmPaperLength;
[System.Runtime.InteropServices.FieldOffset(50)]
Int16 dmPaperWidth;
[System.Runtime.InteropServices.FieldOffset(52)]
Int16 dmScale;
[System.Runtime.InteropServices.FieldOffset(54)]
Int16 dmCopies;
[System.Runtime.InteropServices.FieldOffset(56)]
Int16 dmDefaultSource;
[System.Runtime.InteropServices.FieldOffset(58)]
Int16 dmPrintQuality;
[System.Runtime.InteropServices.FieldOffset(44)]
public POINTL dmPosition;
[System.Runtime.InteropServices.FieldOffset(52)]
public Int32 dmDisplayOrientation;
[System.Runtime.InteropServices.FieldOffset(56)]
public Int32 dmDisplayFixedOutput;
[System.Runtime.InteropServices.FieldOffset(60)]
public short dmColor; // See note below!
[System.Runtime.InteropServices.FieldOffset(62)]
public short dmDuplex; // See note below!
[System.Runtime.InteropServices.FieldOffset(64)]
public short dmYResolution;
[System.Runtime.InteropServices.FieldOffset(66)]
public short dmTTOption;
[System.Runtime.InteropServices.FieldOffset(68)]
public short dmCollate; // See note below!
[System.Runtime.InteropServices.FieldOffset(72)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
public string dmFormName;
[System.Runtime.InteropServices.FieldOffset(102)]
public Int16 dmLogPixels;
[System.Runtime.InteropServices.FieldOffset(104)]
public Int32 dmBitsPerPel;
[System.Runtime.InteropServices.FieldOffset(108)]
public Int32 dmPelsWidth;
[System.Runtime.InteropServices.FieldOffset(112)]
public Int32 dmPelsHeight;
[System.Runtime.InteropServices.FieldOffset(116)]
public Int32 dmDisplayFlags;
[System.Runtime.InteropServices.FieldOffset(116)]
public Int32 dmNup;
[System.Runtime.InteropServices.FieldOffset(120)]
public Int32 dmDisplayFrequency;
}
public enum DISP_CHANGE : int
{
Successful = 0,
Restart = 1,
Failed = -1,
BadMode = -2,
NotUpdated = -3,
BadFlags = -4,
BadParam = -5,
BadDualView = -6
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DISPLAY_DEVICE
{
[MarshalAs(UnmanagedType.U4)]
public int cb;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceString;
[MarshalAs(UnmanagedType.U4)]
public DisplayDeviceStateFlags StateFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceKey;
}
[Flags()]
public enum DisplayDeviceStateFlags : int
{
/// <summary>The device is part of the desktop.</summary>
AttachedToDesktop = 0x1,
MultiDriver = 0x2,
/// <summary>The device is part of the desktop.</summary>
PrimaryDevice = 0x4,
/// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
MirroringDriver = 0x8,
/// <summary>The device is VGA compatible.</summary>
VGACompatible = 0x10,
/// <summary>The device is removable; it cannot be the primary display.</summary>
Removable = 0x20,
/// <summary>The device has more display modes than its output devices support.</summary>
ModesPruned = 0x8000000,
Remote = 0x4000000,
Disconnect = 0x2000000,
}
[Flags()]
public enum ChangeDisplaySettingsFlags : uint
{
CDS_NONE = 0,
CDS_UPDATEREGISTRY = 0x00000001,
CDS_TEST = 0x00000002,
CDS_FULLSCREEN = 0x00000004,
CDS_GLOBAL = 0x00000008,
CDS_SET_PRIMARY = 0x00000010,
CDS_VIDEOPARAMETERS = 0x00000020,
CDS_ENABLE_UNSAFE_MODES = 0x00000100,
CDS_DISABLE_UNSAFE_MODES = 0x00000200,
CDS_RESET = 0x40000000,
CDS_RESET_EX = 0x20000000,
CDS_NORESET = 0x10000000
}
public class NativeMethods
{
[DllImport("user32.dll")]
public static extern DISP_CHANGE ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, ChangeDisplaySettingsFlags dwflags, IntPtr lParam);
[DllImport("user32.dll")]
// A signature for ChangeDisplaySettingsEx with a DEVMODE struct as the second parameter won't allow you to pass in IntPtr.Zero, so create an overload
public static extern DISP_CHANGE ChangeDisplaySettingsEx(string lpszDeviceName, IntPtr lpDevMode, IntPtr hwnd, ChangeDisplaySettingsFlags dwflags, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
[DllImport("user32.dll")]
public static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTL
{
public int x;
public int y;
}
I ran into exactly the same problem, both from C# and after following the advice here to try it in C++. I eventually discovered that the thing the Microsoft documentation doesn't make clear is that the request to set the primary monitor will be ignored (but with the operation reported as successful!) unless you also set the position of the monitor to (0, 0) on the DEVMODE struct. Of course, this means that you also need to shift the positions of your other monitors so that they stay in the same place relative to the new primary monitor. Per the documentation (http://msdn.microsoft.com/en-us/library/windows/desktop/dd183413%28v=vs.85%29.aspx), call ChangeDisplaySettingsEx for each monitor with the CDS_NORESET flag and then make a final call with everything null.
The following code worked for me:
public static void SetAsPrimaryMonitor(uint id)
{
var device = new DISPLAY_DEVICE();
var deviceMode = new DEVMODE();
device.cb = Marshal.SizeOf(device);
NativeMethods.EnumDisplayDevices(null, id, ref device, 0);
NativeMethods.EnumDisplaySettings(device.DeviceName, -1, ref deviceMode);
var offsetx = deviceMode.dmPosition.x;
var offsety = deviceMode.dmPosition.y;
deviceMode.dmPosition.x = 0;
deviceMode.dmPosition.y = 0;
NativeMethods.ChangeDisplaySettingsEx(
device.DeviceName,
ref deviceMode,
(IntPtr)null,
(ChangeDisplaySettingsFlags.CDS_SET_PRIMARY | ChangeDisplaySettingsFlags.CDS_UPDATEREGISTRY | ChangeDisplaySettingsFlags.CDS_NORESET),
IntPtr.Zero);
device = new DISPLAY_DEVICE();
device.cb = Marshal.SizeOf(device);
// Update remaining devices
for (uint otherid = 0; NativeMethods.EnumDisplayDevices(null, otherid, ref device, 0); otherid++)
{
if (device.StateFlags.HasFlag(DisplayDeviceStateFlags.AttachedToDesktop) && otherid != id)
{
device.cb = Marshal.SizeOf(device);
var otherDeviceMode = new DEVMODE();
NativeMethods.EnumDisplaySettings(device.DeviceName, -1, ref otherDeviceMode);
otherDeviceMode.dmPosition.x -= offsetx;
otherDeviceMode.dmPosition.y -= offsety;
NativeMethods.ChangeDisplaySettingsEx(
device.DeviceName,
ref otherDeviceMode,
(IntPtr)null,
(ChangeDisplaySettingsFlags.CDS_UPDATEREGISTRY | ChangeDisplaySettingsFlags.CDS_NORESET),
IntPtr.Zero);
}
device.cb = Marshal.SizeOf(device);
}
// Apply settings
NativeMethods.ChangeDisplaySettingsEx(null, IntPtr.Zero, (IntPtr)null, ChangeDisplaySettingsFlags.CDS_NONE, (IntPtr)null);
}
Note that a signature for ChangeDisplaySettingsEx with a DEVMODE struct as the second parameter obviously won't allow you to pass in IntPtr.Zero. Create yourself two different signatures for the same extern call, i.e.
[DllImport("user32.dll")]
public static extern DISP_CHANGE ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, ChangeDisplaySettingsFlags dwflags, IntPtr lParam);
[DllImport("user32.dll")]
public static extern DISP_CHANGE ChangeDisplaySettingsEx(string lpszDeviceName, IntPtr lpDevMode, IntPtr hwnd, ChangeDisplaySettingsFlags dwflags, IntPtr lParam);
I can't really help you with the winapi-stuff but if you are using a Nvidia card you may have a look at the NVcontrolPanel Api Documentation
Then you could make the secondary output your primary using rundll32.exe NvCpl.dll,dtcfg primary 2
Hope that will help you.
According to the documentation for ChangeDisplaySettingsEx, "the dmSize member must be initialized to the size, in bytes, of the DEVMODE structure." Furthermore, the EnumDisplaySettings documentation states, "Before calling EnumDisplaySettings, set the dmSize member to sizeof(DEVMODE), and set the dmDriverExtra member to indicate the size, in bytes, of the additional space available to receive private driver data". I don't see this happening in the code sample given in the question; that's one reason why it may be failing.
Additionally, you might have errors in the definitions of the DEVMODE and DISPLAY_DEVICE structs, which were not included in the question. Roger Lipscombe's suggestion to get it working from C/C++ first is an excellent way to rule out this type of problem.
Finally, check the return value from ChangeDisplaySettingsEx and see if that gives a clue as to why it might be failing.