How to get back params passed byref (hosting .NET CLR with PB)

Just starting out? Need help? Post your questions and find answers here.
User avatar
SnowyDog
User
User
Posts: 33
Joined: Tue Jun 10, 2014 8:18 pm

How to get back params passed byref (hosting .NET CLR with PB)

Post by SnowyDog »

The manufacturer of some hardware I'm using has supplied a .NET managed DLL which implements an API for working with that hardware. I'd like to develop a Windows application in PureBasic to make use of this API. I'm new to .NET and CLR but with the help of this excellent post by CELTIC88:-

https://www.purebasic.fr/english/viewtopic.php?p=540316

and sample code plus some fairly intensive research over the last week or so, I'm now able to call a method from a managed class within a .NET assembly created in C# with Visual Studio.

The code for my test .NET assembly (built in VS2022 17.2.5) and the .DLL are included below, along with the PB main code I'm using to open the DLL, create an instance of the Class and invoke the Method. Using the pythonnet module I have confirmed that the .NET Method is functioning as it should in Python (script below).

Parameters are passed to the method via a SafeArray structure. The .NET function return value is passed back as the return value from InvokeMember(). So far, so good.

However, I'm not getting back the modified arguments for parameters passed by reference and I don't know how to solve this. Initially, I expected the same SafeArray holding the In arguments would be automatically updated with the Out arguments by the CLR hosting, but this does not happen.

Does anyone have any thoughts on how to solve this please?

C# Source

Code: Select all

using System;
using System.Windows.Forms;

namespace MyNameSpace
{
    public class MyClass
    {
		public static int MyMethod(ref int Param1, ref int Param2)
		{
			Param1++;
			Param2 = 1234;
			return Param1;
		}
	}
}
Built DLL
https://www.dropbox.com/s/mciprpplkv3ae ... y.dll?dl=0

PureBasic Include file clr.pbi

Code: Select all

EnableExplicit

Interface ICLRMetaHost Extends IUnknown
  GetRuntime.l(wstr,struct,ptr)
  GetVersionFromFile.l(ptr,ptr,ptr)
  EnumerateInstalledRuntimes.l(ptr)
  EnumerateLoadedRuntimes.l(ptr,ptr)
  RequestRuntimeLoadedNotification.l(ptr,ptr,ptr)
  QueryLegacyV2RuntimeBinding.l(ptr,ptr)
  ExitProcess.l(INT.l)
EndInterface

Interface ICLRRuntimeInfo Extends IUnknown
  GetVersionString.l(ptr,ptr)
  GetRuntimeDirectory.l(ptr,ptr)
  IsLoaded.l(ptr,ptr)
  LoadErrorString.l(ptr,ptr,ptr,ptr)
  LoadLibrary.l(ptr,ptr)
  GetProcAddress.l(ptr,ptr)
  GetInterface.l(ptr,ptr,ptr)
  IsLoadable.l(*Bool)
  SetDefaultStartupFlags.l(ptr,ptr)
  GetDefaultStartupFlags.l(ptr,ptr,ptr)
  BindAsLegacyV2Runtime.l()
  IsStarted.l(ptr,ptr)
EndInterface

Interface ICLRRuntimeHost Extends IUnknown
  Start.l()
  Stop.l()
  SetHostControl.l(ptr)
  GetCLRControl.l(*ptr)
  UnloadAppDomain.l(ptr,ptr)
  ExecuteInAppDomain.l(ptr,ptr,ptr)
  GetCurrentAppDomainId.l(ptr)
  ExecuteApplication.l(ptr,ptr,ptr,ptr,ptr,ptr)
  ExecuteInDefaultAppDomain.l(wstr,wstr,wstr,wstr,*ptr)
EndInterface

Interface ICorRuntimeHost Extends IUnknown
  CreateLogicalThreadState.l()
  DeleteLogicalThreadState.l()
  SwitchInLogicalThreadState.l()
  SwitchOutLogicalThreadState.l()
  LocksHeldByLogicalThread.l()
  MapFile.l()
  GetConfiguration.l()
  Start.l()
  Stop.l()
  CreateDomain.l()
  GetDefaultDomain.l(*ptr)
  EnumDomains.l()
  NextDomain.l()
  CloseEnum.l()
  CreateDomainEx.l()
  CreateDomainSetup.l()
  CreateEvidence.l()
  UnloadDomain.l()
  CurrentDomain.l()
EndInterface

Interface AppDomain Extends IDispatch
  get_ToString.l()
  Equals.l()
  GetHashCode.l()
  GetType.l(ptr)
  InitializeLifetimeService.l()
  GetLifetimeService.l()
  get_Evidence.l()
  add_DomainUnload.l()
  remove_DomainUnload.l()
  add_AssemblyLoad.l()
  remove_AssemblyLoad.l()
  add_ProcessExit.l()
  remove_ProcessExit.l()
  add_TypeResolve.l()
  remove_TypeResolve.l()
  add_ResourceResolve.l()
  remove_ResourceResolve.l()
  add_AssemblyResolve.l()
  remove_AssemblyResolve.l()
  add_UnhandledException.l()
  remove_UnhandledException.l()
  DefineDynamicAssembly.l()
  DefineDynamicAssembly_2.l()
  DefineDynamicAssembly_3.l()
  DefineDynamicAssembly_4.l()
  DefineDynamicAssembly_5.l()
  DefineDynamicAssembly_6.l()
  DefineDynamicAssembly_7.l()
  DefineDynamicAssembly_8.l()
  DefineDynamicAssembly_9.l()
  CreateInstance.l()
  CreateInstanceFrom.l()
  CreateInstance_2.l()
  CreateInstanceFrom_2.l()
  CreateInstance_3.l()
  CreateInstanceFrom_3.l()
  Load.l()
  Load_2.l(bstr,*ptr)
  Load_3.l(ptr,*ptr)
  Load_4.l()
  Load_5.l()
  Load_6.l()
  Load_7.l()
  ExecuteAssembly.l()
  ExecuteAssembly_2.l()
  ExecuteAssembly_3.l()
  get_FriendlyName.l()
  get_BaseDirectory.l()
  get_RelativeSearchPath.l()
  get_ShadowCopyFiles.l()
  GetAssemblies.l()
  AppendPrivatePath.l()
  ClearPrivatePath.l()
  SetShadowCopyPath.l()
  ClearShadowCopyPath.l()
  SetCachePath.l()
  SetData.l()
  GetData.l()
  SetAppDomainPolicy.l()
  SetThreadPrincipal.l()
  SetPrincipalPolicy.l()
  DoCallBack.l()
  get_DynamicDirectory.l()
EndInterface

Interface IAssembly Extends IDispatch
  get_ToString.l(*bstr)
  Equals.l()
  GetHashCode.l()
  GetType.l(*ptr)
  get_CodeBase.l()
  get_EscapedCodeBase.l()
  GetName.l()
  GetName_2.l()
  get_FullName.l(*bstr)
  get_EntryPoint.l(*ptr)
  GetType_2.i(bstr.p-bstr, *type)
  GetType_3.l()
  GetExportedTypes.l()
  GetTypes.l(*ptr)
  GetManifestResourceStream.l()
  GetManifestResourceStream_2.l()
  GetFile.l()
  GetFiles.l()
  GetFiles_2.l()
  GetManifestResourceNames.l()
  GetManifestResourceInfo.l()
  get_Location.l()
  get_Evidence.l()
  GetCustomAttributes.l()
  GetCustomAttributes_2.l()
  IsDefined.l()
  GetObjectData.l()
  add_ModuleResolve.l()
  remove_ModuleResolve.l()
  GetType_4.l()
  GetSatelliteAssembly.l()
  GetSatelliteAssembly_2.l()
  LoadModule.l()
  LoadModule_2.l()
  CreateInstance.l(bstr,*variant)
  CreateInstance_2.l(bstr.p-bstr,bool.l,*variant)
  CreateInstance_3.l(bstr,bool.l,INT.l,ptr,ptr,ptr,ptr,*variant)
  GetLoadedModules.l()
  GetLoadedModules_2.l()
  GetModules.l()
  GetModules_2.l()
  GetModule.l()
  GetReferencedAssemblies.l()
  get_GlobalAssemblyCache.l()
EndInterface

Interface IMethodInfo Extends IDispatch
  ToString.l()
  Equals.l()
  GetHashCode.l()
  GetType.l()
  MemberType.l()
  name.l(*bstr)
  DeclaringType.l()
  ReflectedType.l()
  GetCustomAttributes.l()
  GetCustomAttributes_2.l()
  IsDefined.l()
  GetParameters.l()
  GetMethodImplementationFlags.l()
  MethodHandle.l()
  Attributes.l()
  CallingConvention.l()
  Invoke_2.l()
  IsPublic.l()
  IsPrivate.l()
  IsFamily.l()
  IsAssembly.l()
  IsFamilyAndAssembly.l()
  IsFamilyOrAssembly.l()
  IsStatic.l()
  IsFinal.l()
  IsVirtual.l()
  IsHideBySig.l()
  IsAbstract.l()
  IsSpecialName.l()
  IsConstructor.l()
  Invoke_3.l(ptr,ptr,ptr,ptr,ptr,ptr)
  returnType.l()
  ReturnTypeCustomAttributes.l()
  GetBaseDefinition.l()
EndInterface

Interface IType  Extends IDispatch
  get_ToString(bstr)
  Equals(variant,short)
  GetHashCode(INT)
  GetType(ptr)
  get_MemberType(ptr)
  get_name(bstr)
  get_DeclaringType(ptr)
  get_ReflectedType(ptr)
  GetCustomAttributes(ptr,short,ptr)
  GetCustomAttributes_2(short,ptr)
  IsDefined(ptr,short,short)
  get_Guid(ptr)
  get_Module(ptr)
  get_Assembly(ptr)
  get_TypeHandle(ptr)
  get_FullName(bstr)
  get_Namespace(bstr)
  get_AssemblyQualifiedName(bstr)
  GetArrayRank(INT)
  get_BaseType(ptr)
  GetConstructors(ptr,ptr)
  GetInterface(bstr,short,ptr)
  GetInterfaces(ptr)
  FindInterfaces(ptr,variant,ptr)
  GetEvent(bstr,ptr,ptr)
  GetEvents(ptr)
  GetEvents_2(INT,ptr)
  GetNestedTypes(INT,ptr)
  GetNestedType(bstr,ptr,ptr)
  GetMember(bstr,ptr,ptr,ptr)
  GetDefaultMembers(ptr)
  FindMembers(ptr,ptr,ptr,variant,ptr)
  GetElementType(ptr)
  IsSubclassOf(ptr,short)
  IsInstanceOfType(variant,short)
  IsAssignableFrom(ptr,short)
  GetInterfaceMap(ptr,ptr)
  GetMethod(bstr,ptr,ptr,ptr,ptr,ptr)
  GetMethod_2(bstr,ptr,ptr)
  GetMethods(INT,ptr)
  GetField(bstr,ptr,ptr)
  GetFields(INT,ptr)
  GetProperty(bstr,ptr,ptr)
  GetProperty_2(bstr,ptr,ptr,ptr,ptr,ptr,ptr)
  GetProperties(ptr,ptr)
  GetMember_2(bstr,ptr,ptr)
  GetMembers(INT,ptr)
  InvokeMember(bstr,ptr,ptr,variant,ptr,ptr,ptr,ptr,variant)
  get_UnderlyingSystemType(ptr)
  InvokeMember_2(bstr,INT,ptr,variant,ptr,ptr,variant)
  InvokeMember_3(bstr.p-bstr,INT,ptr,ptr,ptr,ptr,ptr,ptr,variant)
  
  GetConstructor(ptr,ptr,ptr,ptr,ptr,ptr)
  GetConstructor_2(ptr,ptr,ptr,ptr,ptr)
  GetConstructor_3(ptr,ptr)
  GetConstructors_2(ptr)
  get_TypeInitializer(ptr)
  GetMethod_3(bstr,ptr,ptr,ptr,ptr,ptr,ptr)
  GetMethod_4(bstr,ptr,ptr,ptr)
  GetMethod_5(bstr,ptr,ptr)
  GetMethod_6(bstr,ptr)
  GetMethods_2(ptr)
  GetField_2(bstr,ptr)
  GetFields_2(ptr)
  GetInterface_2(bstr,ptr)
  GetEvent_2(bstr,ptr)
  GetProperty_3(bstr,ptr,ptr,ptr,ptr)
  GetProperty_4(bstr,ptr,ptr,ptr)
  GetProperty_5(bstr,ptr,ptr)
  GetProperty_6(bstr,ptr,ptr)
  GetProperty_7(bstr,ptr)
  GetProperties_2(ptr)
  GetNestedTypes_2(ptr)
  GetNestedType_2(bstr,ptr)
  GetMember_3(bstr,ptr)
  GetMembers_2(ptr)
  get_Attributes(ptr)
  get_IsNotPublic(short)
  get_IsPublic(short)
  get_IsNestedPublic(short)
  get_IsNestedPrivate(short)
  get_IsNestedFamily(short)
  get_IsNestedAssembly(short)
  get_IsNestedFamANDAssem(short)
  get_IsNestedFamORAssem(short)
  get_IsAutoLayout(short)
  get_IsLayoutSequential(short)
  get_IsExplicitLayout(short)
  get_IsClass(short)
  get_IsInterface(short)
  get_IsValueType(short)
  get_IsAbstract(short)
  get_IsSealed(short)
  get_IsEnum(short)
  get_IsSpecialName(short)
  get_IsImport(short)
  get_IsSerializable(short)
  get_IsAnsiClass(short)
  get_IsUnicodeClass(short)
  get_IsAutoClass(short)
  get_IsArray(short)
  get_IsByRef(short)
  get_IsPointer(short)
  get_IsPrimitive(short)
  get_IsCOMObject(short)
  get_HasElementType(short)
  get_IsContextful(short)
  get_IsMarshalByRef(short)
  Equals_2(ptr,short)
EndInterface

Prototype.i CLRCreateInstance(*CLSID, *IID, *Inter)
Global mscoree_dll = OpenLibrary(#PB_Any, "mscoree.dll")
Global CLRCreateInstance.CLRCreateInstance = GetFunction(mscoree_dll, "CLRCreateInstance")

Prototype SafeArrayCreate(a,b,c)
Prototype SafeArrayAccessData(a,b)
Prototype SafeArrayUnaccessData(a)
Prototype SafeArrayDestroy(a)
Prototype SafeArrayGetElement(a,b,c)
Global OleAut32 = OpenLibrary(#PB_Any,"OleAut32.dll")
Global SafeArrayCreate.SafeArrayCreate = GetFunction(OleAut32, "SafeArrayCreate")
Global SafeArrayAccessData.SafeArrayAccessData = GetFunction(OleAut32, "SafeArrayAccessData")
Global SafeArrayUnaccessData.SafeArrayUnaccessData = GetFunction(OleAut32, "SafeArrayUnaccessData")
Global SafeArrayDestroy.SafeArrayDestroy = GetFunction(OleAut32, "SafeArrayDestroy")
Global SafeArrayGetElement.SafeArrayGetElement=GetFunction(OleAut32,"SafeArrayGetElement")

Procedure __CLR_InvokeMember(pAssemblyIType.IType, Member.s, psa)
  If pAssemblyIType
    Protected pObject.VARIANT
    If pAssemblyIType\InvokeMember_3(Member,$158,0,0,0,0,0,psa,@pObject)=0
      ProcedureReturn pObject\byref
    EndIf
  EndIf
EndProcedure

Procedure __CLR_GetClass(pdllAssembly.IAssembly, Class.s)
  If pdllAssembly
    Protected.IType gpAssemblyType, pAssemblyIType
    If pdllAssembly\GetType_2(Class,@gpAssemblyType) = 0
      gpAssemblyType\QueryInterface(?IID_IType,@pAssemblyIType)
      gpAssemblyType\Release()
    EndIf
  EndIf
  ProcedureReturn pAssemblyIType
EndProcedure

Procedure __CLR_Free_Interface(pinterface.IUnknown)
  If pinterface <> 0 : pinterface\Release() : EndIf
EndProcedure

Procedure __CLR_Load_Dll(NetDllPath.s,RuntimeVersion.s)
  Macro OnErrorGo(V,erp)
    If V <> 0 : errorpos = erp: Goto __Error : EndIf
  EndMacro
  Protected pClrHost.ICLRMetaHost
  Protected pRunInfo.ICLRRuntimeInfo
  Protected pRuntimeHost.ICLRRuntimeHost
  Protected pCorRuntimeHost.ICorRuntimeHost
  Protected.AppDomain gpAppDomain,pAppDomain
  Protected pMethodInfo.IMethodInfo
  Protected.IType gpType, pIType
  Protected IsLoadable
  Protected.IAssembly gpIAssembly,pAssembly
  Protected.IType gpAssemblyType, pAssemblyIType
  Protected.IAssembly gpdllAssembly,pdllAssembly
  Protected psa,ppData.i,*v.VARIANT,rgsabound.SAFEARRAYBOUND,pObject.VARIANT
  Protected errorpos
  
  OnErrorGo(CLRCreateInstance(?CLSID_CLRMetaHost,?IID_ICLRMetaHost,@pClrHost), 1)
  OnErrorGo(pClrHost\GetRuntime(@RuntimeVersion,?IID_ICLRRuntimeInfo,@pRunInfo), 2)
  OnErrorGo(pRunInfo\IsLoadable(@IsLoadable), 3)
  OnErrorGo(IsLoadable-1, 3)
  OnErrorGo(pRunInfo\GetInterface(?CLSID_CLRRuntimeHost,?IID_ICLRRuntimeHost,@pRuntimeHost), 4)
  OnErrorGo(pRuntimeHost\Start(), 5)
  OnErrorGo(pRunInfo\GetInterface(?CLSID_CorRuntimeHost,?IID_ICorRuntimeHost,@pCorRuntimeHost), 6)
  OnErrorGo(pCorRuntimeHost\Start(), 7)
  OnErrorGo(pCorRuntimeHost\GetDefaultDomain(@gpAppDomain), 8)
  OnErrorGo(gpAppDomain\QueryInterface(?IID_AppDomain,@pAppDomain), 9)
  OnErrorGo(pAppDomain\GetType(@gpType), 10)
  OnErrorGo(gpType\QueryInterface(?IID_IType,@pIType), 11)
  OnErrorGo(gpType\get_Assembly(@gpIAssembly), 12)
  OnErrorGo(gpIAssembly\QueryInterface(?IID_IAssembly,@pAssembly), 13)
  OnErrorGo(pAssembly\GetType(@gpAssemblyType), 14)
  OnErrorGo(gpAssemblyType\QueryInterface(?IID_IType,@pAssemblyIType), 15)
  
  rgsabound\lLBound = 0
  rgsabound\cElements = 1
  psa = SafeArrayCreate(#VT_VARIANT,1,@rgsabound)
  If psa
    SafeArrayAccessData(psa, @ppData)
    *v = ppData
    *v\vt = #VT_BSTR
    *v\byref = SysAllocString_(NetDllPath)
    SafeArrayUnaccessData(psa)
  Else
    OnErrorGo(0, 16)
  EndIf
  
  If pAssemblyIType\InvokeMember_3("LoadFrom",$158,0,0,0,0,0,psa,pObject) <> 0
    OnErrorGo(pAssemblyIType\InvokeMember_3("LoadWithPartialName",$158,0,0,0,0,0,psa,pObject),17)
  EndIf
  gpdllAssembly = pObject\byref
  OnErrorGo(gpdllAssembly\QueryInterface(?IID_IAssembly,@pdllAssembly), 18)
  
  __Error:
  
  If errorpos 
    Debug "error line : " + errorpos
  EndIf
  
  __CLR_Free_Interface(pClrHost)
  __CLR_Free_Interface(pRunInfo)
  __CLR_Free_Interface(pRuntimeHost)
  __CLR_Free_Interface(pCorRuntimeHost)
  __CLR_Free_Interface(gpAppDomain)
  __CLR_Free_Interface(pAppDomain)
  __CLR_Free_Interface(pMethodInfo)
  __CLR_Free_Interface(gpType)
  __CLR_Free_Interface(pIType)
  __CLR_Free_Interface(gpIAssembly)
  __CLR_Free_Interface(pAssembly)
  __CLR_Free_Interface(gpAssemblyType)
  __CLR_Free_Interface(pAssemblyIType)
  __CLR_Free_Interface(gpdllAssembly)
  
  If psa
    SafeArrayDestroy(psa)
  EndIf
  
  ProcedureReturn pdllAssembly
EndProcedure

Procedure __CLR_GetObject(pdllAssembly.IAssembly, object.s)
  Protected pObject.VARIANT
  If pdllAssembly
    pdllAssembly\CreateInstance_2(object,1,pObject)
  EndIf
  ProcedureReturn pObject\byref
EndProcedure

Structure Var
  SafeArray.i
  type.l
  STR.s
  *SystemStr
  INT.l
  double.d
  quad.q
  float.f
  word.w
EndStructure

Procedure __CLR_FreeArguments(Array Arguments.Var(1))
  Protected arsize = ArraySize(Arguments()), i
  If Arguments(0)\SafeArray
    SafeArrayDestroy(Arguments(0)\SafeArray)
  EndIf
  For i = 1 To arsize
    If Arguments(i)\type = #PB_String
      If Arguments(i)\SystemStr
        SysFreeString_(Arguments(i)\SystemStr)
      EndIf
    EndIf
  Next
EndProcedure

; __CLR_PopArgs()
; This function takes the a SafeArray descriptor and returns each argument as an element in a dynamic array of type .Var
; The member \SafeArray of the first element (index 0) of the dynamic array must contain the pointer to the SafeArray itself. 
; To ensure precise compatibility, the SafeArray descriptor should have been created by the complement function __CLR_PushArgs().
;
Procedure __CLR_PopArgs(Array Arguments.Var(1))
  Protected arsize=ArraySize(Arguments())
  Protected ppData.i,*v.VARIANT,rgsabound.SAFEARRAYBOUND,pObject.VARIANT,i
  rgsabound\lLBound=0
  rgsabound\cElements=arsize
  Protected psa=Arguments(0)\SafeArray
  
  ; Check pointer to safe array is valid
  If psa
    ; Retrieves pointer to the array data (in ppData)
    SafeArrayAccessData(psa,@ppData)
    *v=ppData
    For i = 1 To arsize
      Select *v\vt
        Case #VT_BSTR
          Arguments(i)\type=*v\vt
          Arguments(i)\SystemStr=SysAllocString_(*v\byref)
          Arguments(i)\STR=PeekS(Arguments(i)\SystemStr)
          
        Case #VT_R8
          Arguments(i)\type=*v\vt
          Arguments(i)\double=PeekD(*v\byref)
          
        Case #VT_R4
          Arguments(i)\type=*v\vt
          Arguments(i)\float=PeekF(*v\byref)

        Case #VT_I8
          Arguments(i)\type=*v\vt
          Arguments(i)\quad=PeekQ(*v\byref)
          
        Case #VT_I4
          Arguments(i)\type=*v\vt
          Arguments(i)\INT=*v\byref
          
        Case #VT_I2
          Arguments(i)\type=*v\vt
          Arguments(i)\word=PeekW(*v\byref)
          
      EndSelect
      *v + SizeOf(VARIANT)
    Next
    SafeArrayUnaccessData(psa)
  EndIf
  
EndProcedure
  

; __CLR_PushArgs()
; This function takes a dynamic array (Type .Var) of arguments, creates a SafeArray descriptor and returns a pointer to that 
; descriptor. The first element (index 0) of the dynamic array is reserved for the pointer to the SafeArray itself.
;
; SafeArray descriptors are required by the Microsoft Unmanaged Host API For method argument passing. Calling code 
; should initialise each element of the dynamic array correctly with both the data type and value for the associated argument.
;
Procedure __CLR_PushArgs(Array Arguments.Var(1)) 
  Protected psa,ppData.i,*v.VARIANT,rgsabound.SAFEARRAYBOUND,pObject.VARIANT, i
  Protected arsize = ArraySize(Arguments())
  rgsabound\lLBound = 0
  rgsabound\cElements = arsize
  psa = SafeArrayCreate(#VT_VARIANT,1,@rgsabound)
  Arguments(0)\SafeArray = psa
  If psa
    SafeArrayAccessData(psa,@ppData)
    *v = ppData
    For i = 1 To arsize
      Select Arguments(i)\type
        Case #PB_String
          *v\vt = #VT_BSTR
          Arguments(i)\SystemStr=SysAllocString_(Arguments(i)\STR)
          *v\byref = Arguments(i)\SystemStr
        Case #PB_Long
          *v\vt = #VT_I4
          *v\byref = Arguments(i)\INT
        Case #PB_Double
          *v\vt = #VT_R8
          *v\byref = Arguments(i)\double
        Case #PB_Quad
          *v\vt = #VT_I8
          *v\byref = Arguments(i)\quad
        Case #PB_Float
          *v\vt = #VT_R4
          *v\byref = Arguments(i)\float
        Case #PB_Word
          *v\vt = #VT_I2
          *v\byref = Arguments(i)\word
      EndSelect
      *v + SizeOf(VARIANT)
    Next
    SafeArrayUnaccessData(psa)
  EndIf
  ProcedureReturn psa
EndProcedure
DisableExplicit


DataSection
  IID_ICLRMetaHost:
  Data.l $D332DB9E
  Data.w $B9B3, $4125
  Data.b $82, $07, $A1, $48, $84, $F5, $32, $16 
  
  CLSID_CLRMetaHost: 
  Data.l $9280188d
  Data.w $e8e, $4867
  Data.b $b3, $c, $7f, $a8, $38, $84, $e8, $de
  
  IID_ICLRRuntimeInfo:
  Data.l $BD39D1D2
  Data.w $BA2F, $486a
  Data.b $89, $B0, $B4, $B0, $CB, $46, $68, $91
  
  CLSID_CLRRuntimeHost:
  Data.l $90F1A06E
  Data.w $7712,$4762
  Data.b $86,$B5,$7A,$5E,$BA,$6B,$DB,$02
  
  IID_ICLRRuntimeHost:
  Data.l $90F1A06C
  Data.w $7712,$4762
  Data.b $86,$B5,$7A,$5E,$BA,$6B,$DB,$02
  
  CLSID_CorRuntimeHost:
  Data.l $cb2f6723
  Data.w $ab3a, $11d2
  Data.b $9c, $40, $00, $c0, $4f, $a3, $0a, $3e 
  
  IID_ICorRuntimeHost:
  Data.l $CB2F6722
  Data.w $AB3A,$11D2
  Data.b $9C,$40,$0,$C0,$4F,$A3,$A,$3E
  
  IID_AppDomain:
  Data.l $05F696DC
  Data.w $2B29,$3663
  Data.b $AD,$8B,$C4,$38,$9C,$F2,$A7,$13
  
  IID_IAssembly:
  Data.l $17156360
  Data.w $2F1A
  Data.w $384A
  Data.b $BC,$52,$FD,$E9,$3C,$21,$5C,$5B
  
  IID_MethodInfo:
  Data.l $FFCC1B5D
  Data.w $ECB8,$38DD
  Data.b $9B,$01,$3D,$C8,$AB,$C2,$AA,$5F
  
  IID_IType:
  Data.l $BCA8B44D
  Data.w $AAD6,$3A86
  Data.b $8A,$B7,$03,$34,$9F,$4F,$2D,$A2
EndDataSection
PureBasic Main Code clr.pb

Code: Select all

; This PureBasic code uses the .NET Unmanaged hosting API to call
; managed functions in a .NET DLL.
;
EnableExplicit

IncludeFile "CLR.PBI"
#NET_Runtime="v2.0.50727" ;"v4.0.30319"							; This must be compatible with the Runtime version required by the DLL

Dim Method_InArgs.Var(2)										; argument array (to pass args in to .NET methods)
Dim Method_OutArgs.Var(2)										; argument array (to pass args out from .NET methods)

dllAssembly=__CLR_Load_Dll("MyLibrary.dll",#NET_Runtime)		; Load the .NET assembly (.dll) And invoke specified .NET Runtime version 
class=__CLR_GetClass(dllAssembly,"MyNameSpace.MyClass")			; Load the class

Method_InArgs(1)\type=#PB_Long									; parameter Type
Method_InArgs(1)\INT=$98										; parameter value
Method_InArgs(2)\type=#PB_Long									; parameter Type
Method_InArgs(2)\INT=$101										; parameter value

psa=__CLR_PushArgs(Method_InArgs())								; Push arguments to a SafeArray
Debug "psa=0x"+Hex(psa)											;
ShowMemoryViewer(psa,100)										;

iResult=__CLR_InvokeMember(class,"MyMethod",psa)				; Call function of the class and get result as returncode
Debug "iResult=0x"+Hex(iResult)									; Display result (returncode)

Method_OutArgs(0)\SafeArray=psa									; Load target array element 0\SafeArray with address of SafeArray
__CLR_PopArgs(Method_OutArgs())									; Copy args from SafeArray to OutArgs()

For i=1 To ArraySize(Method_InArgs())
  Debug "Method_InArgs("+Str(i)+")\INT=0x"+Hex(Method_InArgs(i)\INT)
  Debug "Method_OutArgs("+Str(i)+")\INT=0x"+Hex(Method_OutArgs(i)\INT)
Next 

Main_Exit:
__CLR_FreeArguments(Method_InArgs())                             ; Free memory used by the arguments In array
__CLR_FreeArguments(Method_OutArgs())                             ; Free memory used by the arguments Out array
__CLR_Free_Interface(class)                                      ; Free memory used by the Class
__CLR_Free_Interface(dllAssembly)                                ; Free memory used by the Interface

Python Script:

Code: Select all

# Python Script - Testing .NET CLR access to a DLL
import clr
import System
from System import IO
from System import String
from System import Array
from System import Byte
from System import Boolean
from System import UInt32
from System import Text
from System import Int32

clr.AddReference("MyLibrary.dll")
from MyNameSpace import MyClass
NewMyClass=MyClass()

Result=0
OutParam1=0
OutParam2=0

InParam1=50
InParam2=55

[Result,OutParam1,OutParam2]=NewMyClass.MyMethod(InParam1,InParam2)

print("Result="+str(Result))
print("OutParam1="+str(OutParam1))
print("OutParam2="+str(OutParam2))
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: How to get back params passed byref (hosting .NET CLR with PB)

Post by breeze4me »

First of all, I'm a complete beginner at C#. :lol:
After searching and reviewing some ways, the code seems to be running properly if it is as below.

Edit: C# unsafe code is added.

C# dll code: (using Visual Studio Community 2022)

Code: Select all

using System;
using System.Runtime.InteropServices;

namespace MyNameSpace
{
	public class MyClass
	{
		public static int MyMethod(ref int Param1, ref int Param2)
		{

			IntPtr pValue1 = new IntPtr(Param1);
			IntPtr pValue2 = new IntPtr(Param2);
			int Value1 = Marshal.ReadInt32(pValue1);
			int Value2 = Marshal.ReadInt32(pValue2);

			Value1++;
			Marshal.WriteInt32(pValue1, Value1);

			Value2 = 0x1234;
			Marshal.WriteInt32(pValue2, Value2);

			return Value1;
		}
	}
}

The code below requires "unsafe" compilation setting.

Code: Select all

using System;

namespace MyNameSpace
{
	public class MyClass
	{
		public unsafe static int MyMethod(ref int Param1, ref int Param2)
		{
			int* v1 = (int*) Param1;
			int* v2 = (int*) Param2;

            *v1 = *v1 + 1;
            *v2 = 0x1234;

			return *v1;
		}
	}
}
PB code: (tested PB 6.00 x86 asm backend)

Code: Select all

; This PureBasic code uses the .NET Unmanaged hosting API to call
; managed functions in a .NET DLL.
;
;EnableExplicit

IncludeFile "CLR.PBI"
#NET_Runtime="v2.0.50727" ;"v4.0.30319"							; This must be compatible with the Runtime version required by the DLL

Dim Method_InArgs.Var(2)										; argument array (to pass args in to .NET methods)
Dim Method_OutArgs.Var(2)										; argument array (to pass args out from .NET methods)

dllAssembly=__CLR_Load_Dll("MyLibrary.dll",#NET_Runtime)		; Load the .NET assembly (.dll) And invoke specified .NET Runtime version
class=__CLR_GetClass(dllAssembly,"MyNameSpace.MyClass")			; Load the class


Define a.l = $98
Define b.l = $101

Method_InArgs(1)\type=#PB_Long									; parameter Type
Method_InArgs(1)\INT= @a

Method_InArgs(2)\type=#PB_Long									; parameter Type
Method_InArgs(2)\INT= @b

psa=__CLR_PushArgs(Method_InArgs())								; Push arguments to a SafeArray
Debug "psa=0x"+Hex(psa)											;
;ShowMemoryViewer(psa,100)										;

iResult=__CLR_InvokeMember(class,"MyMethod",psa)				; Call function of the class and get result as returncode
Debug "iResult=0x"+Hex(iResult)									; Display result (returncode)

Method_OutArgs(0)\SafeArray=psa									; Load target array element 0\SafeArray with address of SafeArray
__CLR_PopArgs(Method_OutArgs())									; Copy args from SafeArray to OutArgs()

Debug "-----------------------------"
Debug "*** variable address:"
Debug @a
Debug @b

For i=1 To ArraySize(Method_InArgs())
  Debug "Method_InArgs("+Str(i)+")\INT="+Method_InArgs(i)\INT
  Debug "Method_OutArgs("+Str(i)+")\INT="+Method_OutArgs(i)\INT
Next 

Debug "-----------------------------"
Debug "*** modified value:"
Debug "a = 0x" + Hex(a)
Debug "b = 0x" + Hex(b)



Main_Exit:
__CLR_FreeArguments(Method_InArgs())                             ; Free memory used by the arguments In array
__CLR_FreeArguments(Method_OutArgs())                             ; Free memory used by the arguments Out array
__CLR_Free_Interface(class)                                      ; Free memory used by the Class
__CLR_Free_Interface(dllAssembly)                                ; Free memory used by the Interface
User avatar
SnowyDog
User
User
Posts: 33
Joined: Tue Jun 10, 2014 8:18 pm

Re: How to get back params passed byref (hosting .NET CLR with PB)

Post by SnowyDog »

breeze4me wrote: Sun Jul 24, 2022 11:28 pm First of all, I'm a complete beginner at C#. :lol:
After searching and reviewing some ways, the code seems to be running properly if it is as below.
Thank you breeze4me, but this approach requires modification of the .NET DLL to add the Marshal class for types passed by reference and the solution I require cannot involve modification of the DLL.

However, I do now have a solution working in PB using COM.
Amundo
Enthusiast
Enthusiast
Posts: 200
Joined: Thu Feb 16, 2006 1:41 am
Location: New Zealand

Re: How to get back params passed byref (hosting .NET CLR with PB)

Post by Amundo »

Hi SnowyDog, thanks for sharing all this.

You mention that you now have a solution working using COM - this is what I'm trying to achieve also. Would you be willing to share what you have?

Understand if it's proprietary, etc.

Regards, Amundo
Win10, PB6.x, okayish CPU, onboard video card, fuzzy monitor (or is that my eyesight?)
"When the facts change, I change my mind" - John Maynard Keynes
Post Reply