How to find supported Direct3D Feature Levels - c#

I would like to find which Direct 3D Feature Levels (shown here) are supported in my C# UWP program.
Note: I am doing this in trying to research possible answers to my question How do I know, in code, if RadialGradientBrush is not being shown correctly?
In C++, there is a call to D3D11CreateDevice that might possibly be useful, but there does not appear to be a C# equivalent.
Or, even better, might be the ID3D11Device, which has a GetFeatureLevel() call. Again, these are C++ items.

I figured this out:
C++ DLL:
#include "pch.h"
#include <wrl.h>
#include <d3d11_2.h>
#include "BKGraphicsDLL.h"
using Microsoft::WRL::ComPtr;
extern "C"
{
__declspec(dllexport) D3D_FEATURE_LEVEL GetGraphicsFeatureLevel()
{
HRESULT hr = E_FAIL;
D3D_FEATURE_LEVEL MaxSupportedFeatureLevel = D3D_FEATURE_LEVEL_9_1;
D3D_FEATURE_LEVEL FeatureLevels[] = {
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
hr = D3D11CreateDevice(
NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
0,
FeatureLevels,
ARRAYSIZE(FeatureLevels),
D3D11_SDK_VERSION,
NULL,
&MaxSupportedFeatureLevel,
NULL
);
return MaxSupportedFeatureLevel;
}
}
An enum and a NativeMethods class to access the DLL in my MainPage C#:
public enum D3DFeatureLevel
{
D3D_FEATURE_LEVEL_9_1 = 0x9100,
D3D_FEATURE_LEVEL_9_2 = 0x9200,
D3D_FEATURE_LEVEL_9_3 = 0x9300,
D3D_FEATURE_LEVEL_10_0 = 0xa000,
D3D_FEATURE_LEVEL_10_1 = 0xa100,
D3D_FEATURE_LEVEL_11_0 = 0xb000,
D3D_FEATURE_LEVEL_11_1 = 0xb100,
D3D_FEATURE_LEVEL_12_0 = 0xc000,
D3D_FEATURE_LEVEL_12_1 = 0xc100
};
// used to get Graphics capabilities
internal static class NativeMethods
{
[DllImport("BKGraphicsDLL.dll", ExactSpelling = true)]
public static extern D3DFeatureLevel GetGraphicsFeatureLevel();
}
Wherever I need to Feature Level in my code, uses this line:
D3DFeatureLevel maxFeatureLevel = NativeMethods.GetGraphicsFeatureLevel();

Related

How to get content of array from C++ dll in C#

I want to use functions from DLL in C++ with C#.
I store string data in a vector.
My C++ file contains:
#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern "C" __declspec(dllexport) std::vector<std::string> GetProduct();
std::vector<std::string> GetProduct()
{
std::vector<std::string> vectProduct;
vectProduct.push_back("Citroen");
vectProduct.push_back("C5");
vectProduct.push_back("MOP-C5");
return vectProduct;
}
In C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices;
namespace ConsoleApplication
{
class Program
{
[DllImport("ProductLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern StringBuilder GetProduct();
static void Main(string[] args)
{
StringBuilder vectProduct_impl = GetProduct();
}
}
}
I don't know how to continue to browse the array in c#.
I don't know if the use of vector is optimal. if you have other solution I'm ready.
Please help.
My favourite way for passing an array of strings C++-->C# is by using a delegate.
C#:
// If possible use UnmanagedType.LPUTF8Str
// or under Windows rewrite everything to use
// wchar_t, std::wstring and UnmanagedType.LPWStr
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void AddAnsi([MarshalAs(UnmanagedType.LPStr)] string str);
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void TestReturnArrayStrings(AddAnsi add);
and then
var lst = new List<string>();
TestReturnArrayStrings(lst.Add);
foreach (string str in lst)
{
Console.WriteLine(str);
}
And C++:
#include <string>
#include <vector>
extern "C"
{
__declspec(dllexport) void TestReturnArrayStrings(void (add)(const char* pstr))
{
std::string str1 = "Hello";
std::string str2 = "World";
add(str1.data());
add(str2.data());
// Example with std::vector
add("--separator--"); // You can even use C strings
std::vector<std::string> v = { "Foo", "Bar" };
// for (std::vector<std::string>::iterator it = v.begin(); it != v.end(); ++it)
for (std::vector<std::string>::const_iterator it = v.begin(); it != v.end(); ++it)
{
add(it->data());
}
add("--separator--"); // You can even use C strings
// With C++ 11
// for (auto& it: v)
for (const auto& it: v)
{
add(it.data());
}
}
}
Here the "trick" is that C# passes to C++ a delegate to the List<string>.Add() method, and C++ "fills" directly the C# List<>. The memory managed by C++ remains in the C++ side, the memory managed by the C# remains in the C# side. No problems of cross-memory ownership. As you can imagine, it is quite easy to expand the "trick" to any other .Add() method, like HashSet<string>, or Dictionary<string, string>.
As a sidenote, I've created a github with many examples about marshaling between C/C++ and C# (both .NET Framework and .NET Core/5.0).
One way to do it is to use COM's SAFEARRAY structure as it's supported by .NET (the .NET Allocator used by P/Invoke is the COM allocator), including most of associated sub types, like BSTR.
So, in C/C++, you can define this:
extern "C" __declspec(dllexport) LPSAFEARRAY GetProduct();
LPSAFEARRAY GetProduct()
{
LPSAFEARRAY psa = SafeArrayCreateVector(VT_BSTR, 0, 3);
LONG index = 0;
// _bstr_t is a smart class that frees allocated memory automatically
// it needs #include <comdef.h>
// but you can also use raw methods like SysAllocString / SysFreeString
_bstr_t s0(L"Citroen"); // could use "Citroen" if you really want ANSI strings
// note SafeArrayPutElement does a copy internally
SafeArrayPutElement(psa, &index, s0.GetBSTR());
index++;
_bstr_t s1(L"C5");
SafeArrayPutElement(psa, &index, s1.GetBSTR());
index++;
_bstr_t s2(L"MOP - C5");
SafeArrayPutElement(psa, &index, s2.GetBSTR());
index++;
return psa;
}
And in C#, you can define this:
[DllImport("ProductLibrary.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.SafeArray)]
public static extern string[] GetProduct();

type conversion problem for marshaling datatypes from C# to C++

I'm currently working on an C# (.NET Framework 4.7.2) application using some business logic from an unmanaged C++ library. I try to pass data (interop) back and forth from C# to C++. I may not use C++/CLI, no common language runtime allowed in my project.
It works fine for int. Unfortunately as soon as I try to send another datatype I'm getting an conversion error e.g. float 4.2f becomes 1 and string "fourtytwo" turns into -1529101360.
My C# code looks like this:
// works fine, creates an instance of TestClass
var test = TestProxy.Wrapper_Create("test");
// int, works fine, a = 42
var a = TestProxy.TryInt(test, 42);
// float, problem, b = 1
var b = TestProxy.TryFloat(test, 4.2f);
// string, problem, c = -159101360
var c = TestProxy.TryString(test, "fourtytwo");
My C# Interop Proxy class to call the native (unmanaged) C++ code looks like this:
public static class TestProxy
{
private const string coreDLL = "test.core.dll";
[DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrapper_Create(string name);
[DllImport(coreDLL, EntryPoint = "?TryInt#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryInt(IntPtr instance, int n);
[DllImport(coreDLL, EntryPoint = "?TryFloat#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryFloat(IntPtr instance, float n);
[DllImport(coreDLL, EntryPoint = "?TryString#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryString(IntPtr instance, string n);
}
My native (unmanaged) C++ looks like that:
the header file:
#ifdef TESTCORE_EXPORTS
#define TESTCORE_API __declspec(dllexport)
#endif
#pragma once
extern "C"
{
class TESTCORE_API TestClass
{
private:
char* name;
public:
TestClass(char*);
int TryInt(int);
float TryFloat(float);
char* TryString(char*);
};
TESTCORE_API TestClass* Wrapper_Create(char* name);
}
the implementation file:
#include "stdafx.h"
#include "TESTCore.h"
TestClass::TestClass(char* n)
{
name = n;
}
int TestClass::TryInt(int n)
{
return n; // works fine
}
float TestClass::TryFloat(float n)
{
return n; // something goes wrong here
}
char* TestClass::TryString(char* n)
{
return n; // something goes wrong here
}
extern "C"
{
TESTCORE_API TestClass * Wrapper_Create(char* name)
{
return new TestClass(name);
}
TESTCORE_API int TryInt(TestClass * instance, int n)
{
if (instance != NULL)
{
return instance->TryInt(n);
}
}
TESTCORE_API float TryFloat(TestClass * instance, float n)
{
if (instance != NULL)
{
return instance->TryFloat(n);
}
}
TESTCORE_API char* TryString(TestClass * instance, char* n)
{
if (instance != NULL)
{
return instance->TryString(n);
}
}
}
Do you know how to correctly marshal float, string from C# to C++ and back?
Thank you!
C++ doesn't have standard ABI. It's rarely a good idea to use C++ classes across DLLs, even when you have same language on both sides.
There're better ways.
Replace your __thiscall class methods with global functions, cdecl or stdcall whichever you like (but note C# and C++ have different defaults, if you'll do nothing C++ will use cdecl, C# will import as stdcall). You can pass "this" pointer of the class in the first argument, IntPtr in C#, just like you're doing now. Also if you'll write extern "C" or use a module definition file, they will have human-readable names.
If you want objects, use COM. Declare an interface that inherits from IUnknown, implement it in C++ (I usually use ATL), and export a global function to create an instance of that object (2 lines in ATL, CComObject<T>::CreateInstance followed by AddRef). No need to register, type libraries, you just need to implement IUnknown (but see this if you want to use them from multiple threads)
Update: strings are indeed harder. Apply [MarshalAs(UnmanagedType.LPTStr)] to the argument. Apply [return: MarshalAs(UnmanagedType.LPTStr)] to the function. Specify PreserveSig=true in your DllImport. Finally, modify the C++ code to return a copy of the string, i.e. call strlen then CoTaskMemAlloc (don't forget about the '\0') then strcpy.
Easier way to deal with strings is like this:
HRESULT TryString( TestClass *instance, BSTR i, BSTR *o )
At least there're CComBSTR and _bstr_t built-in classes to deal with memory management.

C# call to a C DLL is only partly functional

I am learning C# from my C++/CLR background by rewriting a sample C++/CLR project in C#.
The project is a simple GUI (using Visual Studio/ Windows Forms) that performs calls to a DLL written in C (in fact, in NI LabWindows/CVI but this is just ANSI C with custom libraries). The DLL is not written by me and I cannot perform any changes to it because it is also used elsewhere.
The DLL contains functions to make an RFID device perform certain functions (like reading/writing RFID tag etc). In each of these functions, there is always a call to another function that performs writing to a log file. If the log file is not present, it is created with a certain header and then data is appended.
The problem is: the C++/CLR project works fine.
But, in the C# one, the functions work (the RFID tag is correctly written/read etc.) but there is no activity regarding the log file!
The declarations for DLL exports look like this (just one example, there are more of them, of course):
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
The save_Logdatei function is called during execution of Magnetfeld_einschalten like this:
save_Logdatei(path_Logfile_RFID, "Magnetfeld_einschalten", "OK");
In the C++/CLR project, I declared the function like this:
#ifdef __cplusplus
extern "C" {
#endif
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
#ifdef __cplusplus
}
#endif
then a simple call to the function is working.
In the C# project, the declaration goes like:
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
and, as I said, although the primary function is working (in this case, turning on the magnetic field of the RFID device), the logging is never done (so, the internal DLL call to save_Logdatei is not executing correctly).
The relevant code in the Form constructor is the following:
pathapp = Application.StartupPath;
pathlog = string.Format("{0}\\{1:yyyyMMdd}_RFID_Logdatei.dat", pathapp, DateTime.Now);
//The naming scheme for the log file.
//Normally, it's autogenerated when a `save_Logdatei' call is made.
Magnetfeld_einschalten(pathlog);
What am I missing? I have already tried using unsafe for the DLL method declaration - since there is a File pointer in save_Logdatei - but it didn't make any difference.
===================================EDIT==================================
Per David Heffernan's suggestion, i have tried to recreate the problem in an easy to test way. For this, i have created a very simple DLL ("test.dll") and I have stripped it completely from the custom CVI libaries, so it should be reproducible even without CVI. I have uploaded it here. In any case, the code of the DLL is:
#include <stdio.h>
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300]);
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[]);
int __declspec(dllexport) __stdcall Magnetfeld_einschalten(char path_Logfile_RFID[300])
{
save_Logdatei(path_Logfile_RFID, "Opening Magnet Field", "Success");
return 0;
}
int save_Logdatei(char path_Logdatei[], char Funktion[], char Mitteilung[])
{
FILE *fp; /* File-Pointer */
char line[700]; /* Zeilenbuffer */
char path[700];
sprintf(path,"%s\\20160212_RFID_Logdatei.dat",path_Logdatei);
fp = fopen (path, "a");
sprintf(line, "Just testing");
sprintf(line,"%s %s",line, Funktion);
sprintf(line,"%s %s",line, Mitteilung);
fprintf(fp,"%s\n",line);
fclose(fp);
return 0;
}
The C# code is also stripped down and the only thing i have added to the standard Forms project, is Button 1 (and the generated button click as can be seen). The code is this:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TestDLLCallCSharp
{
public partial class Form1 : Form
{
public int ret;
public string pathapp;
public string pathlog;
[DllImport("test", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(string path_Logfile_RFID);
public Form1()
{
pathapp = #"C:\ProgramData\test";
pathlog = string.Format("{0}\\20160212_RFID_Logdatei.dat", pathapp);
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
ret = Magnetfeld_einschalten(pathlog);
}
}
}
As can be seen, I have avoided using an automatic naming scheme for the log file (normally i use the date) and in both the dll and the C# code, the log file is "20160212_RFID_Logdatei.dat". I have also avoided using the app path as the directory where to put the log file and instead I have opted for a folder named test i created in ProgramData
Again, no file is created at all
This looks like a simple typo in your calling code. Instead of:
ret = Magnetfeld_einschalten(pathlog);
you mean to write:
ret = Magnetfeld_einschalten(pathapp);
In the C# code, these two strings have the following values:
pathapp == "C:\ProgramData\\test"
pathlog == "C:\ProgramData\\test\\20160212_RFID_Logdatei.dat"
When you pass pathlog to the unmanaged code it then does the following:
sprintf(path,"%s\\20160212_RFID_Logdatei.dat",path_Logdatei);
which sets path to be
path == "C:\\ProgramData\\test\\20160212_RFID_Logdatei.dat\\20160212_RFID_Logdatei.dat"
In other words you are appending the file name to the path twice instead of once.
An extensive overview for P/Invoke in C# is given in Platform Invoke Tutorial - MSDN Library.
The problematic bit is you need to pass a fixed char array rather than the standard char*. This is covered in Default Marshalling for Strings.
The gist is, you need to construct a char[300] from your C# string and pass that rather than the string.
For this case, two ways are specified:
pass a StringBuilder instead of a string initialized to the specified length and with your data (I omitted non-essential parameters):
[DllImport("MyDLL.dll", ExactSpelling = true)]
private static extern int Magnetfeld_einschalten(
[MarshalAs(UnmanagedType.LPStr)] StringBuilder path_Logfile_RFID);
<...>
StringBuilder sb = new StringBuilder(pathlog,300);
int result = Magnetfeld_einschalten(sb);
In this case, the buffer is modifiable.
define a struct with the required format and manually convert your string to it:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
struct Char300 {
[MarshalAs(UnmanagedType.ByValTStr,SizeConst=300)]String s;
}
[DllImport("MyDLL.dll")]
private static extern int Magnetfeld_einschalten(Char300 path_Logfile_RFID);
<...>
int result = Magnetfeld_einschalten(new Char300{s=pathlog});
You can define an explicit or implicit cast routine to make this more straightforward.
According to UnmanagedType docs, UnmanagedType.ByValTStr is only valid in structures so it appears to be impossible to get the best of both worlds.
The String is in Unicode format, convert it to byte[]
Encoding ec = Encoding.GetEncoding(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);
byte[] bpathlog = ec.GetBytes(pathlog);
and change parameter type to byte[]
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Magnetfeld_einschalten", CharSet = CharSet.Ansi, ExactSpelling = false)]
private static extern int Magnetfeld_einschalten(byte[] path_Logfile_RFID);
For me it is working
JSh

Reverse PInvoke from native C++

I am currently trying to call a function from a C# DLL from an unmanaged C++ app.
After searching for hours on the web and SO, I found I have a few options.
I can use COM, DllExport, or use reverse PInvoke with delegates. The last sounded most appealing to me, so after searching SO I ended up here.
It states that the article shows how to use reverse PInvoke, but it looks like the C# code has to first import the C++ Dll, before it can be used.
I need to be able to use C++ to call my C# Dll functions, without running a C# app first.
Maybe reverse PInvoke isn't the way to do it, but I am quite inexperienced when it comes to low level stuff, so any pointers or tips on how to do this would be great.
The code in the link is
C#
using System.Runtime.InteropServices;
public class foo
{
public delegate void callback(string str);
public static void callee(string str)
{
System.Console.WriteLine("Managed: " +str);
}
public static int Main()
{
caller("Hello World!", 10, new callback(foo.callee));
return 0;
}
[DllImport("nat.dll",CallingConvention=CallingConvention.StdCall)]
public static extern void caller(string str, int count, callback call);
}
C++
#include <stdio.h>
#include <string.h>
typedef void (__stdcall *callback)(wchar_t * str);
extern "C" __declspec(dllexport) void __stdcall caller(wchar_t * input, int count, callback call)
{
for(int i = 0; i < count; i++)
{
call(input);
}
}
Meh, just spin up your own CLR host and run what you need to:
#include <mscoree.h>
#include <stdio.h>
#pragma comment(lib, "mscoree.lib")
void Bootstrap()
{
ICLRRuntimeHost *pHost = NULL;
HRESULT hr = CorBindToRuntimeEx(L"v4.0.30319", L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pHost);
pHost->Start();
printf("HRESULT:%x\n", hr);
// target method MUST be static int method(string arg)
DWORD dwRet = 0;
hr = pHost->ExecuteInDefaultAppDomain(L"c:\\temp\\test.dll", L"Test.Hello", L"SayHello", L"Person!", &dwRet);
printf("HRESULT:%x\n", hr);
hr = pHost->Stop();
printf("HRESULT:%x\n", hr);
pHost->Release();
}
int main()
{
Bootstrap();
}

Unable to find an entry point when calling C++ dll in C#

I am trying to learn P/Invoke, so I created a simple dll in C++
KingFucs.h:
namespace KingFuncs
{
class KingFuncs
{
public:
static __declspec(dllexport) int GiveMeNumber(int i);
};
}
KingFuns.cpp:
#include "KingFuncs.h"
#include <stdexcept>
using namespace std;
namespace KingFuncs
{
int KingFuncs::GiveMeNumber(int i)
{
return i;
}
}
So it does compile, then I copied this dll into my WPF's debug folder, with code:
[DllImport("KingFuncDll.dll", EntryPoint = "GiveMeNumber", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern int GiveMeNumber(
int i
);
And calling it in button click:
private void Button_Click(object sender, RoutedEventArgs e)
{
int num = GiveMeNumber(123);
}
But it gives me exception:
Unable to find an entry point named 'GiveMeNumber' in DLL
'KingFuncDll.dll'.
Really.... what have I done wrong... It obviously able to find the DLL, otherwise would be another exception. But my method name is exactly the same.... I can't think of other reason.
You need to use extern "C" when you export your function so that you suppress C++ name mangling. And you also should not try to p/invoke to members of a class. Use free functions instead:
extern "C" {
__declspec(dllexport) int GiveMeNumber(int i)
{
return i;
}
}
On the managed side your DllImport attribute is all wrong. Don't use SetLastError which is for Win32 APIs only. Don't bother setting CharSet if there are not text parameters. No need for ExactSpelling. And the calling convention is presumably Cdecl.
[DllImport("KingFuncDll.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int GiveMeNumber(int i);
The problem is that you are declaring the C++ "function" inside a C++ class and are telling P/Invoke to use StdCall.
Try to declare a C++ function outside a class and and export it like you did. Then your code should work.
If you really must have a C++ function inside a class, take a look at CallingConvention.ThisCall. But then you are responsible for creating your unmanaged class instance and pass it as the first parameter of your P/Invoke call
The entry point name of a dll file is given in .exp file which is found in debug folder where other source files are present. If dumpbin doesn't work you can try this.

Categories

Resources