/* Copyright (C) 1992, 1995, 1996, 1997, 1999 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gdevwdib.c,v 1.3.2.1.2.1 2003/01/17 00:49:02 giles Exp $ */ /* MS Windows 3.n driver for Ghostscript using a DIB for buffering. */ #include "gdevmswn.h" #include "gxdevmem.h" #include "gsdll.h" #include "gsdllwin.h" #ifdef __WIN32__ # define USE_SEGMENTS 0 #else # define USE_SEGMENTS 1 #endif /* Make sure we cast to the correct structure type. */ typedef struct gx_device_win_dib_s gx_device_win_dib; #undef wdev #define wdev ((gx_device_win_dib *)dev) /* Device procedures */ /* See gxdevice.h for the definitions of the procedures. */ private dev_proc_open_device(win_dib_open); private dev_proc_get_initial_matrix(win_dib_get_initial_matrix); private dev_proc_close_device(win_dib_close); private dev_proc_fill_rectangle(win_dib_fill_rectangle); private dev_proc_copy_mono(win_dib_copy_mono); private dev_proc_copy_color(win_dib_copy_color); private dev_proc_get_bits(win_dib_get_bits); private dev_proc_put_params(win_dib_put_params); /* Windows-specific procedures */ private win_proc_repaint(win_dib_repaint); private win_proc_alloc_bitmap(win_dib_alloc_bitmap); private win_proc_free_bitmap(win_dib_free_bitmap); /* The device descriptor */ struct gx_device_win_dib_s { gx_device_common; gx_device_win_common; #if USE_SEGMENTS /* The following help manage the division of the DIB */ /* into 64K segments. Each block of y_block scan lines */ /* starting at y_base mod 64K falls in a single segment. */ /* Since the raster is a power of 2, y_block is a power of 2. */ int y_block; int y_base; int y_mask; /* y_block - 1 */ #endif /* USE_SEGMENTS */ HGLOBAL hmdata; #ifdef __WIN32__ HANDLE hmtx; #endif int lock_count; gx_device_memory mdev; }; private const gx_device_procs win_dib_procs = { win_dib_open, win_dib_get_initial_matrix, win_sync_output, win_output_page, win_dib_close, win_map_rgb_color, win_map_color_rgb, win_dib_fill_rectangle, NULL, /* tile_rectangle */ win_dib_copy_mono, win_dib_copy_color, NULL, /* draw_line */ win_dib_get_bits /* NULL */ , /* get_bits */ win_get_params, win_dib_put_params, NULL, /* map_cmyk_color */ win_get_xfont_procs, NULL, /* get_xfont_device */ NULL, /* map_rgb_alpha_color */ gx_page_device_get_page_device }; gx_device_win_dib far_data gs_mswindll_device = { std_device_std_body(gx_device_win_dib, &win_dib_procs, "mswindll", INITIAL_WIDTH, INITIAL_HEIGHT, /* win_open() fills these in later */ INITIAL_RESOLUTION, INITIAL_RESOLUTION /* win_open() fills these in later */ ), {0}, /* std_procs */ 0, /* BitsPerPixel */ 2, /* nColors */ 0, /* mapped_color_flags */ win_dib_alloc_bitmap, win_dib_free_bitmap }; /* forward declarations */ private HGLOBAL win_dib_make_dib(gx_device_win * dev, int orgx, int orgy, int wx, int wy); private int win_dib_lock_device(unsigned char *device, int flag); /* Open the win_dib driver */ private int win_dib_open(gx_device * dev) { int code = win_open(dev); if (code < 0) return code; #ifdef __WIN32__ if (!is_win32s) wdev->hmtx = CreateMutex(NULL, FALSE, NULL); /* unnamed mutex, initially unowned */ #endif if (gdev_mem_device_for_bits(dev->color_info.depth) == 0) { win_close(dev); return gs_error_rangecheck; } code = win_dib_alloc_bitmap((gx_device_win *) dev, dev); if (code < 0) { win_close(dev); return code; } /* notify caller about new device */ if (pgsdll_callback) { (*pgsdll_callback) (GSDLL_DEVICE, (unsigned char *)dev, 1); (*pgsdll_callback) (GSDLL_SIZE, (unsigned char *)dev, (dev->width & 0xffff) + ((ulong) (dev->height & 0xffff) << 16)); } return code; } /* Get the initial matrix. DIBs, unlike most displays, */ /* put (0,0) in the lower left corner. */ private void win_dib_get_initial_matrix(gx_device * dev, gs_matrix * pmat) { pmat->xx = dev->x_pixels_per_inch / 72.0; pmat->xy = 0.0; pmat->yx = 0.0; pmat->yy = dev->y_pixels_per_inch / 72.0; pmat->tx = 0.0; pmat->ty = 0.0; } /* Close the win_dib driver */ private int win_dib_close(gx_device * dev) { int code; /* wait until bitmap is not being used by caller */ win_dib_lock_device((unsigned char *)dev, 1); if (pgsdll_callback) (*pgsdll_callback) (GSDLL_DEVICE, (unsigned char *)dev, 0); win_dib_lock_device((unsigned char *)dev, 0); win_dib_free_bitmap((gx_device_win *) dev); #ifdef __WIN32__ if (!is_win32s) CloseHandle(wdev->hmtx); #endif code = win_close(dev); return code; } #define wmdev ((gx_device *)&wdev->mdev) #define wmproc(proc) (*dev_proc(&wdev->mdev, proc)) #if USE_SEGMENTS /* The drawing routines must all be careful not to cross */ /* a segment boundary. */ #define single_block(y, h)\ !(((y - wdev->y_base) ^ (y - wdev->y_base + h - 1)) & ~wdev->y_mask) #define BEGIN_BLOCKS\ { int by, bh, left = h;\ for ( by = y; left > 0; by += bh, left -= bh )\ { bh = wdev->y_block - (by & wdev->y_mask);\ if ( bh > left ) bh = left; #define END_BLOCKS\ }\ } #endif /* (!)USE_SEGMENTS */ /* Fill a rectangle. */ private int win_dib_fill_rectangle(gx_device * dev, int x, int y, int w, int h, gx_color_index color) { #if USE_SEGMENTS if (single_block(y, h)) { wmproc(fill_rectangle) (wmdev, x, y, w, h, color); } else { /* Divide the transfer into blocks. */ BEGIN_BLOCKS wmproc(fill_rectangle) (wmdev, x, by, w, bh, color); END_BLOCKS } #else wmproc(fill_rectangle) (wmdev, x, y, w, h, color); #endif return 0; } /* Copy a monochrome bitmap. The colors are given explicitly. */ /* Color = gx_no_color_index means transparent (no effect on the image). */ private int win_dib_copy_mono(gx_device * dev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h, gx_color_index zero, gx_color_index one) { #if USE_SEGMENTS if (single_block(y, h)) { wmproc(copy_mono) (wmdev, base, sourcex, raster, id, x, y, w, h, zero, one); } else { /* Divide the transfer into blocks. */ const byte *source = base; BEGIN_BLOCKS wmproc(copy_mono) (wmdev, source, sourcex, raster, gx_no_bitmap_id, x, by, w, bh, zero, one); source += bh * raster; END_BLOCKS } #else wmproc(copy_mono) (wmdev, base, sourcex, raster, id, x, y, w, h, zero, one); #endif return 0; } /* Copy a color pixel map. This is just like a bitmap, except that */ /* each pixel takes 8 or 4 bits instead of 1 when device driver has color. */ private int win_dib_copy_color(gx_device * dev, const byte * base, int sourcex, int raster, gx_bitmap_id id, int x, int y, int w, int h) { #if USE_SEGMENTS if (single_block(y, h)) { wmproc(copy_color) (wmdev, base, sourcex, raster, id, x, y, w, h); } else { /* Divide the transfer into blocks. */ const byte *source = base; BEGIN_BLOCKS wmproc(copy_color) (wmdev, source, sourcex, raster, gx_no_bitmap_id, x, by, w, bh); source += by * raster; END_BLOCKS } #else wmproc(copy_color) (wmdev, base, sourcex, raster, id, x, y, w, h); #endif return 0; } int win_dib_get_bits(gx_device * dev, int y, byte * str, byte ** actual_data) { return wmproc(get_bits) (wmdev, y, str, actual_data); } int win_dib_put_params(gx_device * dev, gs_param_list * plist) { int code; win_dib_lock_device((unsigned char *)dev, 1); code = win_put_params(dev, plist); win_dib_lock_device((unsigned char *)dev, 0); return code; } /* ------ DLL device procedures ------ */ /* make a copy of the device bitmap and return shared memory handle to it */ /* device is a pointer to Ghostscript device from GSDLL_DEVICE message */ HGLOBAL GSDLLAPI gsdll_copy_dib(unsigned char *device) { gx_device_win_dib *dev = (gx_device_win_dib *) device; if (!dev || !dev->is_open || dev->mdev.width == 0 || dev->mdev.height == 0) return (HGLOBAL) NULL; return win_dib_make_dib((gx_device_win *) dev, 0, 0, dev->width, dev->height); } /* make a copy of the device palette and return a handle to it */ /* device is a pointer to Ghostscript device from GSDLL_DEVICE message */ HPALETTE GSDLLAPI gsdll_copy_palette(unsigned char *device) { gx_device_win_dib *dev = (gx_device_win_dib *) device; if (!dev || !dev->is_open || dev->mdev.width == 0 || dev->mdev.height == 0) return (HPALETTE) NULL; if (wdev->nColors > 0) return CreatePalette(dev->limgpalette); return (HPALETTE) NULL; } /* copy the rectangle src from the device bitmap */ /* to the rectangle dest on the device given by hdc */ /* hdc must be a device context for a device (NOT a bitmap) */ /* device is a pointer to Ghostscript device from GSDLL_DEVICE message */ void GSDLLAPI gsdll_draw(unsigned char *device, HDC hdc, LPRECT dest, LPRECT src) { gx_device_win_dib *dev = (gx_device_win_dib *) device; HPALETTE oldpalette; if (!dev || !dev->is_open || dev->mdev.width == 0 || dev->mdev.height == 0) return; if (dev->nColors > 0) { oldpalette = SelectPalette(hdc, dev->himgpalette, FALSE); RealizePalette(hdc); } win_dib_repaint((gx_device_win *) dev, hdc, dest->left, dest->top, dest->right - dest->left, dest->bottom - dest->top, src->left, src->top); if (dev->nColors > 0) { SelectPalette(hdc, oldpalette, FALSE); } return; } /* ------ Windows-specific device procedures ------ */ /* Repaint a section of the window. */ private void win_dib_repaint(gx_device_win * dev, HDC hdc, int dx, int dy, int wx, int wy, int sx, int sy) { struct bmi_s { BITMAPINFOHEADER h; ushort pal_index[256]; } bmi; int i; UINT which_colors; memset(&bmi.h, 0, sizeof(bmi.h)); bmi.h.biSize = sizeof(bmi.h); bmi.h.biWidth = wdev->mdev.width; bmi.h.biHeight = wy; bmi.h.biPlanes = 1; bmi.h.biBitCount = dev->color_info.depth; bmi.h.biCompression = 0; bmi.h.biSizeImage = 0; /* default */ bmi.h.biXPelsPerMeter = 0; /* default */ bmi.h.biYPelsPerMeter = 0; /* default */ if (dev->BitsPerPixel <= 8) { bmi.h.biClrUsed = wdev->nColors; bmi.h.biClrImportant = wdev->nColors; for (i = 0; i < wdev->nColors; i++) bmi.pal_index[i] = i; which_colors = DIB_PAL_COLORS; } else if (dev->BitsPerPixel == 15) { /* 5-5-5 RGB mode */ DWORD* bmi_colors = (DWORD*)(&bmi.pal_index[0]); bmi.h.biCompression = BI_BITFIELDS; bmi_colors[0] = 0x7c00; bmi_colors[1] = 0x03e0; bmi_colors[2] = 0x001f; which_colors = DIB_RGB_COLORS; } else if (dev->BitsPerPixel == 16) { /* 5-6-5 RGB mode */ DWORD* bmi_colors = (DWORD*)(&bmi.pal_index[0]); bmi.h.biCompression = BI_BITFIELDS; bmi_colors[0] = 0xf800; bmi_colors[1] = 0x07e0; bmi_colors[2] = 0x001f; which_colors = DIB_RGB_COLORS; } else { bmi.h.biClrUsed = 0; bmi.h.biClrImportant = 0; which_colors = DIB_RGB_COLORS; } /* * Windows apparently limits the size of a single transfer * to 2 Mb, which can be exceeded on 24-bit displays. * Deal with this here. */ #define max_transfer 2000000 if (wdev->mdev.raster > 0) { /* just in case! */ long ny = max_transfer / wdev->mdev.raster; for (; wy > ny; dy += ny, wy -= ny, sy += ny) SetDIBitsToDevice(hdc, dx, dy, wx, ny, sx, 0, 0, ny, wdev->mdev.line_ptrs[wdev->height - (sy + ny)], (BITMAPINFO FAR *) & bmi, which_colors); } #undef max_transfer SetDIBitsToDevice(hdc, dx, dy, wx, wy, sx, 0, 0, wy, wdev->mdev.line_ptrs[wdev->height - (sy + wy)], (BITMAPINFO FAR *) & bmi, which_colors); } /* This makes a DIB that contains all or part of the bitmap. */ /* The bitmap pixel orgx must start on a byte boundary. */ private HGLOBAL win_dib_make_dib(gx_device_win * dev, int orgx, int orgy, int wx, int wy) { #define xwdev ((gx_device_win_dib *)dev) gx_color_value prgb[3]; HGLOBAL hglobal; BYTE FAR *pDIB; BITMAPINFOHEADER FAR *pbmih; RGBQUAD FAR *pColors; BYTE FAR *pBits; BYTE FAR *pLine; ulong bitmapsize; int palcount; int i; UINT lwidth; /* line width in bytes rounded up to multiple of 4 bytes */ #ifdef USE_SEGMENTS int loffset; /* byte offset to start of line */ UINT lseg; /* bytes remaining in this segment */ #endif if (orgx + wx > wdev->width) wx = wdev->width - orgx; if (orgy + wy > wdev->height) wy = wdev->height - orgy; loffset = orgx * wdev->color_info.depth / 8; lwidth = ((wx * wdev->color_info.depth + 31) & ~31) >> 3; bitmapsize = (long)lwidth *wy; if (wdev->color_info.depth > 16) palcount = 0; else if (wdev->color_info.depth > 8) palcount = 3; // 16-bit BI_BITFIELDS else palcount = wdev->nColors; hglobal = GlobalAlloc(GHND | GMEM_SHARE, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * palcount + bitmapsize); if (hglobal == (HGLOBAL) NULL) { MessageBeep(-1); return (HGLOBAL) NULL; } pDIB = (BYTE FAR *) GlobalLock(hglobal); if (pDIB == (BYTE FAR *) NULL) { MessageBeep(-1); return (HGLOBAL) NULL; } pbmih = (BITMAPINFOHEADER FAR *) (pDIB); pColors = (RGBQUAD FAR *) (pDIB + sizeof(BITMAPINFOHEADER)); pBits = (BYTE FAR *) (pDIB + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * palcount); pbmih->biSize = sizeof(BITMAPINFOHEADER); pbmih->biWidth = wx; pbmih->biHeight = wy; pbmih->biPlanes = 1; pbmih->biBitCount = wdev->color_info.depth; pbmih->biCompression = 0; pbmih->biSizeImage = 0; /* default */ pbmih->biXPelsPerMeter = (DWORD) (dev->x_pixels_per_inch / 25.4 * 1000); pbmih->biYPelsPerMeter = (DWORD) (dev->y_pixels_per_inch / 25.4 * 1000); pbmih->biClrUsed = palcount; pbmih->biClrImportant = palcount; if (dev->BitsPerPixel == 15) { /* 5-5-5 RGB mode */ DWORD* bmi_colors = (DWORD*)(pColors); pbmih->biCompression = BI_BITFIELDS; bmi_colors[0] = 0x7c00; bmi_colors[1] = 0x03e0; bmi_colors[2] = 0x001f; } else if (dev->BitsPerPixel == 16) { /* 5-6-5 RGB mode */ DWORD* bmi_colors = (DWORD*)(pColors); pbmih->biCompression = BI_BITFIELDS; bmi_colors[0] = 0xf800; bmi_colors[1] = 0x07e0; bmi_colors[2] = 0x001f; } else { for (i = 0; i < palcount; i++) { win_map_color_rgb((gx_device *) wdev, (gx_color_index) i, prgb); pColors[i].rgbRed = win_color_value(prgb[0]); pColors[i].rgbGreen = win_color_value(prgb[1]); pColors[i].rgbBlue = win_color_value(prgb[2]); pColors[i].rgbReserved = 0; } } pLine = pBits; for (i = orgy; i < orgy + wy; i++) { #if USE_SEGMENTS /* Window 3.1 has hmemcpy, but 3.0 doesn't */ lseg = (UINT) (-OFFSETOF(pLine)); /* remaining bytes in this segment */ if (lseg >= lwidth) { _fmemcpy(pLine, xwdev->mdev.line_ptrs[i] + loffset, lwidth); } else { /* break up transfer to avoid crossing segment boundary */ _fmemcpy(pLine, xwdev->mdev.line_ptrs[i] + loffset, lseg); _fmemcpy(pLine + lseg, xwdev->mdev.line_ptrs[i] + loffset + lseg, lwidth - lseg); } #else memcpy(pLine, xwdev->mdev.line_ptrs[i], lwidth); #endif pLine += lwidth; } GlobalUnlock(hglobal); return hglobal; } /* Allocate the backing bitmap. */ private int win_dib_alloc_bitmap(gx_device_win * dev, gx_device * param_dev) { int width; gx_device_memory mdev; HGLOBAL hmdata; byte FAR *base; byte FAR *ptr_base; uint ptr_size; uint raster; #ifdef USE_SEGMENTS ulong data_size; #endif #ifdef __WIN32__ if (is_win32s) { #endif /* Round up the width so that the scan line size is a power of 2. */ if (dev->color_info.depth == 24) { width = param_dev->width * 3 - 1; while (width & (width + 1)) width |= width >> 1; width = (width + 1) / 3; } else { width = param_dev->width - 1; while (width & (width + 1)) width |= width >> 1; width++; } #ifdef __WIN32__ } else { /* don't have to worry about segments so use less memory */ width = param_dev->width; } #endif /* Finish initializing the DIB. */ gs_make_mem_device(&mdev, gdev_mem_device_for_bits(dev->color_info.depth), 0, 0, (gx_device *) dev); mdev.width = width; mdev.height = param_dev->height; raster = gdev_mem_raster(&mdev); data_size = (ulong) raster *mdev.height; ptr_size = sizeof(byte **) * mdev.height; hmdata = GlobalAlloc(0, raster + data_size + ptr_size * 2); if (hmdata == 0) { return win_nomemory(); } /* Nothing can go wrong now.... */ wdev->hmdata = hmdata; base = GlobalLock(hmdata); #if USE_SEGMENTS /* Adjust base so scan lines, and the pointer table, */ /* don't cross a segment boundary. */ base += (-PTR_OFF(base) & (raster - 1)); ptr_base = base + data_size; if (PTR_OFF(ptr_base + ptr_size) < ptr_size) base += (uint) - PTR_OFF(ptr_base); wdev->y_block = 0x10000L / raster; wdev->y_mask = wdev->y_block - 1; if ((wdev->y_base = PTR_OFF(base)) != 0) wdev->y_base = -(PTR_OFF(base) / raster); #endif wdev->mdev = mdev; wdev->mdev.base = (byte *) base; wmproc(open_device) ((gx_device *) & wdev->mdev); if (wdev->is_open && pgsdll_callback) (*pgsdll_callback) (GSDLL_SIZE, (unsigned char *)dev, (dev->width & 0xffff) + ((ulong) (dev->height & 0xffff) << 16)); return 0; } /* Free the backing bitmap. */ private void win_dib_free_bitmap(gx_device_win * dev) { HGLOBAL hmdata = wdev->hmdata; GlobalUnlock(hmdata); GlobalFree(hmdata); } /* Lock the device (so it's size cannot be changed) if flag = TRUE */ /* or unlock the device if flag = FALSE */ /* device is a pointer to Ghostscript device from GSDLL_DEVICE message */ private int win_dib_lock_device(unsigned char *device, int flag) { gx_device *dev = (gx_device *) device; #ifdef __WIN32__ if (!is_win32s) { if (flag) { if (WaitForSingleObject(wdev->hmtx, 60000) == WAIT_TIMEOUT) return 2; return 1; } ReleaseMutex(wdev->hmtx); return 0; } #endif if (flag) wdev->lock_count++; else wdev->lock_count--; if (wdev->lock_count < 0) wdev->lock_count = 0; return wdev->lock_count; } int GSDLLAPI _export gsdll_lock_device(unsigned char *device, int flag) { return win_dib_lock_device(device, flag); } /* Copy bitmap * If pbmih nonzero, copy the BITMAPINFOHEADER. * If prgbquad nonzero, copy the palette. * number of entries copied is given by pbmih->biClrUsed * If ppbyte nonzero, return pointer to row. * pointer is only valid while device is locked * GS can change the palette while the device is locked. * Do not call this function while GS is busy. * If all pbmih and prgbquad and ppbyte are all NULL, * return value is byte count needed for BITMAPINFOHEADER * and palette and one bitmap row. * Otherwise return value is 0; * * This function exists to allow the bitmap to be copied to a file * or structured storage, without the overhead of having two copies * of the bitmap in memory at the same time. */ int GSDLLAPI _export gsdll_get_bitmap_row(unsigned char *device, LPBITMAPINFOHEADER pbmih, LPRGBQUAD prgbquad, LPBYTE * ppbyte, unsigned int row) { int palcount; gx_device_win_dib *dev = (gx_device_win_dib *) device; palcount = (dev->color_info.depth == 24) ? 0 : dev->nColors; if (pbmih) { pbmih->biSize = sizeof(BITMAPINFOHEADER); pbmih->biWidth = dev->width; pbmih->biHeight = dev->mdev.height; pbmih->biPlanes = 1; pbmih->biBitCount = dev->color_info.depth; if ((dev->BitsPerPixel == 15) || (dev->BitsPerPixel == 16)) pbmih->biCompression = BI_BITFIELDS; else pbmih->biCompression = 0; pbmih->biSizeImage = 0; /* default */ pbmih->biXPelsPerMeter = (DWORD) (dev->x_pixels_per_inch / 25.4 * 1000); pbmih->biYPelsPerMeter = (DWORD) (dev->y_pixels_per_inch / 25.4 * 1000); pbmih->biClrUsed = palcount; pbmih->biClrImportant = palcount; } if (prgbquad) { int i; gx_color_value prgb[3]; if (dev->BitsPerPixel == 15) { /* 5-5-5 RGB mode */ DWORD* bmi_colors = (DWORD*)(prgbquad); pbmih->biCompression = BI_BITFIELDS; bmi_colors[0] = 0x7c00; bmi_colors[1] = 0x03e0; bmi_colors[2] = 0x001f; } else if (dev->BitsPerPixel == 16) { /* 5-6-5 RGB mode */ DWORD* bmi_colors = (DWORD*)(prgbquad); pbmih->biCompression = BI_BITFIELDS; bmi_colors[0] = 0xf800; bmi_colors[1] = 0x07e0; bmi_colors[2] = 0x001f; } else { for (i = 0; i < palcount; i++) { win_map_color_rgb((gx_device *) wdev, (gx_color_index) i, prgb); prgbquad[i].rgbRed = win_color_value(prgb[0]); prgbquad[i].rgbGreen = win_color_value(prgb[1]); prgbquad[i].rgbBlue = win_color_value(prgb[2]); prgbquad[i].rgbReserved = 0; } } } if (ppbyte) { if (row < dev->mdev.height) *ppbyte = dev->mdev.line_ptrs[row]; else *ppbyte = NULL; } if ((pbmih == NULL) && (prgbquad == NULL) && (ppbyte == NULL)) return sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD) + gdev_mem_raster(&(dev->mdev)); return 0; }