Whenever I try to reference my own DLL in C# through Visual Studio, it tells me it was unable to make a reference to the DLL as it's not a COM library.
I've searched around the internet to find a solution to this with no clear answer or help any where really. It's a rather "simple" DLL which captures the raw picture data from a Fingerprint Scanner. I have tested that the C++ code worked just fine before I tried to make it into a DLL, just so you know.
I followed Microsofts guide on how to make a DLL and here is what I ended up with:
JTBioCaptureFuncsDll.h
JTBioCaptureFuncsDll.cpp
JTBioCapture.cpp
JTBioCaptureFuncsDll.h
#ifdef JTBIOCAPTUREFUNCSDLL_EXPORTS
#define JTBIOCAPTUREFUNCSDLL_API __declspec(dllexport)
#else
#define JTBIOCAPTUREFUNCSDLL_API __declspec(dllimport)
#endif
using byte = unsigned char*;
struct BioCaptureSample {
INT32 Width;
INT32 Height;
INT32 PixelDepth;
byte Buffer;
};
JTBioCaptureFuncsDll.cpp
// JTBioCapture.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
namespace JTBioCapture
{
using byte = unsigned char*;
class JTBioCapture
{
public:
// Returns a Struct with Information Regarding the Fingerprint Sample
static JTBIOCAPTUREFUNCSDLL_API BioCaptureSample CaptureSample();
};
}
JTBioCapture.cpp
/*
* Courtesy of WinBio God Satish Agrawal on Stackoverflow
*/
BioCaptureSample CaptureSample()
{
HRESULT hr = S_OK;
WINBIO_SESSION_HANDLE sessionHandle = NULL;
WINBIO_UNIT_ID unitId = 0;
WINBIO_REJECT_DETAIL rejectDetail = 0;
PWINBIO_BIR sample = NULL;
SIZE_T sampleSize = 0;
// Connect to the system pool.
hr = WinBioOpenSession(
WINBIO_TYPE_FINGERPRINT, // Service provider
WINBIO_POOL_SYSTEM, // Pool type
WINBIO_FLAG_RAW, // Access: Capture raw data
NULL, // Array of biometric unit IDs
0, // Count of biometric unit IDs
WINBIO_DB_DEFAULT, // Default database
&sessionHandle // [out] Session handle
);
if (FAILED(hr))
{
wprintf_s(L"\n WinBioOpenSession failed. hr = 0x%x\n", hr);
goto e_Exit;
}
// Capture a biometric sample.
wprintf_s(L"\n Calling WinBioCaptureSample - Swipe sensor...\n");
hr = WinBioCaptureSample(
sessionHandle,
WINBIO_NO_PURPOSE_AVAILABLE,
WINBIO_DATA_FLAG_RAW,
&unitId,
&sample,
&sampleSize,
&rejectDetail
);
if (FAILED(hr))
{
if (hr == WINBIO_E_BAD_CAPTURE)
{
wprintf_s(L"\n Bad capture; reason: %d\n", rejectDetail);
}
else
{
wprintf_s(L"\n WinBioCaptureSample failed. hr = 0x%x\n", hr);
}
goto e_Exit;
}
wprintf_s(L"\n Swipe processed - Unit ID: %d\n", unitId);
wprintf_s(L"\n Captured %d bytes.\n", sampleSize);
// Courtesy of Art "Messiah" Baker at Microsoft
PWINBIO_BIR_HEADER BirHeader = (PWINBIO_BIR_HEADER)(((PBYTE)sample) + sample->HeaderBlock.Offset);
PWINBIO_BDB_ANSI_381_HEADER AnsiBdbHeader = (PWINBIO_BDB_ANSI_381_HEADER)(((PBYTE)sample) + sample->StandardDataBlock.Offset);
PWINBIO_BDB_ANSI_381_RECORD AnsiBdbRecord = (PWINBIO_BDB_ANSI_381_RECORD)(((PBYTE)AnsiBdbHeader) + sizeof(WINBIO_BDB_ANSI_381_HEADER));
PBYTE firstPixel = (PBYTE)((PBYTE)AnsiBdbRecord) + sizeof(WINBIO_BDB_ANSI_381_RECORD);
int width = AnsiBdbRecord->HorizontalLineLength;
int height = AnsiBdbRecord->VerticalLineLength;
wprintf_s(L"\n ID: %d\n", AnsiBdbHeader->ProductId.Owner);
wprintf_s(L"\n Width: %d\n", AnsiBdbRecord->HorizontalLineLength);
wprintf_s(L"\n Height: %d\n", AnsiBdbRecord->VerticalLineLength);
BioCaptureSample returnSample;
byte byteBuffer;
for (int i = 0; i < AnsiBdbRecord->BlockLength; i++) {
byteBuffer[i] = firstPixel[i];
}
returnSample.Buffer = byteBuffer;
returnSample.Height = height;
returnSample.Width = width;
returnSample.PixelDepth = AnsiBdbHeader->PixelDepth;
/*
* NOTE: (width / 3) is necessary because we ask for a 24-bit BMP but is only provided
* a greyscale image which is 8-bit. So we have to cut the bytes by a factor of 3.
*/
// Commented out as we only need the Byte buffer. Comment it back in should you need to save a BMP of the fingerprint.
// bool b = SaveBMP(firstPixel, (width / 3), height, AnsiBdbRecord->BlockLength, L"C:\\Users\\smf\\Desktop\\fingerprint.bmp");
// wprintf_s(L"\n Success: %d\n", b);
e_Exit:
if (sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
if (sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
wprintf_s(L"\n Press any key to exit...");
_getch();
return returnSample;
}
The idea is that in C# you call "CaptureSample()" and then the code attempts to capture a fingerprint scan. When it does a scan, a struct should be returned to C# that it can work with, holding:
Byte Buffer
Image Height
Image Width
Image Pixeldepth
But when I try to reference the DLL in my C# project I get the following error:
I have also tried to use the TlbImp.exe tool to make the DLL but to no avail. It tells me that the DLL is not a valid type library.
So I'm a bit lost here. I'm new to C++ so making an Interop/COM Component is not something I've done before nor make a DLL for use in C#.
You cannot reference a library of unmanaged code written in C++ in a .NET Project.
So to call code from such library you have to either use DllImport, or use a WrapperClass.
I referred to this answer : https://stackoverflow.com/a/574810/4546874.
Related
I can't seem to get a byte array in a C# program filled from a COM C++ program. The C# program includes a reference to the C++ DLL and is instantiated by:
_wiCore = new WebInspectorCoreLib.WICore();
Actual call
uint imageSize = *image byte count*; // set to size of image being retrieved
var arr = new byte[imageSize];
_wiCore.GetFlawImage(1, 0, ref imageSize, out arr);
C++ IDL:
[id(5)] HRESULT GetFlawImage([in] ULONG flawID, [in] USHORT station, [in, out] ULONG *pImageSize, [out] BYTE *pImageBytes);
This returns the image size correctly, but nothing in the array. I've also tried the signature (extra level of indirection for pImageBytes):
[id(5)] HRESULT GetFlawImage([in] ULONG flawID, [in] USHORT station, [in, out] ULONG *pImageSize, [out] BYTE **pImageBytes);
and in C# passing in an IntPtr but this returns the address of memory that contains the address of the image bytes, not the image bytes.
Any thoughts on what I'm doing wrong?
There are multiple ways to pass an array back from C++.
For example, you can use a raw byte array like you were trying to do. It works but it's not very practical from .NET because it's not a COM automation type which .NET loves.
So, let's say we have this .idl:
interface IBlah : IUnknown
{
HRESULT GetBytes([out] int *count, [out] unsigned char **bytes);
}
Here is a sample native implementation:
STDMETHODIMP CBlah::GetBytes(int* count, unsigned char** bytes)
{
if (!count || !bytes)
return E_INVALIDARG;
*count = numBytes;
*bytes = (unsigned char*)CoTaskMemAlloc(*count);
if (!*bytes)
return E_OUTOFMEMORY;
for (unsigned char i = 0; i < *count; i++)
{
(*bytes)[i] = i;
}
return S_OK;
}
And a sample C# calling code (note the .NET type lib importer doesn't know anything beyond pointers when it's not a COM automation type, so it just blindly defines the argument as an IntPtr):
var obj = (IBlah)Activator.CreateInstance(myType);
// we must allocate a pointer (to a byte array pointer)
var p = Marshal.AllocCoTaskMem(IntPtr.Size);
try
{
obj.GetBytes(out var count, p);
var bytesPtr = Marshal.ReadIntPtr(p);
try
{
var bytes = new byte[count];
Marshal.Copy(bytesPtr, bytes, 0, bytes.Length);
// here bytes is filled
}
finally
{
// here, we *must* use the same allocator than used in native code
Marshal.FreeCoTaskMem(bytesPtr);
}
}
finally
{
Marshal.FreeCoTaskMem(p);
}
Note: this won't work in out-of-process scenario as the .idl is not complete to support this, etc.
Or you can use a COM Automation type such as SAFEARRAY (or a wrapping VARIANT). which also would allow you to use it with other languages (such as VB/VBA, Scripting engines, etc.)
So, we could have this .idl:
HRESULT GetBytesAsArray([out] SAFEARRAY(BYTE)* array);
This sample native implementation (a bit more complex, as COM automation was not meant for C/C++, but for VB/VBA/Scripting object...):
STDMETHODIMP CBlah::GetBytesAsArray(SAFEARRAY** array)
{
if (!array)
return E_INVALIDARG;
// create a 1-dim array of UI1 (byte)
*array = SafeArrayCreateVector(VT_UI1, 0, numBytes);
if (!*array)
return E_OUTOFMEMORY;
unsigned char* bytes;
HRESULT hr = SafeArrayAccessData(*array, (void**)&bytes); // check errors
if (FAILED(hr))
{
SafeArrayDestroy(*array);
return hr;
}
for (unsigned char i = 0; i < numBytes; i++)
{
bytes[i] = i;
}
SafeArrayUnaccessData(*array);
return S_OK;
}
And the sample C# code is much simpler, as expected:
var obj = (IBlah)Activator.CreateInstance(myType);
obj.GetBytesAsArray(out var bytesArray);
issue:
As I increase the amount of data that is being processed inside of loop that is inside of CUDA kernel - it causes the app to abort!
exception:
ManagedCuda.CudaException: 'ErrorLaunchFailed: An exception occurred
on the device while executing a kernel. Common causes include
dereferencing an invalid device pointer and accessing out of bounds
shared memory.
question:
I would appreciate if somebody could shed a light on limitations that I am hitting with my current implementation and what exactly causes the app to crash..
Alternatively, I am attaching a full kernel code, for the sake if somebody could say how it can be re-modelled in such a way, when no exceptions are thrown. The idea is that kernel is accepting combinations and then performing calculations on the same set of data (in a loop). Therefore, loop calculations that are inside shall be sequential. The sequence in which kernel itself is executed is irrelevant. It's combinatorics problem.
Any bit of advice is welcomed.
code (Short version, which is enough to abort the app):
extern "C"
{
__device__ __constant__ int arraySize;
__global__ void myKernel(
unsigned char* __restrict__ output,
const int* __restrict__ in1,
const int* __restrict__ in2,
const double* __restrict__ in3,
const unsigned char* __restrict__ in4)
{
for (int row = 0; row < arraySize; row++)
{
// looping over sequential data.
}
}
}
In the example above if the arraySize is somewhere close to 50_000 then the app starts to abort. With the same kind of input parameters, if we override or hardcore the arraySize to 10_000 then the code finishes successfully.
code - kernel (full version)
#iclude <cuda.h>
#include "cuda_runtime.h"
#include <device_launch_parameters.h>
#include <texture_fetch_functions.h>
#include <builtin_types.h>
#define _SIZE_T_DEFINED
#ifndef __CUDACC__
#define __CUDACC__
#endif
#ifndef __cplusplus
#define __cplusplus
#endif
texture<float2, 2> texref;
extern "C"
{
__device__ __constant__ int width;
__device__ __constant__ int limit;
__device__ __constant__ int arraySize;
__global__ void myKernel(
unsigned char* __restrict__ output,
const int* __restrict__ in1,
const int* __restrict__ in2,
const double* __restrict__ in3,
const unsigned char* __restrict__ in4)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
if (index >= limit)
return;
bool isTrue = false;
int varA = in1[index];
int varB = in2[index];
double calculatable = 0;
for (int row = 0; row < arraySize; row++)
{
if (isTrue)
{
int idx = width * row + varA;
if (!in4[idx])
continue;
calculatable = calculatable + in3[row];
isTrue = false;
}
else
{
int idx = width * row + varB;
if (!in4[idx])
continue;
calculatable = calculatable - in3[row];
isTrue = true;
}
}
if (calculatable >= 0) {
output[index] = 1;
}
}
}
code - host (full version)
public static void test()
{
int N = 10_245_456; // size of an output
CudaContext cntxt = new CudaContext();
CUmodule cumodule = cntxt.LoadModule(#"kernel.ptx");
CudaKernel myKernel = new CudaKernel("myKernel", cumodule, cntxt);
myKernel.GridDimensions = (N + 255) / 256;
myKernel.BlockDimensions = Math.Min(N, 256);
// output
byte[] out_host = new byte[N]; // i.e. bool
var out_dev = new CudaDeviceVariable<byte>(out_host.Length);
// input
int[] in1_host = new int[N];
int[] in2_host = new int[N];
double[] in3_host = new double[50_000]; // change it to 10k and it's OK
byte[] in4_host = new byte[10_000_000]; // i.e. bool
var in1_dev = new CudaDeviceVariable<int>(in1_host.Length);
var in2_dev = new CudaDeviceVariable<int>(in2_host.Length);
var in3_dev = new CudaDeviceVariable<double>(in3_host.Length);
var in4_dev = new CudaDeviceVariable<byte>(in4_host.Length);
// copy input parameters
in1_dev.CopyToDevice(in1_host);
in2_dev.CopyToDevice(in2_host);
in3_dev.CopyToDevice(in3_host);
in4_dev.CopyToDevice(in4_host);
myKernel.SetConstantVariable("width", 2);
myKernel.SetConstantVariable("limit", N);
myKernel.SetConstantVariable("arraySize", in3_host.Length);
// exception is thrown here
myKernel.Run(out_dev.DevicePointer, in1_dev.DevicePointer, in2_dev.DevicePointer,in3_dev.DevicePointer, in4_dev.DevicePointer);
out_dev.CopyToHost(out_host);
}
analysis
My initial assumption was that I am having memory issues, however, according to VS debugger I am hitting a little above 500mb of data on a host environment. So I imagine that no matter how much data I copy to GPU - it shouldn't exceed 1Gb or even maximum 11Gb. Later on I have noticed that the crashing only is happening when the loop that is inside a kernel is having many records of data to process. It makes me to believe that I am hitting some kind of thread time-out limitations or something of that sort. Without a solid proof.
system
My system specs are 16Gb of Ram, and GeForce 1080 Ti 11Gb.
Using Cuda 9.1., and managedCuda version 8.0.22 (also tried with 9.x version from master branch)
edit 1: 26.04.2018 Just tested the same logic, but only on OpenCL. The code not only finished successfully, but also performs 1.5-5x time better than the CUDA, depending on the input parameter sizes:
kernel void Test (global bool* output, global const int* in1, global const int* in2, global const double* in3, global const bool* in4, const int width, const int arraySize)
{
int index = get_global_id(0);
bool isTrue = false;
int varA = in1[index];
int varB = in2[index];
double calculatable = 0;
for (int row = 0; row < arraySize; row++)
{
if (isTrue)
{
int idx = width * row + varA;
if (!in4[idx]) {
continue;
}
calculatable = calculatable + in3[row];
isTrue = false;
}
else
{
int idx = width * row + varB;
if (!in4[idx]) {
continue;
}
calculatable = calculatable - in3[row];
isTrue = true;
}
}
if (calculatable >= 0)
{
output[index] = true;
}
}
I don't really want to start OpenCL/CUDA war here. If there is anything I should be concerned about in my original CUDA implementation - please let me know.
edit: 26.04.2018. After following suggestions from the comment section I was able to increase the amount of data processed, before an exception is thrown, by 3x. I was able to achieve that by switching to .ptx generated in Release mode, rather than Debug mode. This improvement could be related to the fact that in Debug settings we also have Generate GPU Debug information set to Yes and other unnecessary settings that could affect performance.. I will now try to search info about how timings can be increased for kernel.. I am still not reaching the results of OpenCL, but getting close.
For CUDA file generation I am using VS2017 Community, CUDA 9.1 project, v140 toolset, build for x64 platform, post build events disabled, configuration type: utility. Code generation set to: compute_30,sm_30. I am not sure why it's not sm_70, for example. I don't have other options.
I have managed to improve the CUDA performance over OpenCL. And what's more important - the code can now finish executing without exceptions. The credits go to Robert Crovella. Thank You!
Before showing the results here are some specs:
CPU Intel i7 8700k 12 cores (6+6)
GPU GeForce 1080 Ti 11Gb
Here are my results (library/technology):
CPU parallel for loop: 607907 ms (default)
GPU (Alea, CUDA): 9905 ms (x61)
GPU (managedCuda, CUDA): 6272 ms (x97)
GPU (Coo, OpenCL): 8277 ms (x73)
THE solution 1:
The solution was to increase the WDDM TDR Delay from default 2 seconds to 10 seconds. As easy as that.
The solution 2:
I was able to squeeze out a bit more of performance by:
updating the compute_30,sm_30 settings to compute_61,sm_61 in CUDA project properties
using the Release settings instead of Debug
using .cubin file instead of .ptx
If anyone still wants to suggesst some ideas on how to improve the performance any further - please share them! I am opened to ideas. This question has been resolved, though!
p.s. if your display blinks in the same fashion as described here, then try increasing the delay as well.
I have a project with 3 parts:
Managed C# project with a callback which gives me a Bitmap (it should have PixelFormat = Format24bppRgb). I've tried several ways to convert the Bitmap into something I could pass to part 2, that's the last thing I've tried:
public int BufferCB(IntPtr pBuffer, int BufferLen)
{
byte[] aux = new byte[BufferLen];
Marshal.Copy(pBuffer, aux, 0, BufferLen);
String s_aux = System.Text.Encoding.Default.GetString(aux);
wrappedClassInstance.GetBitmap(s_aux);
}
Managed C++/CLI to wrap item 3:
int WrappedClass::GetBitmap(array<System::Byte>^ s_in) {
pin_ptr<unsigned char> pin = &s_in[0];
unsigned char* p = pin;
return privateImplementation->GetBitmap(p);
}
Unmanaged C++ application that uses OpenCV. I want to load this data into a Mat with:
Mat myMat;
int NativeClass::GetBitmap(unsigned char *s_in) {
// If there's no input:
if (!s_in) {
return -1;
}
/* h and w are defined elsewhere */
myMat = Mat(h, w, CV_8UC3, (void *)s_in, Mat::AUTO_STEP));
imwrite("test.bmp", myMat);
return 0;
}
When the the imwrite function is reached an exception is thrown: "System.Runtime.InteropServices.SEHException (0x80004005)". It doesn't say much, but I'm guessing the data I passed into the Mat got corrupted when I've marshalled it.
Before, I was trying to pass the data without a wrapper:
[DllImport("mydll.dll", ...)]
static extern void GetBitmap(IntPtr pBuffer, int h, int w);
void TestMethod(IntPtr pBuffer, int BufferLen)
{
// h and w defined elsewhere
// GetBitmap is essentially the same as in item 3.
GetBitmap(pBuffer, BufferLen, h, w);
}
and that worked (it saved the Bitmap into the file), but because the DLL stays attached until I kill the process that solution is not good enough for me. I also don't want to "mirror" the Mat class into my project, as I know Mat should accept data from some char*.
Please help, how can I do this? Am I doing wrong type conversions?
Thank you.
I've changed part 2 to memcpy unmanaged memory into native memory, just to be sure. But the real problem with my code was that everytime I wanted to get a new Mat, I'd call Mat constructor:
Mat myMat;
int MyClass::GetBitmap(unsigned char* s_in) {
myMat = Mat(...)
}
Instead, I did:
Mat myMat;
int MyClass::GetBitmap(unsigned char* s_in) {
Mat aux;
aux = Mat(...)
aux.copyTo(myMat);
aux.release();
}
... and now my code is working perfectly.
EDIT:
Removed some parts which used new Mat(...), it was a typo and it wouldn't compile because I'm using Mat, not Mat*.
I've been reading on this problem a lot before i decided to post it here. I have C dll that i need to access inside of a C# program. The DLL's code is pretty simple. As it's nothing more then a hook into biometrics driver (it's C because it's has to include lib and header files from driver for the code to work). Here is the code for the dll:
#include <stdio.h>
#include <windows.h>
#include <DPCN_TM.h>
__declspec(dllexport) unsigned char * ConvertPlatinumTemplateToDpTemplate(const unsigned char* inputData);
HRESULT Convert(
const unsigned char* inputData,
const size_t size,
DPCN_DATA_TYPE inputDataType,
DPCN_DATA_TYPE outputDataType,
DPCN_PURPOSE purpose,
unsigned char** ppbOutputData,
const void * outputParameters,
size_t * pcbData)
{
HRESULT hr = 0;
size_t cbData = 0;
if (FAILED(hr = DPCNConvertFingerprintData(inputData, size, inputDataType, purpose, NULL, outputDataType, outputParameters, NULL, &cbData))) {
return hr;
}
if (!(*ppbOutputData = (unsigned char *)malloc(cbData))) {
return DPCN_ERR_NO_MEMORY;
}
hr = DPCNConvertFingerprintData(inputData, size, inputDataType, purpose, NULL, outputDataType, outputParameters, *ppbOutputData, &cbData);
*pcbData = cbData;
return hr;
}
unsigned char * ConvertPlatinumTemplateToDpTemplate(const unsigned char* inputData) {
HRESULT hr = 0;
const size_t inputSize = sizeof(inputData);
DPCN_DATA_TYPE inputDataType = DPCN_DT_DP_TEMPLATE;
DPCN_DATA_TYPE outputDataType = DPCN_DT_DP_PLATINUM_TEMPLATE;
unsigned char *pbOutputData = NULL;
size_t cbData = 0;
hr = Convert(inputData, inputSize, inputDataType, outputDataType, DPCN_PURPOSE_IDENTIFICATION, &pbOutputData, NULL, &cbData);
return pbOutputData;
}
As you can see the contents of the DLL is pretty straight forward. From the code you can see I'm needing to access this function inside of the C# program.
unsigned char * ConvertPlatinumTemplateToDpTemplate(const unsigned char* inputData);
Now in my C# code I have done this:
[DllImport(#"path_to_dll\DPFPTemplateConvert.dll")]
public extern byte[] ConvertPlatinumTemplateToDpTemplate(byte[] inputData);
When I call the function I end up getting this error:
A first chance exception of type 'System.Runtime.InteropServices.MarshalDirectiveException' occurred in DLLImportTest.exe
Additional information: Cannot marshal 'return value': Invalid managed/unmanaged type combination.
What am I doing wrong?
An unsigned char * cannot be converted to a .NET byte array, for one simple reason: what should be the length of that array?
And even then, it's a bad idea to pass a pointer out of your function, if that pointer is pointing to memory allocated by that function. Who will release this memory?
You should have the .NET side allocate the byte[] for the result, and pass it into the function.
If the .NET side does not know beforehand how big the allocated array needs to be, use a callback as explained here: http://blog.getpaint.net/2012/04/30/marshaling-native-arrays-back-as-managed-arrays-without-copying/
I'm using a third party library to render an image to a GDI DC and I need to ensure that any text is rendered without any smoothing/antialiasing so that I can convert the image to a predefined palette with indexed colors.
The third party library i'm using for rendering doesn't support this and just renders text as per the current windows settings for font rendering. They've also said that it's unlikely they'll add the ability to switch anti-aliasing off any time soon.
The best work around I've found so far is to call the third party library in this way (error handling and prior settings checks ommitted for brevity):
private static void SetFontSmoothing(bool enabled)
{
int pv = 0;
SystemParametersInfo(Spi.SetFontSmoothing, enabled ? 1 : 0, ref pv, Spif.None);
}
// snip
Graphics graphics = Graphics.FromImage(bitmap)
IntPtr deviceContext = graphics.GetHdc();
SetFontSmoothing(false);
thirdPartyComponent.Render(deviceContext);
SetFontSmoothing(true);
This obviously has a horrible effect on the operating system, other applications flicker from cleartype enabled to disabled and back every time I render the image.
So the question is, does anyone know how I can alter the font rendering settings for a specific DC?
Even if I could just make the changes process or thread specific instead of affecting the whole operating system, that would be a big step forward! (That would give me the option of farming this rendering out to a separate process- the results are written to disk after rendering anyway)
EDIT: I'd like to add that I don't mind if the solution is more complex than just a few API calls. I'd even be happy with a solution that involved hooking system dlls if it was only about a days work.
EDIT: Background Information
The third-party library renders using a palette of about 70 colors. After the image (which is actually a map tile) is rendered to the DC, I convert each pixel from it's 32-bit color back to it's palette index and store the result as an 8bpp greyscale image. This is uploaded to the video card as a texture. During rendering, I re-apply the palette (also stored as a texture) with a pixel shader executing on the video card. This allows me to switch and fade between different palettes instantaneously instead of needing to regenerate all the required tiles. It takes between 10-60 seconds to generate and upload all the tiles for a typical view of the world.
EDIT: Renamed GraphicsDevice to Graphics
The class GraphicsDevice in the previous version of this question is actually System.Drawing.Graphics. I had renamed it (using GraphicsDevice = ...) because the code in question is in the namespace MyCompany.Graphics and the compiler wasn't able resolve it properly.
EDIT: Success!
I even managed to port the PatchIat function below to C# with the help of Marshal.GetFunctionPointerForDelegate. The .NET interop team really did a fantastic job! I'm now using the following syntax, where Patch is an extension method on System.Diagnostics.ProcessModule:
module.Patch(
"Gdi32.dll",
"CreateFontIndirectA",
(CreateFontIndirectA original) => font =>
{
font->lfQuality = NONANTIALIASED_QUALITY;
return original(font);
});
private unsafe delegate IntPtr CreateFontIndirectA(LOGFONTA* lplf);
private const int NONANTIALIASED_QUALITY = 3;
[StructLayout(LayoutKind.Sequential)]
private struct LOGFONTA
{
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
public unsafe fixed sbyte lfFaceName [32];
}
Unfortunately you cant. The ability to control font anti aliasing is done per font. The GDI call CreateFontIndirect processes members of the LOGFONT struct to determine if its allowed to use cleartype, regular or no anti aliasing.
There are, as you noted, system wide settings. Unfortunately, changing the systemwide setting is pretty much the only (documented) way to downgrade the quality of font rendering on a DC if you cannot control the contents of the LOGFONT.
This code is not mine. Is unmanaged C. And will hook any function imported by a dll or exe file if you know its HMODULE.
#define PtrFromRva( base, rva ) ( ( ( PBYTE ) base ) + rva )
/*++
Routine Description:
Replace the function pointer in a module's IAT.
Parameters:
Module - Module to use IAT from.
ImportedModuleName - Name of imported DLL from which
function is imported.
ImportedProcName - Name of imported function.
AlternateProc - Function to be written to IAT.
OldProc - Original function.
Return Value:
S_OK on success.
(any HRESULT) on failure.
--*/
HRESULT PatchIat(
__in HMODULE Module,
__in PSTR ImportedModuleName,
__in PSTR ImportedProcName,
__in PVOID AlternateProc,
__out_opt PVOID *OldProc
)
{
PIMAGE_DOS_HEADER DosHeader = ( PIMAGE_DOS_HEADER ) Module;
PIMAGE_NT_HEADERS NtHeader;
PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;
UINT Index;
assert( Module );
assert( ImportedModuleName );
assert( ImportedProcName );
assert( AlternateProc );
NtHeader = ( PIMAGE_NT_HEADERS )
PtrFromRva( DosHeader, DosHeader->e_lfanew );
if( IMAGE_NT_SIGNATURE != NtHeader->Signature )
{
return HRESULT_FROM_WIN32( ERROR_BAD_EXE_FORMAT );
}
ImportDescriptor = ( PIMAGE_IMPORT_DESCRIPTOR )
PtrFromRva( DosHeader,
NtHeader->OptionalHeader.DataDirectory
[ IMAGE_DIRECTORY_ENTRY_IMPORT ].VirtualAddress );
//
// Iterate over import descriptors/DLLs.
//
for ( Index = 0;
ImportDescriptor[ Index ].Characteristics != 0;
Index++ )
{
PSTR dllName = ( PSTR )
PtrFromRva( DosHeader, ImportDescriptor[ Index ].Name );
if ( 0 == _strcmpi( dllName, ImportedModuleName ) )
{
//
// This the DLL we are after.
//
PIMAGE_THUNK_DATA Thunk;
PIMAGE_THUNK_DATA OrigThunk;
if ( ! ImportDescriptor[ Index ].FirstThunk ||
! ImportDescriptor[ Index ].OriginalFirstThunk )
{
return E_INVALIDARG;
}
Thunk = ( PIMAGE_THUNK_DATA )
PtrFromRva( DosHeader,
ImportDescriptor[ Index ].FirstThunk );
OrigThunk = ( PIMAGE_THUNK_DATA )
PtrFromRva( DosHeader,
ImportDescriptor[ Index ].OriginalFirstThunk );
for ( ; OrigThunk->u1.Function != NULL;
OrigThunk++, Thunk++ )
{
if ( OrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG )
{
//
// Ordinal import - we can handle named imports
// ony, so skip it.
//
continue;
}
PIMAGE_IMPORT_BY_NAME import = ( PIMAGE_IMPORT_BY_NAME )
PtrFromRva( DosHeader, OrigThunk->u1.AddressOfData );
if ( 0 == strcmp( ImportedProcName,
( char* ) import->Name ) )
{
//
// Proc found, patch it.
//
DWORD junk;
MEMORY_BASIC_INFORMATION thunkMemInfo;
//
// Make page writable.
//
VirtualQuery(
Thunk,
&thunkMemInfo,
sizeof( MEMORY_BASIC_INFORMATION ) );
if ( ! VirtualProtect(
thunkMemInfo.BaseAddress,
thunkMemInfo.RegionSize,
PAGE_EXECUTE_READWRITE,
&thunkMemInfo.Protect ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// Replace function pointers (non-atomically).
//
if ( OldProc )
{
*OldProc = ( PVOID ) ( DWORD_PTR )
Thunk->u1.Function;
}
#ifdef _WIN64
Thunk->u1.Function = ( ULONGLONG ) ( DWORD_PTR )
AlternateProc;
#else
Thunk->u1.Function = ( DWORD ) ( DWORD_PTR )
AlternateProc;
#endif
//
// Restore page protection.
//
if ( ! VirtualProtect(
thunkMemInfo.BaseAddress,
thunkMemInfo.RegionSize,
thunkMemInfo.Protect,
&junk ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
return S_OK;
}
}
//
// Import not found.
//
return HRESULT_FROM_WIN32( ERROR_PROC_NOT_FOUND );
}
}
//
// DLL not found.
//
return HRESULT_FROM_WIN32( ERROR_MOD_NOT_FOUND );
}
You would call this from your code by doing something like (I haven't checked that this in any way compiles :P):
Declare a pointer type to the funciton you want to hook:
typedef FARPROC (WINAPI* PFNCreateFontIndirect)(LOGFONT*);
Implement a hook function
static PFNCreateFontIndirect OldCreateFontIndirect = NULL;
WINAPI MyNewCreateFontIndirectCall(LOGFONT* plf)
{
// do stuff to plf (probably better to create a copy than tamper with passed in struct)
// chain to old proc
if(OldCreateFontIndirect)
return OldCreateFontIndirect(plf);
}
Hook the function sometime during initialization
HMODULE h = LoadLibrary(TEXT("OtherDll"));
PatchIat(h, "USER32.DLL", "CreateFontIndirectW", MyNewCreateFontIndirectProc, (void**)&OldCreateFontIndirectProc);
Of course, if the module you are hooking exists in .NET land its very unclear as to where the CreateFontIndirect call is going to originate from. mscoree.dll? The actual module you call? Good luck I guess :P
As requested, I have packaged up the code I wrote to solve this problem and placed it in a github repository: http://github.com/jystic/patch-iat
It looks like a lot of code because I had to reproduce all the Win32 structures for this stuff to work, and at the time I chose to put each one in its own file.
If you want to go straight to the meat of of the code it's in: ImportAddressTable.cs
It's licensed very freely and is for all intents and purposes, public domain, so feel free to use it in any project that you like.
Do you need more colours than black and white on your fonts?
If not, you could make your bitmap object a 1 bit per pixel image (Format1bppIndexed?).
The system will probably not smooth font rendering on 1bpp images.
is the GraphicsDevice Class a 3rd party class?
the way i would do this is:
Graphics g = Graphics.FromImage(memImg);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
or in your case:
GraphicsDevice graphics = GraphicsDevice.FromImage(bitmap)
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
if the GraphicsDevice Class inherits the Graphics class (otherwise try using the Graphics class?)