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.
Related
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.
This question already has answers here:
Optional Argument of COM Add-in vs Automation Add-in Written in C#
(2 answers)
Closed 4 years ago.
I am working on some VBA COM Add-in and Excel Add-in, whose core codes are written in C#. I'd like to set an optional argument for the function and I know that this is legal for both C# and VBA, and even Excel WorksheetFunction. But I find that finally only Excel functions work well but VBA function always says type mismatch after the library has been registered in both COM Add-in and Excel Add-in.
Here is a simple example: in C# we have some function called
double NormalCDF(double x, double mu = 0, double sigma = 1);
In Excel spreadsheet, I can successfully call
NormalCDF(1.2, 2, 3)
or
NormalCDF(1.2)
Both give the right results. But in VBA, the following is successful,
TestObj.NormalCDF(1.2, 2, 3)
is good, while
TestObj.NormalCDF(1.2)
is failed with "Type mismatch".
Could anyone help with this problem?
////////////////////////////////////////////////////////////////////////////
08/10/2018 Update
Please see a simplified example code:
In "MyLibraryExcel.cs" I have
...
public interface IWorksheetFunctions
{
int test(int a = 1, int b = 1);
}
...
public class WorksheetFunctions : MoodysMathUdfBase,IWorksheetFunctions, IDTExtensibility2
{
protected Application ExcelApplication { get; set; }
public int test(int a = 1, int b = 1)
{
return a + b;
}
}
In "MyLibraryVBA.cs" I have
...
public interface IExcelVBA
{
int test(int a = 1, int b = 1);
}
...
public class ExcelVBA : IExcelVBA
{
public int test(int a = 1, int b = 1)
{
return a + b;
}
}
After building the projects, both two libraries have been registered. In Excel spreadsheet, I want to call
=test(2,3)
which is expected to return 5, and call
=test()
which is expected to return 2.
In VBA macro, I have some codes
Sub TestVBA()
range("Output1").value=TestObj.test(2,3)
range("Output2").value=TestObj.test()
End Sub
which is expected to get 5 and 2 as well.
The interesting thing is that, if I run the two functions of Excel spreadsheet (click the cells and press Enter), both work fine, then run the two functions of VBA, only the first one works, the second one is failed with "Type Mismatch". However, if I run the VBA first, both two test() work fine, then run the two functions of Excel spreadsheet, only the first one works, the second one will display #Value.
Could you please post your VBA Code?
I have tried with below code and it is working fine
Sub OptionalArgs(firstValue As Double, Optional secindValue As Double = 0)
MsgBox CStr(firstValue)
End Sub
try this:
object missing = System.Reflection.Missing.Value;
TestObj.NormalCDF(1.2, missing , missing);
I'm converting a sample VB Client (from a 3rd party SDK which consumes its WCF Services) to C#. The client in VB when executed, works correctly.
I'm writing a small wrapper in C# Windows Form in vs2015 to consume the same WCF Services. I was able to add the service references in VS2015.
I am having a problem making an equivalent call in VB which works but for some reason in C#, it does not work, as if the generated C# classes is incorrect.
in the working sample VB,
I have a call ContinueGetList
Dim siteList() As SRAdminSys.MW_DataContractCommon
Dim iRecCount As Integer = 20
Dim Status As SRAdminSys.MW_ContinueStatusEnum
Status = m_wcfSysDB.ContinueGetList(iRecCount, siteList)
The call returns a list in siteList which is an array of SRAdminSys.MW_DataContractCommon
note: I think* the actual The array returned by the webservice , is of type MW_Site which inherits DataContractCommon - (* when I made the same call in a C# equivalent call, I get an error and when I checked the error , it mentioned that it is casting MW_Site to MW_DataContractCommon )- See further test below.
Partial Public Class MW_Site
Inherits SRAdminSys.MW_DataContractCommon
The Reference.vb from the Generated Proxy Class:
Public Function ContinueGetList(ByVal iCount As Integer, ByRef list() As SRAdminSys.MW_DataContractCommon) As Integer Implements SRAdminSys.IMW_KWSAdminSysDatabase.ContinueGetList
Return MyBase.Channel.ContinueGetList(iCount, list)
End Function
Now in the Non-Working prototype in C# ( it does compile though ) ,
int[] lst_SiteIds = new int[] { };
int iRecCount = 20;
SRAdminSys.MW_ContinueStatusEnum status;
status = (SRAdminSys.MW_ContinueStatusEnum)adminSysClient.ContinueGetList(iRecCount,ref siteList);
The return value status indicated that the call failed and when I check the error - a WCF method GetLastError(), I get
Unable to cast object of type 'MW_DataContractCommon[]' to type 'MW_Site[]'.
When I check the MW_Site Partial class generated by C# Service Reference, I notice it inherits DataContractCommon, so I expected it to work:
public partial class MW_Site : SRAdminSys.MW_DataContractCommon {
Reference.cs
public int ContinueGetList(int iCount, ref TestUtil.SRAdminSys.MW_DataContractCommon[] list) {
TestUtil.SRAdminSys.ContinueGetListRequest inValue = new TestUtil.SRAdminSys.ContinueGetListRequest();
inValue.iCount = iCount;
inValue.list = list;
TestUtil.SRAdminSys.ContinueGetListResponse retVal = ((TestUtil.SRAdminSys.IMW_KWSAdminSysDatabase)(this)).ContinueGetList(inValue);
list = retVal.list;
return retVal.ContinueGetListResult;
}
CWUtility.SRAdminSys.ContinueGetListResponse retVal = ((CWUtility.SRAdminSys.IMW_KWSAdminSysDatabase)(this)).ContinueGetList(inValue);
list = retVal.list;
return retVal.ContinueGetListResult;
}
In relation to the partial class and inheritance of DataContractCommon - On another sample code which works in VB but not in C# when both is pointing to the same WCF service. The only difference is a VB App vs a C# App which points to their respective generated proxy classes from VS Service Reference.
Dim sitekey__ As New SRAdminSite.MW_Key
bRet = m_wcfSiteDB.Add(1, eKEY_COLLECTION, sitekey__)
VB, Reference.vb
Public Function Add(ByVal iSiteID As Integer, ByVal iCollectionType As Integer, ByRef iRecord As SRAdminSite.MW_DataContractCommon) As Boolean Implements SRAdminSite.IMW_KWSAdminSiteDatabase.Add
Return MyBase.Channel.Add(iSiteID, iCollectionType, iRecord)
End Function
SRAdminSite.MW_Key sitekey__ = new SRAdminSite.MW_Key();
//COMPILE ERROR on sitekey__ although MW_Key is a partial class which inherits MW_DataContractCommon
bRet = adminSiteClient.Add(1, (int)SRAdminSite.MW_SiteCollectionTypesEnum.eKEY_COLLECTION, ref sitekey__);
C#, Reference.cs
public bool Add(int iSiteID, int iCollectionType, ref SRAdminSite.MW_DataContractCommon iRecord)
{
SRAdminSite.AddRequest inValue = new SRAdminSite.AddRequest();
inValue.iSiteID = iSiteID;
inValue.iCollectionType = iCollectionType;
inValue.iRecord = iRecord;
SRAdminSite.AddResponse retVal = ((SRAdminSite.IMW_KWSAdminSiteDatabase)(this)).Add(inValue);
iRecord = retVal.iRecord;
return retVal.AddResult;
}
So both these examples seems to be related to the way the proxy classes the WCF Service was generated and inheriting other classes but unable to work. In the 2nd sample the error was at compile time...
Can anyone shed some light, why in VB the default generated class works when called, but in c#, the same equivalent call to the same method does not work?
I've consumed other 3rd party WCF services and normally, importing the Service Reference and consuming the methods were a breeze...
For some reason, this one, I cant wrap my head around why it will not work. Is there something I'm missing or need to modify in the way the Reference.cs is generated before I consume them?
I have the following COM types: Project, ContainerItem and Node.
Project has a collection property with an Append function that accepts ContainerItems.
In C#, using type libraries I can send a Node object to the Append function and the library works as expected:
var prj = new Project();
var node = new Node();
prj.collection.Append(node);
In C++ I tried a direct pointer cast expecting this is what C# was doing, but it ends up in an error:
ProjectPtr prj;
prj.CreateInstance(__uuidof(Project));
NodePtr node;
node.CreateInstance(__uuidof(Node));
prj->collection->Append((ContainerItem**)&node.GetInterfacePtr());
Is there a specific way to these type of COM pointer casts in C++? What am I missing?
COM casting is done with the QueryInterface() method. The object is queried for its support of the interface (based on the GUID), and if the interface is supported, the internal reference counter is incremented (see AddRef()) and a pointer to the interface is returned. MSDN has more detail on the inner workings.
C++ does not directly support the code generation for the "COM cast" as C# does, but its implementation is simple enough.
struct bad_com_cast : std::runtime_error {
bad_com_cast() : std::runtime_error("COM interface not supported") {}
};
template <class To, class From>
To* qi_cast(From* iunknown)
{
HRESULT hr = S_OK;
To* ptr = NULL;
if (iunknown) {
hr = iunknown->QueryInterface(__uuidof(To), (void**)(&ptr));
if (hr != S_OK) {
throw bad_com_cast(); // or return NULL
}
}
return ptr;
}
Using the above "cast", the sample can be implemented as follows;
ContainerItem* ci = qi_cast<ContainerItem>(node);
prj->collection->Append(&ci);
If the ATL library is being used, you can use ATL::CComQIPtr<> directly to obtain the equivalent semantics;
auto ci = CComQIPtr<ContainerItem>(node);
if (ci) {
// ...
}
Just like #HansPassant commented, I had to use the QueryInterface function:
ContainerItem* ci = nullptr;
node.QueryInterface(__uuidof(ContainerItem), ci);
prj->collection->Append(&ci);
I'm working in an assembly in C# that will replace an old DLL registered as COM. That old DLL allowed COM enabled applications (like VB or Perl) or do things like in the following VBS example:
dim All_Domains
set All_Domains = WScript.CreateObject("MailServerX.LocalDomains")
dim Specific_Domain
set Specific_Domain = All_Domains.Items(3)
dim Domain_Aliases
set Domain_Aliases = WScript.CreateObject("MailServerX.Lines")
Domain_Aliases.Add "one.com"
Domain_Aliases.Add "two.com"
Specific_Domain.Domain_Aliases = Domain_Aliases
All_Domains.Items(3) = Specific_Domain
As you can see in the last line, the property/method LocalDomains.Items is being assigned while passing the parameter "3".
I need to maintain the same interface in the new assembly in order to keep compatibility with all existing scripts that access the old DLL. I have this (very summarized) C# class:
public class LocalDomains
{
private List<LocalDomain> itemsList = new List<LocalDomain>();
# Assume the list is now loaded
public LocalDomain Items(int index)
{
return itemsList[index];
}
}
How can I write the method Items in the class LocalDomains so it can not only return a value from the list, but also receive a value assignment so it can do some processing with itemsList[index] including assigning the new value to it?
By that I mean, keeping the last line of my first code block valid with the new code.
Thanks in advance for any advice!