I've been struggling to get PowerShell to control SetWindowCompositionAttribute via Pinvoke (to add Windows 10 Acrylic Blur effect to the Windows taskbar and other windows via PowerShell).
I am comfortable sending simple parameters like HWND and respective values (as shown in two of my examples below). However, I’m not sure how to enhance the PowerShell code below so it can also handle sending parameters packed in a struct. See SetWindowCompositionAttribute.
I've seen code examples in Delphi and AutoIt (instead of PowerShell) that do this. Unfortunately, I couldn't figure them out.
Anyway, below is my working PowerShell code and usage examples that demonstrate basic interaction with the Windows API. I'm hoping someone could help me enhance this code (with a couple of examples) to also control various features offered by SetWindowCompositionAttribute.
Ultimately, I'd like to be able to specify hWnd, Windows class name, and/or Window title name of the component I wish to add blur too; and, specify the amount of blur/transparency if possible.
Working Functions and example Usage:
$script:nativeMethods = #();
function Register-NativeMethod([string]$dll, [string]$methodSignature) {
$script:nativeMethods += [PSCustomObject]#{ Dll = $dll; Signature = $methodSignature; }
}
function Add-NativeMethods() {
$nativeMethodsCode = $script:nativeMethods | % { "
[DllImport(`"$($_.Dll)`")]
public static extern $($_.Signature);
" }
Add-Type #"
using System;
using System.Runtime.InteropServices;
public class NativeMethods {
$nativeMethodsCode
}
"#
}
#Build class and registers them:
Add-NativeMethods
#Example 1:
Register-NativeMethod "user32.dll" "bool SetForegroundWindow(IntPtr hWnd)"
[NativeMethods]::SetForegroundWindow((Get-Process -name notepad).MainWindowHandle)
#Example 2:
Register-NativeMethod "user32.dll" "bool ShowWindow(IntPtr hWnd, int nCmdShow)"
[NativeMethods]::ShowWindow((Get-Process -name notepad).MainWindowHandle, 0)
EDIT: Added an option for "acrylic" blur with color tinting. It seems to be a bit slow though when moving the windows about.
Is this what you're after?
Window before running function:
Window after running function (Set-WindowBlur -MainWindowHandle 853952 -Enable):
Main code:
$SetWindowComposition = #'
[DllImport("user32.dll")]
public static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
[StructLayout(LayoutKind.Sequential)]
public struct WindowCompositionAttributeData {
public WindowCompositionAttribute Attribute;
public IntPtr Data;
public int SizeOfData;
}
public enum WindowCompositionAttribute {
WCA_ACCENT_POLICY = 19
}
public enum AccentState {
ACCENT_DISABLED = 0,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4
}
[StructLayout(LayoutKind.Sequential)]
public struct AccentPolicy {
public AccentState AccentState;
public int AccentFlags;
public int GradientColor;
public int AnimationId;
}
'#
Add-Type -MemberDefinition $SetWindowComposition -Namespace 'WindowStyle' -Name 'Blur'
function Set-WindowBlur {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[int]
$MainWindowHandle,
[Parameter(ParameterSetName='Enable',Mandatory)]
[switch]
$Enable,
[Parameter(ParameterSetName='Acrylic',Mandatory)]
[switch]
$Acrylic,
# Color in BGR hex format (for ease, will just be used as an integer), eg. for red use 0x0000FF
[Parameter(ParameterSetName='Acrylic')]
[ValidateRange(0x000000, 0xFFFFFF)]
[int]
$Color= 0x000000,
# Transparency 0-255, 0 full transparency and 255 is a solid $Color
[Parameter(ParameterSetName='Acrylic')]
[ValidateRange(0, 255)]
[int]
$Transparency = 80,
[Parameter(ParameterSetName='Disable',Mandatory)]
[switch]
$Disable
)
$Accent = [WindowStyle.Blur+AccentPolicy]::new()
switch ($PSCmdlet.ParameterSetName) {
'Enable' {
$Accent.AccentState = [WindowStyle.Blur+AccentState]::ACCENT_ENABLE_BLURBEHIND
}
'Acrylic' {
$Accent.AccentState = [WindowStyle.Blur+AccentState]::ACCENT_ENABLE_ACRYLICBLURBEHIND
$Accent.GradientColor = $Transparency -shl 24 -bor ($Color -band 0xFFFFFF)
}
'Disable' {
$Accent.AccentState = [WindowStyle.Blur+AccentState]::ACCENT_DISABLED
}
}
$AccentStructSize = [System.Runtime.InteropServices.Marshal]::SizeOf($Accent)
$AccentPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($AccentStructSize)
[System.Runtime.InteropServices.Marshal]::StructureToPtr($Accent,$AccentPtr,$false)
$Data = [WindowStyle.Blur+WindowCompositionAttributeData]::new()
$Data.Attribute = [WindowStyle.Blur+WindowCompositionAttribute]::WCA_ACCENT_POLICY
$Data.SizeOfData = $AccentStructSize
$Data.Data = $AccentPtr
$Result = [WindowStyle.Blur]::SetWindowCompositionAttribute($MainWindowHandle,[ref]$Data)
if ($Result -eq 1) {
Write-Verbose "Successfully set Window Blur status."
}
else {
Write-Verbose "Warning, couldn't set Window Blur status."
}
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($AccentPtr)
}
To use call with something like:
Set-WindowBlur -MainWindowHandle 1114716 -Acrylic -Color 0xFF0000 -Transparency 50
Set-WindowBlur -MainWindowHandle 1114716 -Disable
Set-WindowBlur -MainWindowHandle 1114716 -Enable
Adapted from: https://gist.github.com/riverar/fd6525579d6bbafc6e48
and from:
https://github.com/riverar/sample-win32-acrylicblur/blob/master/MainWindow.xaml.cs
I can see you invoke these Windows API functions by embedding/building C# source code inside your Powershell script. It's probably the way to go, although I think I wouldn't have bothered with trying to 'add' methods and construct the C# source code piece by piece (because declaring SetWindowCompositionAttribute using your system won't be that easy).
First, because SetWindowCompositionAttribute accepts complex arguments you'll have to declare the underlying structures in you C# code, then you can expose C# methods that simplify the use of SetWindowCompositionAttribute for consumption by the Powershell side.
By googling a bit (simply typed TheMethodINeedToWrap C#), I quickly found a few resources. I've mixed these findings into a C# class that exposes a (simple enough that it is Powershell-callable) method to blur a window. The results of my tests were not ideal as I ended up with a blurred window border but weird looking interior and controls (must have something to do with winforms and/or the window creation parameters), but at least I obtained something.
Because it's a bit long, I've saved this class as a gist here, but as I said it's made of a bunch of interop structures and methods definitions, then a public method simplifying access to SetWindowCompositionAttribute.
PS: If SetWindowCompositionAttribute proves too difficult to wrap, DwmSetWindowAttribute may be a viable aleternative. You'll also find that, although it sometimes needs tweaking, http://pinvoke.net/ proves a viable source for interop definitions.
Related
Hi all you c# wizards!
I need to store all the memory offset values of (packed) nested structs within these respective structs.
Recusively looping through all the members works fine so far. Also, i get the appropriate memory offset values.
This struct contraption might contain several dozends of structs, and several hundreds of other members in the end.
But i do this whole thing at initialization time, so CPU performance won't be an issue here.
But:
In this iteration process, it seems i have trouble accessing the actual instances of those structs. As it turns out, when i try to store these offset values, they don't end up where i need them (of course, i need them in the instance "SomeStruct1" and its containing other struct instances, but the debugger clearly shows me the init values (-1)).
I suspect "field_info.GetValue" or "obj_type.InvokeMember" is not the proper thing to get the object reference? Is there any other way to loop through nested struct instances?
Please help! I've desperately debugged and googled for three days, but i'm so out of ideas now...
Thanks for your efforts!
-Albert
PS - the reason i do this unusual stuff:
I communicate between two embedded CPU cores via the mentioned nested struct (both are mixed c/c++ projects). This works like a charm, as both cores share the same memory, where the struct resides.
Additionally, i have to communicate between a c# host application and theses embedded cores, so i thought it could be a neat thing, if i implement a third instance of this struct. Only this time, i oviously can't use shared RAM. Instead, i implement value setters and getters for the data-holding members, find out the memory offset as well as the lenght of the data-holding members, and feed this information (along with the value itself) via USB or Ethernet down to the embedded system - so the "API" to my embedded system will simply be a struct. The only maintenance i have to do every thime i change the struct: i have to copy the holding .h file (of the embedded project) to a .cs file (host project).
I know it's crazy - but it works now.
Thanks for your interest. -Albert
This is a simplified (buggy, see below) example that should compile and execute (WinForms, c#7.3):
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace CodingExample
{
public interface Interf
{
Int32 Offset {get; set; }
}
[StructLayout (LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct sSomeStruct2 : Interf
{
public sSomeStruct2 (bool dummy)
{
Offset = -1;
SomeMember3 = 0;
}
public Int32 Offset {get; set; }
public Int32 SomeMember3;
// much more various-typed members (e. g. nested structs)...
}
[StructLayout (LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct sSomeStruct1 : Interf
{
public sSomeStruct1 (bool dummy)
{
Offset = -1;
SomeMember1 = 0;
SomeStruct2 = new sSomeStruct2 (true);
SomeMember2 = 0;
}
public Int32 Offset {get; set; }
public Int32 SomeMember1;
public sSomeStruct2 SomeStruct2;
public Int16 SomeMember2;
// much more various-typed members...
}
public partial class Form1 : Form
{
void InitializeOffsets (object obj)
{
Console.WriteLine ("obj: {0}", obj);
Type obj_type = obj.GetType ();
foreach (FieldInfo field_info in obj_type.GetFields ())
{
string field_name = field_info.Name;
Int32 offset = (Int32) Marshal.OffsetOf (obj_type, field_name);
Type field_type = field_info.FieldType;
bool is_leafe = field_type.IsPrimitive;
// none of theses three options seem to give me the right reference:
// object node_obj = field_info.GetValue (obj);
// object node_obj = field_info.GetValue (null);
object node_obj = obj_type.InvokeMember (field_name, BindingFlags.GetField, null, obj, null);
Console.WriteLine ("field: {0}; field_type: {1}; is_leafe: {2}; offset: {3}", field_name, field_type, is_leafe, offset);
if (! is_leafe)
{
// this writes not as expected:
(node_obj as Interf).Offset = offset;
InitializeOffsets (node_obj);
}
}
}
sSomeStruct1 SomeStruct1;
public Form1 ()
{
InitializeComponent ();
SomeStruct1 = new sSomeStruct1 (true);
InitializeOffsets (SomeStruct1);
}
}
}
Meanwhile i found out, what i did wrong:
i have to do boxing, so i can use "ref" when i call my initialize function:
// instead of this:
SomeStruct1 = new sSomeStruct1 (true);
// i have to do it this way:
object boxed_SomeStruct1 = new sSomeStruct1 (true);
InitializeOffsets (ref boxed_SomeStruct1);
SomeStruct1 = (sSomeStruct1) boxed_SomeStruct1;
Within the "InitializeOffsets" function, "field_info.GetValue (obj)" delivers a copy of my member object. That's why i have to copy the modified copy back at the very end of the foreach loop:
field_info.SetValue (obj, node_obj);
After these changes, the code works as intended.
Thanks for your interest. -Albert
I need help in creating a C# script that set a android WiFi in hotspot mode. Here is the code that I managed to create.
public bool setAPEnabled(bool enabled)
{
using (AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity"))
{
try
{
if(isWifiEnabled()==true){
setWifiEnabled(false);
}
using (var wifiManager = activity.Call<AndroidJavaObject>("getSystemService", "wifi"))
{
return wifiManager.Call<bool>("setWifiApEnabled",null, enabled);
}
}
catch (Exception e)
{
}
}
return false;
}
Everything works well - but I have a problem with setting the SSID and password. After reviewing the documentation I know that I have to replace my null value with the settings object, but I completely don't know how to do it in Unity.
Theses methods works only for android 5.0 and less !
The EASY way :
Try instantiating the WifiConfiguration first :
AndroidJavaObject wifiConfiguration = new AndroidJavaClass("android.net.wifi.WifiConfiguration");
Now you can call methods and set/get fields within this object :
// to set SSID
wifiConfiguration.Set("SSID", meSSID); // string
wifiConfiguration.Set("preSharedKey", mePassword); // string
After settings all of the required fields just call your setWifiApEnabled method :
wifiManager.Call<bool>("setWifiApEnabled", wifiConfiguration, enabled);
Maybe you will have to set more fields than these two but to confirm that you should check the source and ensure what setWifiApEnabled method does internaly.
The HARD way :
( using reflection code )
Step 6 does not work for android 5.0+ !
Using reflection with AndroidJavaObject can be a bit tricky because you have to remember to dispose every object.
So from the beginning :
// android code for that should look like :
// wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
// but in Unity C# you have to split this into few chunks:
// 1. Get calling class :
using ( AndroidJavaObject classObj = wifiManager.Call<AndroidJavaObject>("getClass") )
{
// classObj should contains your class object
// 2. call get WifiConfiguration class details :
using ( AndroidJavaObject wifiConfiguration = new AndroidJavaObject("setWifiApEnabled") )
{
// 3. Fill that object :
wifiConfiguration.Set("SSID", meSSID); // string
wifiConfiguration.Set("preSharedKey", mePassword); // string
// 4. Get WifiConfiguration class definition
using (AndroidJavaObject wifiCfgClass = wifiConfiguration.Call<AndroidJavaObject>("getClass") )
{
// 5. Get boolean definition
using ( AndroidJavaObject booleanObj = new AndroidJavaObject("java.lang.Boolean") )
{
using ( AndroidJavaObject booleanClass = booleanObj.Call<AndroidJavaObject>("getClass") )
// 6. Get method definition
using ( AndroidJavaObject methodObj = classObj.Call<AndroidJavaObject>("getMethod", "setWifiApEnabled", wifiCfgClass , booleanClass))
{
// 7. Call that method :)
methodObj.Call("invoke", wifiManager, wifiConfiguration, enabled);
}
}
}
}
}
WifiConfiguration :
I was trying to find out why the above code might not work but for me it was working okay ( tested on some virtual machines and Samsung Galaxy S5 Neo ).
What may be the case ( which I found out at almost midnight ) is a passphrase.
According to this wikipedia article in the section about WPA-PSK
Also referred to as WPA-PSK (pre-shared key) mode, this is designed for home and small office networks and doesn't require an authentication server.[9] Each wireless network device encrypts the network traffic using a 256 bit key. This key may be entered either as a string of 64 hexadecimal digits, or as a passphrase of 8 to 63 printable ASCII characters.[10] If ASCII characters are used, the 256 bit key is calculated by applying the PBKDF2 key derivation function to the passphrase, using the SSID as the salt and 4096 iterations of HMAC-SHA1.[11] WPA-Personal mode is available with both WPA and WPA2.)
My suggestion would be to use the same passphrase as in the article linked above to make sure it's valid.
Also another thing to note is the SSID part which has a short but good description here on wikipedia.
A common, albeit incorrect assumption, is that an SSID is a string of human-readable characters (such as ASCII), terminated by a NUL character (as in a C-string). SSIDs must be treated and handled as what they are, a sequence of 0–32 octets, some of which may not be human-readable
From what I've checked you do not need to null-terminate your string within Java or C# because it will be handled by native code but still you should not exceed 31 characters ( 32 will be the null character ).
I checked this with :
SSID:MeHotSpot
WPA-PSK:5260305714217573
Ok, I will preface this by saying I did do a look up and read a lot of similar questions and answers on here before posting this.
Background Info
Using Visual Studio Professional 2013
Goal:
This project is for my own amusement and to try to learn as much as I can.
I have a native C++ header file called BinaryTree.h which is just a simple data structure that uses recursive logic to build a binary search tree. It works quite well on its own.
I want to build a GUI in C# that uses this. (Its not really useful or practical, I just choose it, well because I wanted to. Also, while the logic inside the binary tree class is complex(ish), I only need to call 2 methods, a addNode method, and a toString method which return the max depth and number of nodes).
I choose using a c++/cli wrapper to accomplish this. Everything seemed to go well, the build was successful and a .dll file was created in the debug directory of my project.
Now, I started in on the C# side of things. I added the .dll file to references. However, when I typed in " using filename.dll;" I got an error saying "Type or namespace not found...".
To reiterate I did some researching. I found (it seemed in VS2010) that different target frameworks could cause this error. I checked mine, targets for both were net4.5, so that is not the problem.
Here is the code from my c++/cli wrapper. Perhaps it has something to do with using templates? Any help is appreciated.
#pragma once
#include "D:\Schoolwork 2015\Test Projects\CPPtoC#WrapperTest\CPPLogic\CPPLogic\BinaryTree.h"
using namespace System;
namespace BinaryTreeWrapper {
template<class Data>
public ref class BinaryTreeWrapperClass
{
public:
BinaryTreeWrapperClass(){ tree = new BinaryTree(); }
~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
!BinaryTreeWrapperClass(){ delete tree; }
//methods
void wrapperAddNode(Data)
{
tree->addNode(Data);
}
std::string wrapperToString()
{
return tree->toString();
}
private:
BinaryTree* tree;
};
}
Screenshot of the error:
EDIT
Ok, so here is a weird thing... my original file built just fine with the new code and produced a .dll file. However, I decided to try a fresh project since the namespace was still not being found. Upon moving the code over and trying to build, I've run into 4 errors:
Error 1 error C2955: 'BinaryTree' : use of class template requires template argument list
Error 2 error C2512: 'BinaryTree' : no appropriate default constructor available
Error 3 error C2662: 'void BinaryTree::addNode(Data)' : cannot convert 'this' pointer from 'BinaryTree' to 'BinaryTree &'
Error 4 error C2662: 'std::string BinaryTree::toString(void) const' : cannot convert 'this' pointer from 'BinaryTree' to 'const BinaryTree &'
I copied the code exactly, only changing the namespace to "TreeWrapper' and the class name to 'TreeWrapperClass'.
To help, I've include a snippet from my BinaryTree.h file. There is a bunch more that defines the 'NODE' class, but I didn't want to clutter it up more than i needed.
After further investigation, it appears the problem lies with using 'generic'. If I switch it all to 'template' it builds just fine, but then it can't be used as a reference in C# (getting the namespace error). I built a test project using very simple methods (no templates) and was able to use the .dll wrapper I made in C#. So the problem lies with templates and generics.
Last Edit
I've found if I change the code to initiate the template as 'int' it works just fine, and I can use it in C#. For example:
...
BinaryTreeWrapperClass(){ tree = new BinaryTree<int>(); }
....
private:
BinaryTree<int>* tree;
BinaryTree.h
template<class Data>
class BinaryTree
{
private:
Node<Data>* root;
unsigned int nNodes;
unsigned int maxDepth;
unsigned int currentDepth;
void traverse(Node<Data>*& node, Data data);
public:
BinaryTree();
~BinaryTree();
void addNode(Data);
std::string toString() const
{
std::stringstream sstrm;
sstrm << "\n\t"
<< "Max Depth: " << maxDepth << "\n"
<< "Number of Nodes: " << nNodes << "\n";
return sstrm.str(); // convert the stringstream to a string
}
};
template<class Data>
BinaryTree<Data>::BinaryTree() //constructor
{
//this->root = NULL;
this->root = new Node<Data>(); //we want root to point to a null node.
maxDepth = 0;
nNodes = 0;
}
template<class Data>
BinaryTree<Data>::~BinaryTree() //destructor
{
}
template<class Data>
void BinaryTree<Data>::addNode(Data data)
{
traverse(root, data); //call traverse to get to the node
//set currentDepth to 0
currentDepth = 0;
}
template<class Data>
void BinaryTree<Data>::traverse(Node<Data>*& node, Data data)
{
//increment current depth
currentDepth++;
if (node == NULL) //adds new node with data
{
node = new Node<Data>(data);
//increment nNode
nNodes++;
//increment maxDepth if current depth is greater
if (maxDepth < currentDepth)
{
maxDepth = currentDepth - 1; //currentDepth counts root as 1, even though its 0;
}
return;
}
else if (node->getData() >= data) //case for left, getData must be bigger. The rule is, if a number is equal to getData or greater, it is added to the left node
{
Node<Data>* temp = node->getLeftNode();
traverse(temp, data); //recursive call, going down left side of tree
node->setLeftNode(temp);
}
else if (node->getData() < data) //case for right, getData must be less
{
Node<Data>* temp = node->getRightNode();
traverse(temp, data);
node->setRightNode(temp);
}
return;
}
You're declaring a template, but are not actually instantiating it. C++/CLI templates are just like C++ templates - if you don't instantiate them, they just don't exist outside of the compilation unit.
You're looking for generics here (yes, C++/CLI has both templates and generics). And here's how you declare a generic in C++/CLI:
generic<class Data>
public ref class BinaryTreeWrapperClass
{
// ...
}
But you'll get stuck at this point for several reasons.
First, I'll include the parts which are OK:
generic<class Data>
public ref class BinaryTreeWrapperClass
{
public:
BinaryTreeWrapperClass(){ tree = new BinaryTree(); }
~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
!BinaryTreeWrapperClass(){ delete tree; }
private:
BinaryTree* tree;
};
You've got this right.
Next, let's look at:
std::string wrapperToString()
{
return tree->toString();
}
That's no good, since you're returning an std::string - you don't want to use that from C#, so let's return a System::String^ instead (using marshal_as):
#include <msclr/marshal_cppstd.h>
System::String^ wrapperToString()
{
return msclr::interop::marshal_as<System::String^>(tree->toString());
}
Here, that's much better for use in C#.
And finally, there's this:
void wrapperAddNode(Data)
{
tree->addNode(Data);
}
See... here you'll have to do some real interop. You want to pass a managed object to a native one for storage. The GC will get in your way.
The GC is allowed to relocate any managed object (move it to another memory location), but your native code is clueless about this. You'll need to pin the object so that the GC won't move it.
There are several ways to do this, and I don't know what BinaryTree::addNode looks like, but I'll just suppose it's BinaryTree::addNode(void*).
For long-term object pinning, you can use a GCHandle.
The full code looks like this:
generic<class Data>
public ref class BinaryTreeWrapperClass
{
public:
BinaryTreeWrapperClass()
{
tree = new BinaryTree();
nodeHandles = gcnew System::Collections::Generic::List<System::Runtime::InteropServices::GCHandle>();
}
~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
!BinaryTreeWrapperClass()
{
delete tree;
for each (auto handle in nodeHandles)
handle.Free();
}
void wrapperAddNode(Data data)
{
auto handle = System::Runtime::InteropServices::GCHandle::Alloc(
safe_cast<System::Object^>(data),
System::Runtime::InteropServices::GCHandleType::Pinned);
nodeHandles->Add(handle);
tree->addNode(handle.AddrOfPinnedObject().ToPointer());
}
System::String^ wrapperToString()
{
return msclr::interop::marshal_as<System::String^>(tree->toString());
}
private:
BinaryTree* tree;
System::Collections::Generic::List<System::Runtime::InteropServices::GCHandle>^ nodeHandles;
};
This allocates a GCHandle for each node, and stores it in a list in order to free it later. Freeing a pinned handle releases a reference to the object (so it becomes collectable if nothing else references it) as well as its pinned status.
After digging around I think I found the answer, although not definitively, so if anyone can chime in I would appreciate it. (Apologies in advance for any misuse of vocabulary, but I think I can get the idea across).
It seems the problem lies in the fundamental difference between templates in native C++ and generics. Templates are instantiated at compilation, and are considered a type. They cannot be changed at runtime, whereas generics can. I don't think there is an elegant way to solve that.
At least I accomplished one goal of my project, which was learning as much as I can haha. I was able to get c++/cli wrappers working for things without templates, and if I choose a type for the template before building the .dll (see above)
If anyone else has an idea please let me know.
I have some customers that uses apps using VB6 and some others languages. The code works fine using OLE (COM), but customers prefers to use native DLL to avoid to register the libraries and deploy them in the field.
When I register the DLL and test in VB6 (OLE), it works fine. When I call a method that return a Strutc, it works fine with OLE, but, if I access in VB6 using Declare, I got fatal error in method that should return the same kind of struct (method 'EchoTestData' see bellow).
The code is compile in C# to use in unmanaged code with OLE or by entry points> I had tested with VB6.
namespace TestLib
{
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("TestClass")]
public class TestClass : System.EnterpriseServices.ServicedComponent
{
/*
* NOTE:
* ExportDllAttribut: a library that I have used to publish the Entry Points,
* I had modified that project and it works fine. After complile, the libray
* make the entry points...
* http://www.codeproject.com/Articles/16310/How-to-Automate-Exporting-NET-Function-to-Unmanage
*/
/*
* System.String: Converts to a string terminating in a null
* reference or to a BSTR
*/
StructLayout(LayoutKind.Sequential)]
public struct StructEchoData
{
[MarshalAs(UnmanagedType.BStr)]
public string Str1;
[MarshalAs(UnmanagedType.BStr)]
public string Str2;
}
/*
* Method static: when I use this method, the Vb6 CRASH and the EVENT VIEWER
* show only: System.Runtime.InteropServices.MarshalDirectiveException
* HERE IS THE PROBLEM in VB6 with declare...
* Return: struct of StructEchoData type
*/
[ExportDllAttribute.ExportDll("EchoTestStructure", CallingConvention.StdCall)]
public static StructEchoData EchoTestStructure(string echo1, string echo2)
{
var ws = new StructEchoData
{
Str1 = String.Concat("[EchoTestData] Retorno String[1]: ", echo1),
Str2 = String.Concat("[EchoTestData] Retorno String[1]: ", echo2)
};
return ws;
}
/*
* Method NOT static: it is used as COM (OLE) in VB6
* In VB6 it returns very nice using with COM.
* Note that returns the StructEchoData without problems...
* Return: struct of StructEchoData
*/
[ExportDllAttribute.ExportDll("EchoTestStructureOle", CallingConvention.StdCall)]
public StructEchoData EchoTestStructureOle(string echo1, string echo2)
{
var ws = new StructEchoData
{
Str1 = String.Concat("[EchoOle] Return StringOle[1]: ", echo1),
Str2 = String.Concat("[EchoOle] Return StringOle[2]: ", echo2),
};
return ws;
}
/*
* Method static: It works very nice using 'Declare in VB6'
* Return: single string
*/
[ExportDllAttribute.ExportDll("EchoS", CallingConvention.StdCall)]
// [return: MarshalAs(UnmanagedType.LPStr)]
public static string EchoS(string echo)
{
return "[TestClass::EchoS from TestLib.dll]" + echo;
}
/*
* Method NOT static: it is used as COM (OLE) in VB6
* In VB6 it returns very nice
* Return: single string
*/
[ExportDllAttribute.ExportDll("EchoSOle", CallingConvention.StdCall)]
// [return: MarshalAs(UnmanagedType.LPStr)]
public string EchoSOle(string echo)
{
return "[TestClass::EchoS from TestLib.dll]: " + echo;
}
}
}
Now, in VB6 I cant test using Declare or register the TestLib.Dll as COM
USING DECLARE in VB6:
Private Declare Function EchoS Lib "C:\Temp\_run.dll\src.app.vb6\TestLib.dll"_
(ByVal echo As String) As String
Private Type StructEchoData
Str1 As String
Str2 As String
End Type
Private Declare Function EchoTestStructure Lib "C:\Temp\_run.dll\src.app.vb6\TestLib.dll"_
(ByVal echo1 As String, ByVal echo2 As String) As StructEchoData
// ERROR - CRASH VB6
Private Sub EchoData_Click()
Dim ret As StructEchoData
ret = EchoTestStructure("echo1 Vb6", "echo2 vb6")
TextBox.Text = ret.Str1
End Sub
// WORKS Fine, returns a string
Private Sub btRunEchoTestLib_Click()
TextBox.Text = EchoS("{Run from VB6}")
End Sub
And using VB6 wiht OLE:
1St. Registering the DLL: C:\Windows\Microsoft.NET\Framework\v4.0.30319\regsvcs.exe TestLib.dll /tlb:Test.tlb
2nd. Add the reference in project. The program runs and I got the response with one string and receive the response when has a structure too.
Private Sub Echo_Click()
Dim ResStr As String
Dim obj As TestLib.TestClass
Set obj = New TestClass
ResStr = obj.EchoSOle(" Test message")
MsgBox "Msg Echo: " & ResStr, vbInformation, "ResStr"
Beep
End Sub
Private Sub EchoDataOle_Click()
Dim obj As TestLib.TestClass
Set obj = New TestClass
// Here I define the struct and works fine!!
Dim ret As TestLib.StructEchoData
ret = obj.EchoTestStructureOle("test msg1", "test msg2")
TextStr1.Text = ret.Str1
TextStr2.Text = ret.Str2
Debug.Print ret.Str1
Debug.Print ret.Str2
Beep
End Sub
So, the StructEchoData is wrapped fine using COM, but if I want to use Declare and got the access by entry point, not work. Could anybody suggest anything, please?
The VB6 Declare Lib only works for unmanged DLL exported functions. C# does not expose it's functions as unmanged functions, since it's managed code. The only supported way to exporting classes from C# is to use COM. So you can't use Declare Lib to access C# methods from VB6.
There is a library that is supposed to create unmanged exports from your C# code; Robert Giesecke's Unmanaged Exports. I've personally never used it; I've only seen it mentioned on Stack Overflow.
There is a supported way to export unmanged functions from a .Net assembly and that is using C++/CLR since it allows the mixing of managed and unmanged code. You could create a C++/CLR wrapper that exported unmanged functions that call your C# DLL. That is the way I would go.
You cannot create Dynamic Link Libraries with c#.
However, with a little bit of C++ you can create a bootstrapper for .Net dll's by leveraging the CLR hosting API.
CLR Hosting API
You can create a Dynamic Link Library in C++ with a method called something like "LoadPlugins".
Write LoadPlugins to load the CLR (or a specific version of the CLR), then use reflection to load some .net DLL's.
Also with the same C++ code, you can expose .net methods in the C++ dll as exported native functions in c++ that will work with VB6's declare...
Each function in c++ would have to check to make sure the CLR is loaded, and that the .net code being called is loaded, then use reflection to call it.
Thanks for replies,
The C# only will work with unmanaged DLL if there are Entry Points into the code. The declarations that were used into the code with the statement 'ExportDllAttribut' generate the respectives Entry Points that are necessary to be used by unmanagement code.
The problem is to use export with "Data Structure", that is my question.
I had used without problems the methods that return string or integer values, like EchoS in this post. I used this example (TestLib.DLL) with VB6 and it works fine with "Declare" in VB6:
Private Declare Function EchoS Lib "C:\Temp\_run.dll\src.app.vb6\TestLib.dll"_
(ByVal echo As String) As String
// WORKS Fine, returns a string
Private Sub btRunEchoTestLib_Click()
TextBox.Text = EchoS("{Run from VB6}")
End Sub
I wrote a note at the begining the C# code, but could not be clear, sorry. Explained a bit more. After I compile the library, I use in "Project properties", "Build events" the following command:
"$(ProjectDir)libs\ExportDll.exe" "$(TargetPath)" /Debug
This directive [ExportDllAttribute.ExportDll("NameOfEntryPoint"] disasembly the DLL (using ilasm.exe and ildasm.exe) and write the exports directives to create Entry Points, compiling and generating the DLL again. I used few years and works fine.
If I apply the dumpbin command in DLL, the results are the Entry Points published, so, it is possible to use with unmanaged code like VB6, but using the correct Marshalling type or statement in Vb6. Example:
dumpbin.exe /exports TestLib.dll
ordinal hint RVA name
2 0 0000A70E EchoC
5 1 0000A73E EchoSOle
3 2 0000A71E EchoTestStructure
6 3 0000A74E EchoTestStructureOle
This code was tested using the method EchoS (with Declare) or EchoSOle (COM) and in both cases are fine. When the DLL is used as OLE in app, the structure can be see as Type and the run fine, but, in VB6 with declare, I got error MarshalDirectiveException only with Structure returns, singles types like string C# or integer, I don't have problem.
I think thet the problem is how to the Structure was marshalling by the static method EchoTestStructure or how to was declared in VB6. Maybe could be a wrong way used in VB6 (that I am not any expert) or any marshalling parameter in Static Method 'EchoTestStructure', that is the real question and help.
PS: I will see the links in the replies to try another approaches if I can´t solve it.
Thanks again, Any other idea?
It was Solved, but by another way... this can be not the state of the art, but it works.
First thing, it was needed to change the Marshaling type in struct, from BStr to LPStr.
I don't know what was exactly the motive because some Microsoft helps and others links, they said: "System.String : Converts to a string terminating in a null reference or to a BSTR".
If I set "Hello" into the Str1 in the Structure, I receive "效汬㉯映潲䉖⸶⸮" into the DLL. If I return a fixed string "Hello" form DLL, the VB6 shows only the first char "H", so, I change to ANSI (LPStr) and solve it. Using OLE, the BSTR Works fine, but native DLL does not. The code was changed to:
StructLayout(LayoutKind.Sequential)]
public struct StructEchoData
{
[MarshalAs(UnmanagedType.BStr)]
public string Str1;
[MarshalAs(UnmanagedType.BStr)]
public string Str2;
}
In the method 'EchoTestStructureOle' declaration, it was change to use reference and passing the address of structure intead return a structure. Before the code was:
public StructEchoData EchoTestStructure(string echo1, string echo2)
{
// It Works only wtih OLE the return of the type StructEchoData
var ws = new StructEchoData
{
Str1 = String.Concat("[EchoTestData] Return from DLL String[1]: ", echo1),
Str2 = String.Concat("[EchoTestData] Return from DLL String[2]: ", echo2)
};
return ws;
}
And now I am using interger to return the status (1 ok, -1 error) and the parameter structure type by reference, the method was change to:
public static int EchoTestStructure(ref StructEchoData inOutString)
{
// used to test the return of values only, data 'in' not used
var ws = new StructEchoData
{
Str1 = String.Concat("[EchoTestData] Return from DLL String[1]: ", inOutString.Str1),
Str2 = String.Concat("[EchoTestData] Return from DLL String[2]: ", inOutString.Str2)
};
inOutString = ws;
return 1;
}
With reference, I can receive the values from VB6 and return to VB6 without no problems. I think that must have a way to return a structure, but, this approach solve it for this time.
In VB6 side: the code was changed to:
Private Type StructEchoData // Same type, do not change...
Str1 As String
Str2 As String
End Type
Private Declare Function EchoTestData Lib "C:\Temp\_run.dll\src.app.vb6\TestLib.dll" (ByRef strcData As StructEchoData) As Long
Private Sub ShowMessage_Click()
Dim res As Long
Dim strcData As StructEchoData
strcData.Str1 = "Str1 from VB6..."
strcData.Str2 = "Str2 from VB6..."
res = EchoTestData(strcData)
/*
strcData.Str1 --> Data Received from DLL:
[EchoTestData] Return from DLL String[1]: Str1 from VB6...
strcData.Str2 --> Data Received from DLL
[EchoTestData] Return from DLL String[2]: Str2 from VB6...
*/
...
End Sub
Thanks for the tips for while. If you have any suggest, it will be welcome.
I have this class KernelHelper which is written in C# .NET Framework 2.0. What I want to do is call its static functions in a C program.
namespace Kernel.Client {
public class KernelHelper {
public static int testc(string msg) {
// Removing the message box does not change anything
System.Windows.Forms.MessageBox.Show(msg);
return 0;
}
// ...
}
}
which compiles and does not seem to make any problems so far. But calling ICLRRuntimeHost_ExecuteInDefaultAppDomain() returns 0x80131513 which is according to this the fact that I didn't follow the correct signature convention. But this can not be the problem.
#if defined(_WIN32)
# include <Windows.h>
# define COBJMACROS
# define CINTERFACE
# include <mscoree.h>
#endif
// ...
HRESULT status;
ICLRRuntimeHost *Host;
BOOL Started;
DWORD Result;
Host = NULL;
Started = FALSE;
status = CorBindToRuntimeEx(
NULL,
NULL,
0,
&CLSID_CLRRuntimeHost,
&IID_ICLRRuntimeHost,
(PVOID *)&Host
);
if (FAILED(status)) {
printf("failed 1\n");
}
status = ICLRRuntimeHost_Start(Host);
if (FAILED(status)) {
printf("failed 2\n");
}
Started = TRUE;
status = ICLRRuntimeHost_ExecuteInDefaultAppDomain(
Host,
L"C:\\svn\\Server\\Kernel\\interface\\bin\\Kernel.Client.dll",
L"Kernel.Client.KernelHelper",
L"testc",
L"My message",
&Result
);
if (FAILED(status)) {
printf("failed 3\n");
}
Could anybody help me here?
Edit:
I tried it also without the message box and let the function just return 0 but it didn't change a thing.
Wow, I did not expect to find the solution so fast and on my own. If you experiance the same problem try this. It seems not to work for everyone.
All I needed to do is to change the Platform Target from Any CPU to x86 inside my project properties.
Edit: If anyone can show me another solution I'd still be glad since I can not tell if this might not be a problem anyway changing this setting.