Cleanup phase, don’t forgetrelease the value// when is not needed any moreif (MessageArg) {session->ReleaseValue( MessageArg ) ;}Call<strong>in</strong>g the <strong>PowerBuilder</strong> NVOfrom .NET Managed CodeNow we know how to call PBNI <strong>in</strong>C++ unmanaged code. The next step isto export the methods from the C++DLL and declare proper C# prototypesto make it available for C#. The__declspec(dllexport) modifier tells thecompiler to make the function visible(export) to other applications, so thatother applications can call this method.List<strong>in</strong>g 2 exports the PBInvoker class asDLL functions.Note: To check if the methods areproperly exported, you can use dumpb<strong>in</strong>.exeto see if the function names are<strong>in</strong> the list; here is an example <strong>of</strong> theoutput <strong>of</strong> dumpb<strong>in</strong>.exe show<strong>in</strong>g theexported symbols:dumpb<strong>in</strong> /exports PBInvoker.dll1 0 000115B4 _PBX_GetVersion@02 1 0001162C barcode4 3 000115FF closePBSession5 4 000116BD <strong>in</strong>itPBSessionParameter Marshal<strong>in</strong>g from.NET to Unmanaged CodeThe .NET Framework uses managedcode, which is an <strong>in</strong>terpreted <strong>in</strong>structionset, similar to Java bytecode or<strong>PowerBuilder</strong> P-Code. The C++ DLL usesunmanaged code, which is mach<strong>in</strong>ecode. This <strong>in</strong>troduces an issue becausethe data types <strong>in</strong> the .NET Frameworkneed to be marshaled (mean<strong>in</strong>g <strong>con</strong>vertbetween the C/C++ data type formatand the .NET data type format) to passback and forth between managed andunmanaged code. C# provides themeans to do this job.In this example, we need to callthree C++ DLL methods <strong>in</strong> C#:void <strong>in</strong>itPBSession(LPCTSTR libList[],<strong>in</strong>t libNum, LPCTSTR appName);void closePBSession();void barcode(LPCTSTR specimen_formatted, LPCTSTR spcimen_id,char *code);First, we need to tell C# that thesethree methods are from an unmanagedDLL. This is done by us<strong>in</strong>g the[DllImport] attribute tag <strong>in</strong> the functionprototype <strong>in</strong> C#. Also don’t forget to use“PBNI <strong>of</strong>fers a goodsolution to reuse<strong>PowerBuilder</strong>components <strong>in</strong> the.NET Framework”the “extern” keyword, mean<strong>in</strong>g thefunction exists outside the C# module:// pb<strong>in</strong>voker.dll is the DLL file name[DllImport("pb<strong>in</strong>voker.dll",CharSet=CharSet.Auto)]public static extern void closePBSession();Se<strong>con</strong>d, we need to use the proper[MarshalAs] attribute options to def<strong>in</strong>ethe marshal behaviors for LPCSTR[],<strong>in</strong>t, LPCSTR, and char* unmanaged C++parameters. The follow<strong>in</strong>g code showshow to def<strong>in</strong>e the marshal behaviors forthose three DLL methods:[DllImport("pb<strong>in</strong>voker.dll",CharSet=CharSet.Auto)]public static extern void<strong>in</strong>itPBSession([MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr)]str<strong>in</strong>g[]libList,<strong>in</strong>t libNum,[MarshalAs(UnmanagedType.LPStr)]str<strong>in</strong>g appName);[DllImport("pb<strong>in</strong>voker.dll",CharSet=CharSet.Auto)]public static extern void closePBSession();[DllImport("pb<strong>in</strong>voker.dll",CharSet=CharSet.Auto)]public static extern void barcode([MarshalAs(UnmanagedType.LPStr)]str<strong>in</strong>gspecimen_formatted,[MarshalAs(UnmanagedType.LPStr)]str<strong>in</strong>gspcimen_id,// Use C# Str<strong>in</strong>gBuilder for outputstr<strong>in</strong>g buffer[MarshalAs(UnmanagedType.LPStr)]Str<strong>in</strong>gBuilder code);There are some <strong>in</strong>terest<strong>in</strong>g th<strong>in</strong>gs Ineed to po<strong>in</strong>t out:1. [MarshalAs(UnmanagedType.LPArray,ArraySubType=Unmanaged-Type.LPStr)] is the way to marshalLPCTSTR[]. It tells C# the array typeand element type <strong>in</strong> C++, which is apo<strong>in</strong>ter array and a C str<strong>in</strong>g po<strong>in</strong>ter<strong>in</strong> each element <strong>in</strong> our case.2. [MarshalAs(UnmanagedType.LPStr)]Str<strong>in</strong>gBuilder is the way to marshalan output str<strong>in</strong>g buffer. The Str<strong>in</strong>g-Builder should have enough space tohold the return value from the DLLmethod; you can allocate the spacefor the Str<strong>in</strong>gBuilder and then call theDLL method <strong>in</strong> C#.Here is the C# code to <strong>in</strong>voke theDLL methods:// allocate buffer for the outputstr<strong>in</strong>gStr<strong>in</strong>gBuilder buf = new Str<strong>in</strong>g-Builder(256);// Init PBInvoker, automation is the<strong>PowerBuilder</strong> application object name<strong>in</strong>itPBSession(new Str<strong>in</strong>g[] {"automation.pbl"},1, "automation");// Call the methodbarcode("C05-10", "tpro402137", buf);Console.Out.WriteL<strong>in</strong>e("sb = {0}", buf);// Clean upclosePBSession();Creat<strong>in</strong>g a Generic<strong>PowerBuilder</strong> Invoker for C#We have discussed some basic techniquesfor us<strong>in</strong>g <strong>PowerBuilder</strong> NVOs <strong>in</strong>C#. One <strong>in</strong>terest<strong>in</strong>g idea is to create atool to generate both the C++ proxyDLL and C# prototypes based on theselected NVOs and methods. To generatethese proxy files, all we need is a list<strong>of</strong> NVOs <strong>in</strong> the PBL and prototypes <strong>of</strong>each method. Fortunately, pbsig100.exedoes it for us. We can parse the output<strong>of</strong> pbsig100.exe to retrieve the metadata<strong>of</strong> any PBL file. The rest <strong>of</strong> the work is togenerate the source code based on theprototypes, which is fairly easy to do. I’llcover the details <strong>of</strong> the code generation<strong>in</strong> a follow-up article.ConclusionPBNI is a great <strong>in</strong>novation <strong>in</strong> Power-Builder. It opens a brand new space fordevelopers to reuse and extend Power-Builder. PBNI <strong>of</strong>fers a good solution toreuse <strong>PowerBuilder</strong> components <strong>in</strong> the.NET Framework.To f<strong>in</strong>d more <strong>in</strong>formation aboutPBNI, please reference the PBNI SDKdocument and CodeXchange for examples.▼david.huo@sybase.com28 PBDJ volume12 issue11pbdj.<strong>sys</strong>-<strong>con</strong>.com
List<strong>in</strong>g 1// Def<strong>in</strong>e the function po<strong>in</strong>ter for PB_GetVMtypedef PBXEXPORT PBXRESULT (*P_PB_GetVM)(IPB_VM** vm);// <strong>con</strong>structorCPBInvoker::CPBInvoker(LPCTSTR LibList[], <strong>in</strong>t libNum, LPCT-STR appName){_h<strong>in</strong>st = NULL; // a member variable to hold the DLL handleIPB_VM* pbvm;_h<strong>in</strong>st = LoadLibrary("pbvm100.dll"); // step2: load PBVMif ( _h<strong>in</strong>st == NULL ) {throw "Loaded PBVM successfully";}// step 3: Get PBVM referenceP_PB_GetVM getvm =(P_PB_GetVM)GetProcAddress(_h<strong>in</strong>st,"PB_GetVM");if (getvm == NULL) {throw "PB_GetVM failed";}getvm(&pbvm);if (pbvm == NULL) {throw "getvm failed";}// step 4: Create IPB_Session <strong>in</strong>stance, LibList is anarray holds the pbl file namesif ( pbvm->CreateSession(appName, LibList, libNum, &_session)!= PBX_OK ) {throw "Error <strong>in</strong> CreateSession";}}// destructorCPBInvoker::~CPBInvoker(){// step 13: Release IPB_Sessionif( NULL != _session ) {_session->Release();}// unload PBVM DLLif( NULL != _h<strong>in</strong>st ) {FreeLibrary(_h<strong>in</strong>st);}}// Call the NVO methodvoid CPBInvoker::barcode(LPCTSTR text, LPCTSTR format,char* code){// step 5: F<strong>in</strong>d the group, n_automation is the NVO’snamepbgroup group = _session->F<strong>in</strong>dGroup("n_automation",pbgroup_userobject);if (group == NULL) throw "n_automation is not found!";// step 6: F<strong>in</strong>d the class, n_automation is the NVO’snamepbclass cls = _session->F<strong>in</strong>dClass(group,"n_automation");if (cls == NULL) throw "n_automation class notfound!";objectpbobject pbobj = _session->NewObject(cls);// PBCallInfo <strong>con</strong>ta<strong>in</strong>s arguments and return valuePBCallInfo ci;// step 8: GetMethodID to get the method. “SSS” is thesignature str<strong>in</strong>g, fn_barcode is// the function namepbmethodID mid = _session->GetMethodID(cls, "fn_barcode",PBRT_FUNCTION, "SSS");// step 9: Initialize call <strong>in</strong>fo structure based onmethod ID_session->InitCallInfo(cls, mid, &ci);// step 10: Fill <strong>in</strong> parameters.ci.pArgs-> GetAt(0)->SetStr<strong>in</strong>g(text);ci.pArgs-> GetAt(1)->SetStr<strong>in</strong>g(format);// step 11: <strong>in</strong>voke the method_session->InvokeObjectFunction(pbobj, mid, &ci);// step 12: Retrieve the return value, code is a validmemory// buffer passed <strong>in</strong> by the callerpbstr<strong>in</strong>g ret = ci.returnValue->GetStr<strong>in</strong>g();strcpy( code, _session->GetStr<strong>in</strong>g(ret) );}// step 13: Release the PBCallInfo structure_session->FreeCallInfo( &ci );List<strong>in</strong>g 2#def<strong>in</strong>e PBINVOKER_API __declspec(dllexport)PBINVOKER_API void barcode(LPCTSTR specimen_formatted,LPCTSTR spcimen_id, char *code){// _<strong>in</strong>voker is a global variable holds the reference <strong>of</strong>the PBInvoker objectif( NULL != _<strong>in</strong>voker ) {// call the NVO method_<strong>in</strong>voker->barcode(specimen_formatted, spcimen_id, code);}}PBINVOKER_API void <strong>in</strong>itPBSession(LPCTSTR libList[], <strong>in</strong>tlibNum, LPCTSTR appName){// <strong>in</strong>it the IPB_Sessionif( NULL == _<strong>in</strong>voker ) {// <strong>in</strong>stantiate the CPBInvoker wrapper class_<strong>in</strong>voker = new CPBInvoker(libList, libNum, appName);}}PBINVOKER_API void closePBSession(){// clean upif( NULL != _<strong>in</strong>voker ) {// delete the wrapper objectdelete _<strong>in</strong>voker;_<strong>in</strong>voker = NULL;}}// step 7: Create an <strong>in</strong>stance <strong>of</strong> the <strong>PowerBuilder</strong>pbdj.<strong>sys</strong>-<strong>con</strong>.comPBDJ volume12 issue1129